/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-9 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ /* This monolithic file contains the entire Juce source tree! To build an app which uses Juce, all you need to do is to add this file to your project, and include juce.h in your own cpp files. */ #ifdef __JUCE_JUCEHEADER__ /* When you add the amalgamated cpp file to your project, you mustn't include it in a file where you've already included juce.h - just put it inside a file on its own, possibly with your config flags preceding it, but don't include anything else. */ #error #endif /*** Start of inlined file: juce_TargetPlatform.h ***/ #ifndef __JUCE_TARGETPLATFORM_JUCEHEADER__ #define __JUCE_TARGETPLATFORM_JUCEHEADER__ /* This file figures out which platform is being built, and defines some macros that the rest of the code can use for OS-specific compilation. Macros that will be set here are: - One of JUCE_WINDOWS, JUCE_MAC or JUCE_LINUX. - Either JUCE_32BIT or JUCE_64BIT, depending on the architecture. - Either JUCE_LITTLE_ENDIAN or JUCE_BIG_ENDIAN. - Either JUCE_INTEL or JUCE_PPC - Either JUCE_GCC or JUCE_MSVC */ #if (defined (_WIN32) || defined (_WIN64)) #define JUCE_WIN32 1 #define JUCE_WINDOWS 1 #elif defined (LINUX) || defined (__linux__) #define JUCE_LINUX 1 #elif defined(__APPLE_CPP__) || defined(__APPLE_CC__) #include // (needed to find out what platform we're using) #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR #define JUCE_IPHONE 1 #define JUCE_IOS 1 #else #define JUCE_MAC 1 #endif #else #error "Unknown platform!" #endif #if JUCE_WINDOWS #ifdef _MSC_VER #ifdef _WIN64 #define JUCE_64BIT 1 #else #define JUCE_32BIT 1 #endif #endif #ifdef _DEBUG #define JUCE_DEBUG 1 #endif #ifdef __MINGW32__ #define JUCE_MINGW 1 #endif /** If defined, this indicates that the processor is little-endian. */ #define JUCE_LITTLE_ENDIAN 1 #define JUCE_INTEL 1 #endif #if JUCE_MAC #ifndef NDEBUG #define JUCE_DEBUG 1 #endif #ifdef __LITTLE_ENDIAN__ #define JUCE_LITTLE_ENDIAN 1 #else #define JUCE_BIG_ENDIAN 1 #endif #if defined (__ppc__) || defined (__ppc64__) #define JUCE_PPC 1 #else #define JUCE_INTEL 1 #endif #ifdef __LP64__ #define JUCE_64BIT 1 #else #define JUCE_32BIT 1 #endif #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_4 #error "Building for OSX 10.3 is no longer supported!" #endif #ifndef MAC_OS_X_VERSION_10_5 #error "To build with 10.4 compatibility, use a 10.5 or 10.6 SDK and set the deployment target to 10.4" #endif #endif #if JUCE_IOS #ifndef NDEBUG #define JUCE_DEBUG 1 #endif #ifdef __LITTLE_ENDIAN__ #define JUCE_LITTLE_ENDIAN 1 #else #define JUCE_BIG_ENDIAN 1 #endif #endif #if JUCE_LINUX #ifdef _DEBUG #define JUCE_DEBUG 1 #endif // Allow override for big-endian Linux platforms #ifndef JUCE_BIG_ENDIAN #define JUCE_LITTLE_ENDIAN 1 #endif #if defined (__LP64__) || defined (_LP64) #define JUCE_64BIT 1 #else #define JUCE_32BIT 1 #endif #define JUCE_INTEL 1 #endif // Compiler type macros. #ifdef __GNUC__ #define JUCE_GCC 1 #elif defined (_MSC_VER) #define JUCE_MSVC 1 #if _MSC_VER >= 1400 #define JUCE_USE_INTRINSICS 1 #endif #else #error unknown compiler #endif #endif // __JUCE_TARGETPLATFORM_JUCEHEADER__ /*** End of inlined file: juce_TargetPlatform.h ***/ // FORCE_AMALGAMATOR_INCLUDE /*** Start of inlined file: juce_Config.h ***/ #ifndef __JUCE_CONFIG_JUCEHEADER__ #define __JUCE_CONFIG_JUCEHEADER__ /* This file contains macros that enable/disable various JUCE features. */ /** The name of the namespace that all Juce classes and functions will be put inside. If this is not defined, no namespace will be used. */ #ifndef JUCE_NAMESPACE #define JUCE_NAMESPACE juce #endif /** JUCE_FORCE_DEBUG: Normally, JUCE_DEBUG is set to 1 or 0 based on compiler and project settings, but if you define this value, you can override this to force it to be true or false. */ #ifndef JUCE_FORCE_DEBUG //#define JUCE_FORCE_DEBUG 0 #endif /** JUCE_LOG_ASSERTIONS: If this flag is enabled, the the jassert and jassertfalse macros will always use Logger::writeToLog() to write a message when an assertion happens. Enabling it will also leave this turned on in release builds. When it's disabled, however, the jassert and jassertfalse macros will not be compiled in a release build. @see jassert, jassertfalse, Logger */ #ifndef JUCE_LOG_ASSERTIONS #define JUCE_LOG_ASSERTIONS 0 #endif /** JUCE_ASIO: Enables ASIO audio devices (MS Windows only). Turning this on means that you'll need to have the Steinberg ASIO SDK installed on your Windows build machine. See the comments in the ASIOAudioIODevice class's header file for more info about this. */ #ifndef JUCE_ASIO #define JUCE_ASIO 0 #endif /** JUCE_WASAPI: Enables WASAPI audio devices (Windows Vista and above). */ #ifndef JUCE_WASAPI #define JUCE_WASAPI 0 #endif /** JUCE_DIRECTSOUND: Enables DirectSound audio (MS Windows only). */ #ifndef JUCE_DIRECTSOUND #define JUCE_DIRECTSOUND 1 #endif /** JUCE_ALSA: Enables ALSA audio devices (Linux only). */ #ifndef JUCE_ALSA #define JUCE_ALSA 1 #endif /** JUCE_JACK: Enables JACK audio devices (Linux only). */ #ifndef JUCE_JACK #define JUCE_JACK 0 #endif /** JUCE_QUICKTIME: Enables the QuickTimeMovieComponent class (Mac and Windows). If you're building on Windows, you'll need to have the Apple QuickTime SDK installed, and its header files will need to be on your include path. */ #if ! (defined (JUCE_QUICKTIME) || JUCE_LINUX || JUCE_IOS || (JUCE_WINDOWS && ! JUCE_MSVC)) #define JUCE_QUICKTIME 0 #endif #if (JUCE_IOS || JUCE_LINUX) && JUCE_QUICKTIME #undef JUCE_QUICKTIME #endif /** JUCE_OPENGL: Enables the OpenGLComponent class (available on all platforms). If you're not using OpenGL, you might want to turn this off to reduce your binary's size. */ #ifndef JUCE_OPENGL #define JUCE_OPENGL 1 #endif /** JUCE_DIRECT2D: Enables the Windows 7 Direct2D renderer. If you're building on a platform older than Vista, you won't be able to compile with this feature. */ #ifndef JUCE_DIRECT2D #define JUCE_DIRECT2D 0 #endif /** JUCE_USE_FLAC: Enables the FLAC audio codec classes (available on all platforms). If your app doesn't need to read FLAC files, you might want to disable this to reduce the size of your codebase and build time. */ #ifndef JUCE_USE_FLAC #define JUCE_USE_FLAC 1 #endif /** JUCE_USE_OGGVORBIS: Enables the Ogg-Vorbis audio codec classes (available on all platforms). If your app doesn't need to read Ogg-Vorbis files, you might want to disable this to reduce the size of your codebase and build time. */ #ifndef JUCE_USE_OGGVORBIS #define JUCE_USE_OGGVORBIS 1 #endif /** JUCE_USE_CDBURNER: Enables the audio CD reader code (Mac and Windows only). Unless you're using CD-burning, you should probably turn this flag off to reduce code size. */ #if (! defined (JUCE_USE_CDBURNER)) && ! (JUCE_WINDOWS && ! JUCE_MSVC) #define JUCE_USE_CDBURNER 0 #endif /** JUCE_USE_CDREADER: Enables the audio CD reader code (Mac and Windows only). Unless you're using CD-reading, you should probably turn this flag off to reduce code size. */ #ifndef JUCE_USE_CDREADER #define JUCE_USE_CDREADER 0 #endif /** JUCE_USE_CAMERA: Enables web-cam support using the CameraDevice class (Mac and Windows). */ #if (JUCE_QUICKTIME || JUCE_WINDOWS) && ! defined (JUCE_USE_CAMERA) #define JUCE_USE_CAMERA 0 #endif /** JUCE_ENABLE_REPAINT_DEBUGGING: If this option is turned on, each area of the screen that gets repainted will flash in a random colour, so that you can check exactly how much and how often your components are being drawn. */ #ifndef JUCE_ENABLE_REPAINT_DEBUGGING #define JUCE_ENABLE_REPAINT_DEBUGGING 0 #endif /** JUCE_USE_XINERAMA: Enables Xinerama multi-monitor support (Linux only). Unless you specifically want to disable this, it's best to leave this option turned on. */ #ifndef JUCE_USE_XINERAMA #define JUCE_USE_XINERAMA 1 #endif /** JUCE_USE_XSHM: Enables X shared memory for faster rendering on Linux. This is best left turned on unless you have a good reason to disable it. */ #ifndef JUCE_USE_XSHM #define JUCE_USE_XSHM 1 #endif /** JUCE_USE_XRENDER: Uses XRender to allow semi-transparent windowing on Linux. */ #ifndef JUCE_USE_XRENDER #define JUCE_USE_XRENDER 0 #endif /** JUCE_USE_XCURSOR: Uses XCursor to allow ARGB cursor on Linux. This is best left turned on unless you have a good reason to disable it. */ #ifndef JUCE_USE_XCURSOR #define JUCE_USE_XCURSOR 1 #endif /** JUCE_PLUGINHOST_VST: Enables the VST audio plugin hosting classes. This requires the Steinberg VST SDK to be installed on your machine, and should be left turned off unless you're building a plugin hosting app. @see VSTPluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_AU */ #ifndef JUCE_PLUGINHOST_VST #define JUCE_PLUGINHOST_VST 0 #endif /** JUCE_PLUGINHOST_AU: Enables the AudioUnit plugin hosting classes. This is Mac-only, of course, and should only be enabled if you're building a plugin hosting app. @see AudioUnitPluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_VST */ #ifndef JUCE_PLUGINHOST_AU #define JUCE_PLUGINHOST_AU 0 #endif /** JUCE_ONLY_BUILD_CORE_LIBRARY: Enabling this will avoid including any UI classes in the build. This should be enabled if you're writing a console application. */ #ifndef JUCE_ONLY_BUILD_CORE_LIBRARY #define JUCE_ONLY_BUILD_CORE_LIBRARY 0 #endif /** JUCE_WEB_BROWSER: This lets you disable the WebBrowserComponent class (Mac and Windows). If you're not using any embedded web-pages, turning this off may reduce your code size. */ #ifndef JUCE_WEB_BROWSER #define JUCE_WEB_BROWSER 1 #endif /** JUCE_SUPPORT_CARBON: Enabling this allows the Mac code to use old Carbon library functions. Carbon isn't required for a normal app, but may be needed by specialised classes like plugin-hosts, which support older APIs. */ #ifndef JUCE_SUPPORT_CARBON #define JUCE_SUPPORT_CARBON 1 #endif /* JUCE_INCLUDE_ZLIB_CODE: Can be used to disable Juce's embedded 3rd-party zlib code. You might need to tweak this if you're linking to an external zlib library in your app, but for normal apps, this option should be left alone. */ #ifndef JUCE_INCLUDE_ZLIB_CODE #define JUCE_INCLUDE_ZLIB_CODE 1 #endif #ifndef JUCE_INCLUDE_FLAC_CODE #define JUCE_INCLUDE_FLAC_CODE 1 #endif #ifndef JUCE_INCLUDE_OGGVORBIS_CODE #define JUCE_INCLUDE_OGGVORBIS_CODE 1 #endif #ifndef JUCE_INCLUDE_PNGLIB_CODE #define JUCE_INCLUDE_PNGLIB_CODE 1 #endif #ifndef JUCE_INCLUDE_JPEGLIB_CODE #define JUCE_INCLUDE_JPEGLIB_CODE 1 #endif /** JUCE_CHECK_MEMORY_LEAKS: Enables a memory-leak check when an app terminates. (Currently, this only affects Windows builds in debug mode). */ #ifndef JUCE_CHECK_MEMORY_LEAKS #define JUCE_CHECK_MEMORY_LEAKS 1 #endif /** JUCE_CATCH_UNHANDLED_EXCEPTIONS: Turn on juce's internal catching of exceptions that are thrown by the message dispatch loop. With it enabled, any unhandled exceptions are passed to the JUCEApplication::unhandledException() callback for logging. */ #ifndef JUCE_CATCH_UNHANDLED_EXCEPTIONS #define JUCE_CATCH_UNHANDLED_EXCEPTIONS 1 #endif // If only building the core classes, we can explicitly turn off some features to avoid including them: #if JUCE_ONLY_BUILD_CORE_LIBRARY #undef JUCE_QUICKTIME #define JUCE_QUICKTIME 0 #undef JUCE_OPENGL #define JUCE_OPENGL 0 #undef JUCE_USE_CDBURNER #define JUCE_USE_CDBURNER 0 #undef JUCE_USE_CDREADER #define JUCE_USE_CDREADER 0 #undef JUCE_WEB_BROWSER #define JUCE_WEB_BROWSER 0 #undef JUCE_PLUGINHOST_AU #define JUCE_PLUGINHOST_AU 0 #undef JUCE_PLUGINHOST_VST #define JUCE_PLUGINHOST_VST 0 #endif #endif /*** End of inlined file: juce_Config.h ***/ // FORCE_AMALGAMATOR_INCLUDE #ifndef JUCE_BUILD_CORE #define JUCE_BUILD_CORE 1 #endif #ifndef JUCE_BUILD_MISC #define JUCE_BUILD_MISC 1 #endif #ifndef JUCE_BUILD_GUI #define JUCE_BUILD_GUI 1 #endif #ifndef JUCE_BUILD_NATIVE #define JUCE_BUILD_NATIVE 1 #endif #if JUCE_ONLY_BUILD_CORE_LIBRARY #undef JUCE_BUILD_MISC #undef JUCE_BUILD_GUI #endif //============================================================================== #if JUCE_BUILD_NATIVE || JUCE_BUILD_CORE || (JUCE_BUILD_MISC && (JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_AU)) #if JUCE_WINDOWS /*** Start of inlined file: juce_win32_NativeIncludes.h ***/ #ifndef __JUCE_WIN32_NATIVEINCLUDES_JUCEHEADER__ #define __JUCE_WIN32_NATIVEINCLUDES_JUCEHEADER__ #ifndef STRICT #define STRICT 1 #endif #undef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN 1 #if JUCE_MSVC #pragma warning (push) #pragma warning (disable : 4100 4201 4514 4312 4995) #endif #define _WIN32_WINNT 0x0500 #define _UNICODE 1 #define UNICODE 1 #ifndef _WIN32_IE #define _WIN32_IE 0x0400 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if ! JUCE_MINGW #include #include #endif #if JUCE_OPENGL #include #endif #undef PACKED #if JUCE_ASIO /* This is very frustrating - we only need to use a handful of definitions from a couple of the header files in Steinberg's ASIO SDK, and it'd be easy to copy about 30 lines of code into this cpp file to create a fully stand-alone ASIO implementation... ..unfortunately that would break Steinberg's license agreement for use of their SDK, so I'm not allowed to do this. This means that anyone who wants to use JUCE's ASIO abilities will have to: 1) Agree to Steinberg's licensing terms and download the ASIO SDK (see www.steinberg.net/Steinberg/Developers.asp). 2) Rebuild the whole of JUCE, setting the global definition JUCE_ASIO (you can un-comment the "#define JUCE_ASIO" line in juce_Config.h if you prefer). Make sure that your header search path will find the iasiodrv.h file that comes with the SDK. (Only about 2-3 of the SDK header files are actually needed - so to simplify things, you could just copy these into your JUCE directory). If you're compiling and you get an error here because you don't have the ASIO SDK installed, you can disable ASIO support by commenting-out the "#define JUCE_ASIO" line in juce_Config.h, and rebuild your Juce library. */ #include "iasiodrv.h" #endif #if JUCE_USE_CDBURNER /* You'll need the Platform SDK for these headers - if you don't have it and don't need to use CD-burning, then you might just want to disable the JUCE_USE_CDBURNER flag in juce_Config.h to avoid these includes. */ #include #include #endif #if JUCE_USE_CAMERA /* If you're using the camera classes, you'll need access to a few DirectShow headers. These files are provided in the normal Windows SDK, but some Microsoft plonker didn't realise that qedit.h doesn't actually compile without the rest of the DirectShow SDK.. Microsoft's suggested fix for this is to hack their qedit.h file! See: http://social.msdn.microsoft.com/Forums/en-US/windowssdk/thread/ed097d2c-3d68-4f48-8448-277eaaf68252 .. which is a bit of a bodge, but a lot less hassle than installing the full DShow SDK. An alternative workaround is to create a dummy dxtrans.h file and put it in your include path. The dummy file just needs to contain the following content: #define __IDxtCompositor_INTERFACE_DEFINED__ #define __IDxtAlphaSetter_INTERFACE_DEFINED__ #define __IDxtJpeg_INTERFACE_DEFINED__ #define __IDxtKey_INTERFACE_DEFINED__ ..and that should be enough to convince qedit.h that you have the SDK! */ #include #include #include #endif #if JUCE_WASAPI #include #include #include #include #include #endif #if JUCE_QUICKTIME /* If you've got an include error here, you probably need to install the QuickTime SDK and add its header directory to your include path. Alternatively, if you don't need any QuickTime services, just turn off the JUCE_QUICKTIME flag in juce_Config.h */ #include #include #include #include #include /* If you've got QuickTime 7 installed, then these COM objects should be found in the "\Program Files\Quicktime" directory. You'll need to add this directory to your include search path to make these import statements work. */ #import #import #endif #if JUCE_MSVC #pragma warning (pop) #endif #if JUCE_DIRECT2D #include #include #endif /** A simple COM smart pointer. Avoids having to include ATL just to get one of these. */ template class ComSmartPtr { public: ComSmartPtr() throw() : p (0) {} ComSmartPtr (ComClass* const p_) : p (p_) { if (p_ != 0) p_->AddRef(); } ComSmartPtr (const ComSmartPtr& p_) : p (p_.p) { if (p != 0) p->AddRef(); } ~ComSmartPtr() { if (p != 0) p->Release(); } operator ComClass*() const throw() { return p; } ComClass& operator*() const throw() { return *p; } ComClass** operator&() throw() { return &p; } ComClass* operator->() const throw() { return p; } ComClass* operator= (ComClass* const newP) { if (newP != 0) newP->AddRef(); if (p != 0) p->Release(); p = newP; return newP; } ComClass* operator= (const ComSmartPtr& newP) { return operator= (newP.p); } HRESULT CoCreateInstance (REFCLSID rclsid, DWORD dwClsContext = CLSCTX_INPROC_SERVER) { #ifndef __MINGW32__ operator= (0); return ::CoCreateInstance (rclsid, 0, dwClsContext, __uuidof (ComClass), (void**) &p); #else return S_FALSE; #endif } private: ComClass* p; }; /** Handy base class for writing COM objects, providing ref-counting and a basic QueryInterface method. */ template class ComBaseClassHelper : public ComClass { public: ComBaseClassHelper() : refCount (1) {} virtual ~ComBaseClassHelper() {} HRESULT __stdcall QueryInterface (REFIID refId, void __RPC_FAR* __RPC_FAR* result) { #ifndef __MINGW32__ if (refId == __uuidof (ComClass)) { AddRef(); *result = dynamic_cast (this); return S_OK; } #endif if (refId == IID_IUnknown) { AddRef(); *result = dynamic_cast (this); return S_OK; } *result = 0; return E_NOINTERFACE; } ULONG __stdcall AddRef() { return ++refCount; } ULONG __stdcall Release() { const int r = --refCount; if (r == 0) delete this; return r; } protected: int refCount; }; #endif // __JUCE_WIN32_NATIVEINCLUDES_JUCEHEADER__ /*** End of inlined file: juce_win32_NativeIncludes.h ***/ #elif JUCE_LINUX /*** Start of inlined file: juce_linux_NativeIncludes.h ***/ #ifndef __JUCE_LINUX_NATIVEINCLUDES_JUCEHEADER__ #define __JUCE_LINUX_NATIVEINCLUDES_JUCEHEADER__ /* This file wraps together all the linux-specific headers, so that we can include them all just once, and compile all our platform-specific stuff in one big lump, keeping it out of the way of the rest of the codebase. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Got a build error here? You'll need to install the freetype library... The name of the package to install is "libfreetype6-dev". */ #include #include FT_FREETYPE_H #include #include #include #include #include #include #include #if JUCE_USE_XINERAMA /* If you're trying to use Xinerama, you'll need to install the "libxinerama-dev" package.. */ #include #endif #if JUCE_USE_XSHM #include #include #include #endif #if JUCE_USE_XRENDER // If you're missing these headers, try installing the libxrender-dev and libxcomposite-dev #include #include #endif #if JUCE_USE_XCURSOR // If you're missing this header, try installing the libxcursor-dev package #include #endif #if JUCE_OPENGL /* Got an include error here? If you want to install OpenGL support, the packages to get are "mesa-common-dev" and "freeglut3-dev". Alternatively, you can turn off the JUCE_OPENGL flag in juce_Config.h if you want to disable it. */ #include #endif #undef KeyPress #if JUCE_ALSA /* Got an include error here? If so, you've either not got ALSA installed, or you've not got your paths set up correctly to find its header files. The package you need to install to get ASLA support is "libasound2-dev". If you don't have the ALSA library and don't want to build Juce with audio support, just disable the JUCE_ALSA flag in juce_Config.h */ #include #endif #if JUCE_JACK /* Got an include error here? If so, you've either not got jack-audio-connection-kit installed, or you've not got your paths set up correctly to find its header files. The package you need to install to get JACK support is "libjack-dev". If you don't have the jack-audio-connection-kit library and don't want to build Juce with low latency audio support, just disable the JUCE_JACK flag in juce_Config.h */ #include //#include #endif #undef SIZEOF #endif // __JUCE_LINUX_NATIVEINCLUDES_JUCEHEADER__ /*** End of inlined file: juce_linux_NativeIncludes.h ***/ #elif JUCE_MAC || JUCE_IPHONE /*** Start of inlined file: juce_mac_NativeIncludes.h ***/ #ifndef __JUCE_MAC_NATIVEINCLUDES_JUCEHEADER__ #define __JUCE_MAC_NATIVEINCLUDES_JUCEHEADER__ /* This file wraps together all the mac-specific code, so that we can include all the native headers just once, and compile all our platform-specific stuff in one big lump, keeping it out of the way of the rest of the codebase. */ #define USE_COREGRAPHICS_RENDERING 1 #if JUCE_IOS #import #import #import #import #import #import #import #include #if JUCE_OPENGL #include #include #endif #else #import #import #import #import #import #import #import #import #import #import #import #import #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #if MACOS_10_4_OR_EARLIER #include #endif #if ! CGFLOAT_DEFINED #define CGFloat float #endif #endif // __JUCE_MAC_NATIVEINCLUDES_JUCEHEADER__ /*** End of inlined file: juce_mac_NativeIncludes.h ***/ #else #error "Unknown platform!" #endif #endif //============================================================================== #define DONT_SET_USING_JUCE_NAMESPACE 1 #undef max #undef min #define NO_DUMMY_DECL #if JUCE_BUILD_NATIVE #include "juce_amalgamated.h" // FORCE_AMALGAMATOR_INCLUDE #endif #if (defined(_MSC_VER) && (_MSC_VER <= 1200)) #pragma warning (disable: 4309 4305) #endif #if JUCE_MAC && JUCE_32BIT && JUCE_SUPPORT_CARBON && JUCE_BUILD_NATIVE && ! JUCE_ONLY_BUILD_CORE_LIBRARY BEGIN_JUCE_NAMESPACE /*** Start of inlined file: juce_mac_CarbonViewWrapperComponent.h ***/ #ifndef __JUCE_MAC_CARBONVIEWWRAPPERCOMPONENT_JUCEHEADER__ #define __JUCE_MAC_CARBONVIEWWRAPPERCOMPONENT_JUCEHEADER__ /** Creates a floating carbon window that can be used to hold a carbon UI. This is a handy class that's designed to be inlined where needed, e.g. in the audio plugin hosting code. */ class CarbonViewWrapperComponent : public Component, public ComponentMovementWatcher, public Timer { public: CarbonViewWrapperComponent() : ComponentMovementWatcher (this), wrapperWindow (0), embeddedView (0), recursiveResize (false) { } virtual ~CarbonViewWrapperComponent() { jassert (embeddedView == 0); // must call deleteWindow() in the subclass's destructor! } virtual HIViewRef attachView (WindowRef windowRef, HIViewRef rootView) = 0; virtual void removeView (HIViewRef embeddedView) = 0; virtual void mouseDown (int, int) {} virtual void paint() {} virtual bool getEmbeddedViewSize (int& w, int& h) { if (embeddedView == 0) return false; HIRect bounds; HIViewGetBounds (embeddedView, &bounds); w = jmax (1, roundToInt (bounds.size.width)); h = jmax (1, roundToInt (bounds.size.height)); return true; } void createWindow() { if (wrapperWindow == 0) { Rect r; r.left = getScreenX(); r.top = getScreenY(); r.right = r.left + getWidth(); r.bottom = r.top + getHeight(); CreateNewWindow (kDocumentWindowClass, (WindowAttributes) (kWindowStandardHandlerAttribute | kWindowCompositingAttribute | kWindowNoShadowAttribute | kWindowNoTitleBarAttribute), &r, &wrapperWindow); jassert (wrapperWindow != 0); if (wrapperWindow == 0) return; NSWindow* carbonWindow = [[NSWindow alloc] initWithWindowRef: wrapperWindow]; NSWindow* ownerWindow = [((NSView*) getWindowHandle()) window]; [ownerWindow addChildWindow: carbonWindow ordered: NSWindowAbove]; embeddedView = attachView (wrapperWindow, HIViewGetRoot (wrapperWindow)); EventTypeSpec windowEventTypes[] = { { kEventClassWindow, kEventWindowGetClickActivation }, { kEventClassWindow, kEventWindowHandleDeactivate }, { kEventClassWindow, kEventWindowBoundsChanging }, { kEventClassMouse, kEventMouseDown }, { kEventClassMouse, kEventMouseMoved }, { kEventClassMouse, kEventMouseDragged }, { kEventClassMouse, kEventMouseUp}, { kEventClassWindow, kEventWindowDrawContent }, { kEventClassWindow, kEventWindowShown }, { kEventClassWindow, kEventWindowHidden } }; EventHandlerUPP upp = NewEventHandlerUPP (carbonEventCallback); InstallWindowEventHandler (wrapperWindow, upp, sizeof (windowEventTypes) / sizeof (EventTypeSpec), windowEventTypes, this, &eventHandlerRef); setOurSizeToEmbeddedViewSize(); setEmbeddedWindowToOurSize(); creationTime = Time::getCurrentTime(); } } void deleteWindow() { removeView (embeddedView); embeddedView = 0; if (wrapperWindow != 0) { RemoveEventHandler (eventHandlerRef); DisposeWindow (wrapperWindow); wrapperWindow = 0; } } void setOurSizeToEmbeddedViewSize() { int w, h; if (getEmbeddedViewSize (w, h)) { if (w != getWidth() || h != getHeight()) { startTimer (50); setSize (w, h); if (getParentComponent() != 0) getParentComponent()->setSize (w, h); } else { startTimer (jlimit (50, 500, getTimerInterval() + 20)); } } else { stopTimer(); } } void setEmbeddedWindowToOurSize() { if (! recursiveResize) { recursiveResize = true; if (embeddedView != 0) { HIRect r; r.origin.x = 0; r.origin.y = 0; r.size.width = (float) getWidth(); r.size.height = (float) getHeight(); HIViewSetFrame (embeddedView, &r); } if (wrapperWindow != 0) { Rect wr; wr.left = getScreenX(); wr.top = getScreenY(); wr.right = wr.left + getWidth(); wr.bottom = wr.top + getHeight(); SetWindowBounds (wrapperWindow, kWindowContentRgn, &wr); ShowWindow (wrapperWindow); } recursiveResize = false; } } void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) { setEmbeddedWindowToOurSize(); } void componentPeerChanged() { deleteWindow(); createWindow(); } void componentVisibilityChanged (Component&) { if (isShowing()) createWindow(); else deleteWindow(); setEmbeddedWindowToOurSize(); } static void recursiveHIViewRepaint (HIViewRef view) { HIViewSetNeedsDisplay (view, true); HIViewRef child = HIViewGetFirstSubview (view); while (child != 0) { recursiveHIViewRepaint (child); child = HIViewGetNextView (child); } } void timerCallback() { setOurSizeToEmbeddedViewSize(); // To avoid strange overpainting problems when the UI is first opened, we'll // repaint it a few times during the first second that it's on-screen.. if ((Time::getCurrentTime() - creationTime).inMilliseconds() < 1000) recursiveHIViewRepaint (HIViewGetRoot (wrapperWindow)); } OSStatus carbonEventHandler (EventHandlerCallRef /*nextHandlerRef*/, EventRef event) { switch (GetEventKind (event)) { case kEventWindowHandleDeactivate: ActivateWindow (wrapperWindow, TRUE); return noErr; case kEventWindowGetClickActivation: { getTopLevelComponent()->toFront (false); ClickActivationResult howToHandleClick = kActivateAndHandleClick; SetEventParameter (event, kEventParamClickActivation, typeClickActivationResult, sizeof (ClickActivationResult), &howToHandleClick); HIViewSetNeedsDisplay (embeddedView, true); return noErr; } } return eventNotHandledErr; } static pascal OSStatus carbonEventCallback (EventHandlerCallRef nextHandlerRef, EventRef event, void* userData) { return ((CarbonViewWrapperComponent*) userData)->carbonEventHandler (nextHandlerRef, event); } protected: WindowRef wrapperWindow; HIViewRef embeddedView; bool recursiveResize; Time creationTime; EventHandlerRef eventHandlerRef; }; #endif // __JUCE_MAC_CARBONVIEWWRAPPERCOMPONENT_JUCEHEADER__ /*** End of inlined file: juce_mac_CarbonViewWrapperComponent.h ***/ END_JUCE_NAMESPACE #endif #define JUCE_AMALGAMATED_TEMPLATE 1 //============================================================================== #if JUCE_BUILD_CORE /*** Start of inlined file: juce_FileLogger.cpp ***/ BEGIN_JUCE_NAMESPACE FileLogger::FileLogger (const File& logFile_, const String& welcomeMessage, const int maxInitialFileSizeBytes) : logFile (logFile_) { if (maxInitialFileSizeBytes >= 0) trimFileSize (maxInitialFileSizeBytes); if (! logFile_.exists()) { // do this so that the parent directories get created.. logFile_.create(); } String welcome; welcome << "\r\n**********************************************************\r\n" << welcomeMessage << "\r\nLog started: " << Time::getCurrentTime().toString (true, true) << "\r\n"; logMessage (welcome); } FileLogger::~FileLogger() { } void FileLogger::logMessage (const String& message) { DBG (message); const ScopedLock sl (logLock); FileOutputStream out (logFile, 256); out << message << "\r\n"; } void FileLogger::trimFileSize (int maxFileSizeBytes) const { if (maxFileSizeBytes <= 0) { logFile.deleteFile(); } else { const int64 fileSize = logFile.getSize(); if (fileSize > maxFileSizeBytes) { ScopedPointer in (logFile.createInputStream()); jassert (in != 0); if (in != 0) { in->setPosition (fileSize - maxFileSizeBytes); String content; { MemoryBlock contentToSave; contentToSave.setSize (maxFileSizeBytes + 4); contentToSave.fillWith (0); in->read (contentToSave.getData(), maxFileSizeBytes); in = 0; content = contentToSave.toString(); } int newStart = 0; while (newStart < fileSize && content[newStart] != '\n' && content[newStart] != '\r') ++newStart; logFile.deleteFile(); logFile.appendText (content.substring (newStart), false, false); } } } } FileLogger* FileLogger::createDefaultAppLogger (const String& logFileSubDirectoryName, const String& logFileName, const String& welcomeMessage, const int maxInitialFileSizeBytes) { #if JUCE_MAC File logFile ("~/Library/Logs"); logFile = logFile.getChildFile (logFileSubDirectoryName) .getChildFile (logFileName); #else File logFile (File::getSpecialLocation (File::userApplicationDataDirectory)); if (logFile.isDirectory()) { logFile = logFile.getChildFile (logFileSubDirectoryName) .getChildFile (logFileName); } #endif return new FileLogger (logFile, welcomeMessage, maxInitialFileSizeBytes); } END_JUCE_NAMESPACE /*** End of inlined file: juce_FileLogger.cpp ***/ /*** Start of inlined file: juce_Logger.cpp ***/ BEGIN_JUCE_NAMESPACE Logger::Logger() { } Logger::~Logger() { } Logger* Logger::currentLogger = 0; void Logger::setCurrentLogger (Logger* const newLogger, const bool deleteOldLogger) { Logger* const oldLogger = currentLogger; currentLogger = newLogger; if (deleteOldLogger) delete oldLogger; } void Logger::writeToLog (const String& message) { if (currentLogger != 0) currentLogger->logMessage (message); else outputDebugString (message); } #if JUCE_LOG_ASSERTIONS void JUCE_API juce_LogAssertion (const char* filename, const int lineNum) throw() { String m ("JUCE Assertion failure in "); m << filename << ", line " << lineNum; Logger::writeToLog (m); } #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_Logger.cpp ***/ /*** Start of inlined file: juce_Random.cpp ***/ BEGIN_JUCE_NAMESPACE Random::Random (const int64 seedValue) throw() : seed (seedValue) { } Random::~Random() throw() { } void Random::setSeed (const int64 newSeed) throw() { seed = newSeed; } void Random::combineSeed (const int64 seedValue) throw() { seed ^= nextInt64() ^ seedValue; } void Random::setSeedRandomly() { combineSeed ((int64) (pointer_sized_int) this); combineSeed (Time::getMillisecondCounter()); combineSeed (Time::getHighResolutionTicks()); combineSeed (Time::getHighResolutionTicksPerSecond()); combineSeed (Time::currentTimeMillis()); } int Random::nextInt() throw() { seed = (seed * literal64bit (0x5deece66d) + 11) & literal64bit (0xffffffffffff); return (int) (seed >> 16); } int Random::nextInt (const int maxValue) throw() { jassert (maxValue > 0); return (nextInt() & 0x7fffffff) % maxValue; } int64 Random::nextInt64() throw() { return (((int64) nextInt()) << 32) | (int64) (uint64) (uint32) nextInt(); } bool Random::nextBool() throw() { return (nextInt() & 0x80000000) != 0; } float Random::nextFloat() throw() { return static_cast (nextInt()) / (float) 0xffffffff; } double Random::nextDouble() throw() { return static_cast (nextInt()) / (double) 0xffffffff; } const BigInteger Random::nextLargeNumber (const BigInteger& maximumValue) { BigInteger n; do { fillBitsRandomly (n, 0, maximumValue.getHighestBit() + 1); } while (n >= maximumValue); return n; } void Random::fillBitsRandomly (BigInteger& arrayToChange, int startBit, int numBits) { arrayToChange.setBit (startBit + numBits - 1, true); // to force the array to pre-allocate space while ((startBit & 31) != 0 && numBits > 0) { arrayToChange.setBit (startBit++, nextBool()); --numBits; } while (numBits >= 32) { arrayToChange.setBitRangeAsInt (startBit, 32, (unsigned int) nextInt()); startBit += 32; numBits -= 32; } while (--numBits >= 0) arrayToChange.setBit (startBit + numBits, nextBool()); } Random& Random::getSystemRandom() throw() { static Random sysRand (1); return sysRand; } END_JUCE_NAMESPACE /*** End of inlined file: juce_Random.cpp ***/ /*** Start of inlined file: juce_RelativeTime.cpp ***/ BEGIN_JUCE_NAMESPACE RelativeTime::RelativeTime (const double seconds_) throw() : seconds (seconds_) { } RelativeTime::RelativeTime (const RelativeTime& other) throw() : seconds (other.seconds) { } RelativeTime::~RelativeTime() throw() { } const RelativeTime RelativeTime::milliseconds (const int milliseconds) throw() { return RelativeTime (milliseconds * 0.001); } const RelativeTime RelativeTime::milliseconds (const int64 milliseconds) throw() { return RelativeTime (milliseconds * 0.001); } const RelativeTime RelativeTime::minutes (const double numberOfMinutes) throw() { return RelativeTime (numberOfMinutes * 60.0); } const RelativeTime RelativeTime::hours (const double numberOfHours) throw() { return RelativeTime (numberOfHours * (60.0 * 60.0)); } const RelativeTime RelativeTime::days (const double numberOfDays) throw() { return RelativeTime (numberOfDays * (60.0 * 60.0 * 24.0)); } const RelativeTime RelativeTime::weeks (const double numberOfWeeks) throw() { return RelativeTime (numberOfWeeks * (60.0 * 60.0 * 24.0 * 7.0)); } int64 RelativeTime::inMilliseconds() const throw() { return (int64) (seconds * 1000.0); } double RelativeTime::inMinutes() const throw() { return seconds / 60.0; } double RelativeTime::inHours() const throw() { return seconds / (60.0 * 60.0); } double RelativeTime::inDays() const throw() { return seconds / (60.0 * 60.0 * 24.0); } double RelativeTime::inWeeks() const throw() { return seconds / (60.0 * 60.0 * 24.0 * 7.0); } const String RelativeTime::getDescription (const String& returnValueForZeroTime) const { if (seconds < 0.001 && seconds > -0.001) return returnValueForZeroTime; String result; if (seconds < 0) result = "-"; int fieldsShown = 0; int n = abs ((int) inWeeks()); if (n > 0) { result << n << ((n == 1) ? TRANS(" week ") : TRANS(" weeks ")); ++fieldsShown; } n = abs ((int) inDays()) % 7; if (n > 0) { result << n << ((n == 1) ? TRANS(" day ") : TRANS(" days ")); ++fieldsShown; } if (fieldsShown < 2) { n = abs ((int) inHours()) % 24; if (n > 0) { result << n << ((n == 1) ? TRANS(" hr ") : TRANS(" hrs ")); ++fieldsShown; } if (fieldsShown < 2) { n = abs ((int) inMinutes()) % 60; if (n > 0) { result << n << ((n == 1) ? TRANS(" min ") : TRANS(" mins ")); ++fieldsShown; } if (fieldsShown < 2) { n = abs ((int) inSeconds()) % 60; if (n > 0) { result << n << ((n == 1) ? TRANS(" sec ") : TRANS(" secs ")); ++fieldsShown; } if (fieldsShown < 1) { n = abs ((int) inMilliseconds()) % 1000; if (n > 0) { result << n << TRANS(" ms"); ++fieldsShown; } } } } } return result.trimEnd(); } RelativeTime& RelativeTime::operator= (const RelativeTime& other) throw() { seconds = other.seconds; return *this; } bool RelativeTime::operator== (const RelativeTime& other) const throw() { return seconds == other.seconds; } bool RelativeTime::operator!= (const RelativeTime& other) const throw() { return seconds != other.seconds; } bool RelativeTime::operator> (const RelativeTime& other) const throw() { return seconds > other.seconds; } bool RelativeTime::operator< (const RelativeTime& other) const throw() { return seconds < other.seconds; } bool RelativeTime::operator>= (const RelativeTime& other) const throw() { return seconds >= other.seconds; } bool RelativeTime::operator<= (const RelativeTime& other) const throw() { return seconds <= other.seconds; } const RelativeTime RelativeTime::operator+ (const RelativeTime& timeToAdd) const throw() { return RelativeTime (seconds + timeToAdd.seconds); } const RelativeTime RelativeTime::operator- (const RelativeTime& timeToSubtract) const throw() { return RelativeTime (seconds - timeToSubtract.seconds); } const RelativeTime RelativeTime::operator+ (const double secondsToAdd) const throw() { return RelativeTime (seconds + secondsToAdd); } const RelativeTime RelativeTime::operator- (const double secondsToSubtract) const throw() { return RelativeTime (seconds - secondsToSubtract); } const RelativeTime& RelativeTime::operator+= (const RelativeTime& timeToAdd) throw() { seconds += timeToAdd.seconds; return *this; } const RelativeTime& RelativeTime::operator-= (const RelativeTime& timeToSubtract) throw() { seconds -= timeToSubtract.seconds; return *this; } const RelativeTime& RelativeTime::operator+= (const double secondsToAdd) throw() { seconds += secondsToAdd; return *this; } const RelativeTime& RelativeTime::operator-= (const double secondsToSubtract) throw() { seconds -= secondsToSubtract; return *this; } END_JUCE_NAMESPACE /*** End of inlined file: juce_RelativeTime.cpp ***/ /*** Start of inlined file: juce_SystemStats.cpp ***/ BEGIN_JUCE_NAMESPACE SystemStats::CPUFlags SystemStats::cpuFlags; const String SystemStats::getJUCEVersion() { return "JUCE v" + String (JUCE_MAJOR_VERSION) + "." + String (JUCE_MINOR_VERSION) + "." + String (JUCE_BUILDNUMBER); } const StringArray SystemStats::getMACAddressStrings() { int64 macAddresses [16]; const int numAddresses = getMACAddresses (macAddresses, numElementsInArray (macAddresses), false); StringArray s; for (int i = 0; i < numAddresses; ++i) { s.add (String::toHexString (0xff & (int) (macAddresses [i] >> 40)).paddedLeft ('0', 2) + "-" + String::toHexString (0xff & (int) (macAddresses [i] >> 32)).paddedLeft ('0', 2) + "-" + String::toHexString (0xff & (int) (macAddresses [i] >> 24)).paddedLeft ('0', 2) + "-" + String::toHexString (0xff & (int) (macAddresses [i] >> 16)).paddedLeft ('0', 2) + "-" + String::toHexString (0xff & (int) (macAddresses [i] >> 8)).paddedLeft ('0', 2) + "-" + String::toHexString (0xff & (int) (macAddresses [i] >> 0)).paddedLeft ('0', 2)); } return s; } #ifdef JUCE_DLL void* juce_Malloc (const int size) { return malloc (size); } void* juce_Calloc (const int size) { return calloc (1, size); } void* juce_Realloc (void* const block, const int size) { return realloc (block, size); } void juce_Free (void* const block) { free (block); } #if JUCE_DEBUG && JUCE_MSVC && JUCE_CHECK_MEMORY_LEAKS void* juce_DebugMalloc (const int size, const char* file, const int line) { return _malloc_dbg (size, _NORMAL_BLOCK, file, line); } void* juce_DebugCalloc (const int size, const char* file, const int line) { return _calloc_dbg (1, size, _NORMAL_BLOCK, file, line); } void* juce_DebugRealloc (void* const block, const int size, const char* file, const int line) { return _realloc_dbg (block, size, _NORMAL_BLOCK, file, line); } void juce_DebugFree (void* const block) { _free_dbg (block, _NORMAL_BLOCK); } #endif #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_SystemStats.cpp ***/ /*** Start of inlined file: juce_Time.cpp ***/ #if JUCE_MSVC #pragma warning (push) #pragma warning (disable: 4514) #endif #ifndef JUCE_WINDOWS #include #else #include #endif #include #if JUCE_MSVC #pragma warning (pop) #ifdef _INC_TIME_INL #define USE_NEW_SECURE_TIME_FNS #endif #endif BEGIN_JUCE_NAMESPACE namespace TimeHelpers { static struct tm millisToLocal (const int64 millis) throw() { struct tm result; const int64 seconds = millis / 1000; if (seconds < literal64bit (86400) || seconds >= literal64bit (2145916800)) { // use extended maths for dates beyond 1970 to 2037.. const int timeZoneAdjustment = 31536000 - (int) (Time (1971, 0, 1, 0, 0).toMilliseconds() / 1000); const int64 jdm = seconds + timeZoneAdjustment + literal64bit (210866803200); const int days = (int) (jdm / literal64bit (86400)); const int a = 32044 + days; const int b = (4 * a + 3) / 146097; const int c = a - (b * 146097) / 4; const int d = (4 * c + 3) / 1461; const int e = c - (d * 1461) / 4; const int m = (5 * e + 2) / 153; result.tm_mday = e - (153 * m + 2) / 5 + 1; result.tm_mon = m + 2 - 12 * (m / 10); result.tm_year = b * 100 + d - 6700 + (m / 10); result.tm_wday = (days + 1) % 7; result.tm_yday = -1; int t = (int) (jdm % literal64bit (86400)); result.tm_hour = t / 3600; t %= 3600; result.tm_min = t / 60; result.tm_sec = t % 60; result.tm_isdst = -1; } else { time_t now = static_cast (seconds); #if JUCE_WINDOWS #ifdef USE_NEW_SECURE_TIME_FNS if (now >= 0 && now <= 0x793406fff) localtime_s (&result, &now); else zeromem (&result, sizeof (result)); #else result = *localtime (&now); #endif #else // more thread-safe localtime_r (&now, &result); #endif } return result; } static int extendedModulo (const int64 value, const int modulo) throw() { return (int) (value >= 0 ? (value % modulo) : (value - ((value / modulo) + 1) * modulo)); } static uint32 lastMSCounterValue = 0; } Time::Time() throw() : millisSinceEpoch (0) { } Time::Time (const Time& other) throw() : millisSinceEpoch (other.millisSinceEpoch) { } Time::Time (const int64 ms) throw() : millisSinceEpoch (ms) { } Time::Time (const int year, const int month, const int day, const int hours, const int minutes, const int seconds, const int milliseconds, const bool useLocalTime) throw() { jassert (year > 100); // year must be a 4-digit version if (year < 1971 || year >= 2038 || ! useLocalTime) { // use extended maths for dates beyond 1970 to 2037.. const int timeZoneAdjustment = useLocalTime ? (31536000 - (int) (Time (1971, 0, 1, 0, 0).toMilliseconds() / 1000)) : 0; const int a = (13 - month) / 12; const int y = year + 4800 - a; const int jd = day + (153 * (month + 12 * a - 2) + 2) / 5 + (y * 365) + (y / 4) - (y / 100) + (y / 400) - 32045; const int64 s = ((int64) jd) * literal64bit (86400) - literal64bit (210866803200); millisSinceEpoch = 1000 * (s + (hours * 3600 + minutes * 60 + seconds - timeZoneAdjustment)) + milliseconds; } else { struct tm t; t.tm_year = year - 1900; t.tm_mon = month; t.tm_mday = day; t.tm_hour = hours; t.tm_min = minutes; t.tm_sec = seconds; t.tm_isdst = -1; millisSinceEpoch = 1000 * (int64) mktime (&t); if (millisSinceEpoch < 0) millisSinceEpoch = 0; else millisSinceEpoch += milliseconds; } } Time::~Time() throw() { } Time& Time::operator= (const Time& other) throw() { millisSinceEpoch = other.millisSinceEpoch; return *this; } int64 Time::currentTimeMillis() throw() { static uint32 lastCounterResult = 0xffffffff; static int64 correction = 0; const uint32 now = getMillisecondCounter(); // check the counter hasn't wrapped (also triggered the first time this function is called) if (now < lastCounterResult) { // double-check it's actually wrapped, in case multi-cpu machines have timers that drift a bit. if (lastCounterResult == 0xffffffff || now < lastCounterResult - 10) { // get the time once using normal library calls, and store the difference needed to // turn the millisecond counter into a real time. #if JUCE_WINDOWS struct _timeb t; #ifdef USE_NEW_SECURE_TIME_FNS _ftime_s (&t); #else _ftime (&t); #endif correction = (((int64) t.time) * 1000 + t.millitm) - now; #else struct timeval tv; struct timezone tz; gettimeofday (&tv, &tz); correction = (((int64) tv.tv_sec) * 1000 + tv.tv_usec / 1000) - now; #endif } } lastCounterResult = now; return correction + now; } uint32 juce_millisecondsSinceStartup() throw(); uint32 Time::getMillisecondCounter() throw() { const uint32 now = juce_millisecondsSinceStartup(); if (now < TimeHelpers::lastMSCounterValue) { // in multi-threaded apps this might be called concurrently, so // make sure that our last counter value only increases and doesn't // go backwards.. if (now < TimeHelpers::lastMSCounterValue - 1000) TimeHelpers::lastMSCounterValue = now; } else { TimeHelpers::lastMSCounterValue = now; } return now; } uint32 Time::getApproximateMillisecondCounter() throw() { jassert (TimeHelpers::lastMSCounterValue != 0); return TimeHelpers::lastMSCounterValue; } void Time::waitForMillisecondCounter (const uint32 targetTime) throw() { for (;;) { const uint32 now = getMillisecondCounter(); if (now >= targetTime) break; const int toWait = targetTime - now; if (toWait > 2) { Thread::sleep (jmin (20, toWait >> 1)); } else { // xxx should consider using mutex_pause on the mac as it apparently // makes it seem less like a spinlock and avoids lowering the thread pri. for (int i = 10; --i >= 0;) Thread::yield(); } } } double Time::highResolutionTicksToSeconds (const int64 ticks) throw() { return ticks / (double) getHighResolutionTicksPerSecond(); } int64 Time::secondsToHighResolutionTicks (const double seconds) throw() { return (int64) (seconds * (double) getHighResolutionTicksPerSecond()); } const Time JUCE_CALLTYPE Time::getCurrentTime() throw() { return Time (currentTimeMillis()); } const String Time::toString (const bool includeDate, const bool includeTime, const bool includeSeconds, const bool use24HourClock) const throw() { String result; if (includeDate) { result << getDayOfMonth() << ' ' << getMonthName (true) << ' ' << getYear(); if (includeTime) result << ' '; } if (includeTime) { const int mins = getMinutes(); result << (use24HourClock ? getHours() : getHoursInAmPmFormat()) << (mins < 10 ? ":0" : ":") << mins; if (includeSeconds) { const int secs = getSeconds(); result << (secs < 10 ? ":0" : ":") << secs; } if (! use24HourClock) result << (isAfternoon() ? "pm" : "am"); } return result.trimEnd(); } const String Time::formatted (const String& format) const { String buffer; int bufferSize = 128; buffer.preallocateStorage (bufferSize); struct tm t (TimeHelpers::millisToLocal (millisSinceEpoch)); while (CharacterFunctions::ftime (static_cast (buffer), bufferSize, format, &t) <= 0) { bufferSize += 128; buffer.preallocateStorage (bufferSize); } return buffer; } int Time::getYear() const throw() { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_year + 1900; } int Time::getMonth() const throw() { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_mon; } int Time::getDayOfMonth() const throw() { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_mday; } int Time::getDayOfWeek() const throw() { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_wday; } int Time::getHours() const throw() { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_hour; } int Time::getHoursInAmPmFormat() const throw() { const int hours = getHours(); if (hours == 0) return 12; else if (hours <= 12) return hours; else return hours - 12; } bool Time::isAfternoon() const throw() { return getHours() >= 12; } int Time::getMinutes() const throw() { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_min; } int Time::getSeconds() const throw() { return TimeHelpers::extendedModulo (millisSinceEpoch / 1000, 60); } int Time::getMilliseconds() const throw() { return TimeHelpers::extendedModulo (millisSinceEpoch, 1000); } bool Time::isDaylightSavingTime() const throw() { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_isdst != 0; } const String Time::getTimeZone() const throw() { String zone[2]; #if JUCE_WINDOWS _tzset(); #ifdef USE_NEW_SECURE_TIME_FNS { char name [128]; size_t length; for (int i = 0; i < 2; ++i) { zeromem (name, sizeof (name)); _get_tzname (&length, name, 127, i); zone[i] = name; } } #else const char** const zonePtr = (const char**) _tzname; zone[0] = zonePtr[0]; zone[1] = zonePtr[1]; #endif #else tzset(); const char** const zonePtr = (const char**) tzname; zone[0] = zonePtr[0]; zone[1] = zonePtr[1]; #endif if (isDaylightSavingTime()) { zone[0] = zone[1]; if (zone[0].length() > 3 && zone[0].containsIgnoreCase ("daylight") && zone[0].contains ("GMT")) zone[0] = "BST"; } return zone[0].substring (0, 3); } const String Time::getMonthName (const bool threeLetterVersion) const { return getMonthName (getMonth(), threeLetterVersion); } const String Time::getWeekdayName (const bool threeLetterVersion) const { return getWeekdayName (getDayOfWeek(), threeLetterVersion); } const String Time::getMonthName (int monthNumber, const bool threeLetterVersion) { const char* const shortMonthNames[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; const char* const longMonthNames[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; monthNumber %= 12; return TRANS (threeLetterVersion ? shortMonthNames [monthNumber] : longMonthNames [monthNumber]); } const String Time::getWeekdayName (int day, const bool threeLetterVersion) { const char* const shortDayNames[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; const char* const longDayNames[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; day %= 7; return TRANS (threeLetterVersion ? shortDayNames [day] : longDayNames [day]); } END_JUCE_NAMESPACE /*** End of inlined file: juce_Time.cpp ***/ /*** Start of inlined file: juce_Initialisation.cpp ***/ BEGIN_JUCE_NAMESPACE #if ! JUCE_ONLY_BUILD_CORE_LIBRARY #endif #if JUCE_WINDOWS extern void juce_shutdownWin32Sockets(); // (defined in the sockets code) #endif #if JUCE_DEBUG extern void juce_CheckForDanglingStreams(); // (in juce_OutputStream.cpp) #endif static bool juceInitialisedNonGUI = false; void JUCE_PUBLIC_FUNCTION initialiseJuce_NonGUI() { if (! juceInitialisedNonGUI) { juceInitialisedNonGUI = true; JUCE_AUTORELEASEPOOL DBG (SystemStats::getJUCEVersion()); SystemStats::initialiseStats(); Random::getSystemRandom().setSeedRandomly(); // (mustn't call this before initialiseStats() because it relies on the time being set up) } // Some basic tests, to keep an eye on things and make sure these types work ok // on all platforms. Let me know if any of these assertions fail on your system! static_jassert (sizeof (pointer_sized_int) == sizeof (void*)); static_jassert (sizeof (int8) == 1); static_jassert (sizeof (uint8) == 1); static_jassert (sizeof (int16) == 2); static_jassert (sizeof (uint16) == 2); static_jassert (sizeof (int32) == 4); static_jassert (sizeof (uint32) == 4); static_jassert (sizeof (int64) == 8); static_jassert (sizeof (uint64) == 8); } void JUCE_PUBLIC_FUNCTION shutdownJuce_NonGUI() { if (juceInitialisedNonGUI) { juceInitialisedNonGUI = false; JUCE_AUTORELEASEPOOL LocalisedStrings::setCurrentMappings (0); Thread::stopAllThreads (3000); #if JUCE_WINDOWS juce_shutdownWin32Sockets(); #endif #if JUCE_DEBUG juce_CheckForDanglingStreams(); #endif } } #if ! JUCE_ONLY_BUILD_CORE_LIBRARY void juce_setCurrentThreadName (const String& name); static bool juceInitialisedGUI = false; void JUCE_PUBLIC_FUNCTION initialiseJuce_GUI() { if (! juceInitialisedGUI) { juceInitialisedGUI = true; JUCE_AUTORELEASEPOOL initialiseJuce_NonGUI(); MessageManager::getInstance(); LookAndFeel::setDefaultLookAndFeel (0); juce_setCurrentThreadName ("Juce Message Thread"); #if JUCE_DEBUG // This section is just for catching people who mess up their project settings and // turn RTTI off.. try { TextButton tb (String::empty); Component* c = &tb; // Got an exception here? Then TURN ON RTTI in your compiler settings!! c = dynamic_cast (c); } catch (...) { // Ended up here? If so, TURN ON RTTI in your compiler settings!! jassertfalse; } #endif } } void JUCE_PUBLIC_FUNCTION shutdownJuce_GUI() { if (juceInitialisedGUI) { juceInitialisedGUI = false; JUCE_AUTORELEASEPOOL DeletedAtShutdown::deleteAll(); LookAndFeel::clearDefaultLookAndFeel(); delete MessageManager::getInstance(); shutdownJuce_NonGUI(); } } #endif #if JUCE_UNIT_TESTS class AtomicTests : public UnitTest { public: AtomicTests() : UnitTest ("Atomics") {} void runTest() { beginTest ("Misc"); char a1[7]; expect (numElementsInArray(a1) == 7); int a2[3]; expect (numElementsInArray(a2) == 3); expect (ByteOrder::swap ((uint16) 0x1122) == 0x2211); expect (ByteOrder::swap ((uint32) 0x11223344) == 0x44332211); expect (ByteOrder::swap ((uint64) literal64bit (0x1122334455667788)) == literal64bit (0x8877665544332211)); beginTest ("Atomic types"); AtomicTester ::testInteger (*this); AtomicTester ::testInteger (*this); AtomicTester ::testInteger (*this); AtomicTester ::testInteger (*this); AtomicTester ::testInteger (*this); AtomicTester ::testInteger (*this); AtomicTester ::testInteger (*this); AtomicTester ::testFloat (*this); #if ! JUCE_64BIT_ATOMICS_UNAVAILABLE // 64-bit intrinsics aren't available on some old platforms AtomicTester ::testInteger (*this); AtomicTester ::testInteger (*this); AtomicTester ::testFloat (*this); #endif } template class AtomicTester { public: AtomicTester() {} static void testInteger (UnitTest& test) { Atomic a, b; a.set ((Type) 10); a += (Type) 15; a.memoryBarrier(); a -= (Type) 5; ++a; ++a; --a; a.memoryBarrier(); testFloat (test); } static void testFloat (UnitTest& test) { Atomic a, b; a = (Type) 21; a.memoryBarrier(); /* These are some simple test cases to check the atomics - let me know if any of these assertions fail on your system! */ test.expect (a.get() == (Type) 21); test.expect (a.compareAndSetValue ((Type) 100, (Type) 50) == (Type) 21); test.expect (a.get() == (Type) 21); test.expect (a.compareAndSetValue ((Type) 101, a.get()) == (Type) 21); test.expect (a.get() == (Type) 101); test.expect (! a.compareAndSetBool ((Type) 300, (Type) 200)); test.expect (a.get() == (Type) 101); test.expect (a.compareAndSetBool ((Type) 200, a.get())); test.expect (a.get() == (Type) 200); test.expect (a.exchange ((Type) 300) == (Type) 200); test.expect (a.get() == (Type) 300); b = a; test.expect (b.get() == a.get()); } }; }; static AtomicTests atomicUnitTests; #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_Initialisation.cpp ***/ /*** Start of inlined file: juce_BigInteger.cpp ***/ BEGIN_JUCE_NAMESPACE BigInteger::BigInteger() : numValues (4), highestBit (-1), negative (false) { values.calloc (numValues + 1); } BigInteger::BigInteger (const int value) : numValues (4), highestBit (31), negative (value < 0) { values.calloc (numValues + 1); values[0] = abs (value); highestBit = getHighestBit(); } BigInteger::BigInteger (int64 value) : numValues (4), highestBit (63), negative (value < 0) { values.calloc (numValues + 1); if (value < 0) value = -value; values[0] = (unsigned int) value; values[1] = (unsigned int) (value >> 32); highestBit = getHighestBit(); } BigInteger::BigInteger (const unsigned int value) : numValues (4), highestBit (31), negative (false) { values.calloc (numValues + 1); values[0] = value; highestBit = getHighestBit(); } BigInteger::BigInteger (const BigInteger& other) : numValues (jmax (4, (other.highestBit >> 5) + 1)), highestBit (other.getHighestBit()), negative (other.negative) { values.malloc (numValues + 1); memcpy (values, other.values, sizeof (unsigned int) * (numValues + 1)); } BigInteger::~BigInteger() { } void BigInteger::swapWith (BigInteger& other) throw() { values.swapWith (other.values); swapVariables (numValues, other.numValues); swapVariables (highestBit, other.highestBit); swapVariables (negative, other.negative); } BigInteger& BigInteger::operator= (const BigInteger& other) { if (this != &other) { highestBit = other.getHighestBit(); numValues = jmax (4, (highestBit >> 5) + 1); negative = other.negative; values.malloc (numValues + 1); memcpy (values, other.values, sizeof (unsigned int) * (numValues + 1)); } return *this; } void BigInteger::ensureSize (const int numVals) { if (numVals + 2 >= numValues) { int oldSize = numValues; numValues = ((numVals + 2) * 3) / 2; values.realloc (numValues + 1); while (oldSize < numValues) values [oldSize++] = 0; } } bool BigInteger::operator[] (const int bit) const throw() { return bit <= highestBit && bit >= 0 && ((values [bit >> 5] & (1 << (bit & 31))) != 0); } int BigInteger::toInteger() const throw() { const int n = (int) (values[0] & 0x7fffffff); return negative ? -n : n; } const BigInteger BigInteger::getBitRange (int startBit, int numBits) const { BigInteger 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 BigInteger::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 BigInteger::setBitRangeAsInt (const int startBit, int numBits, unsigned int valueToSet) { if (numBits > 32) { jassertfalse; numBits = 32; } for (int i = 0; i < numBits; ++i) { setBit (startBit + i, (valueToSet & 1) != 0); valueToSet >>= 1; } } void BigInteger::clear() { if (numValues > 16) { numValues = 4; values.calloc (numValues + 1); } else { zeromem (values, sizeof (unsigned int) * (numValues + 1)); } highestBit = -1; negative = false; } void BigInteger::setBit (const int bit) { if (bit >= 0) { if (bit > highestBit) { ensureSize (bit >> 5); highestBit = bit; } values [bit >> 5] |= (1 << (bit & 31)); } } void BigInteger::setBit (const int bit, const bool shouldBeSet) { if (shouldBeSet) setBit (bit); else clearBit (bit); } void BigInteger::clearBit (const int bit) throw() { if (bit >= 0 && bit <= highestBit) values [bit >> 5] &= ~(1 << (bit & 31)); } void BigInteger::setRange (int startBit, int numBits, const bool shouldBeSet) { while (--numBits >= 0) setBit (startBit++, shouldBeSet); } void BigInteger::insertBit (const int bit, const bool shouldBeSet) { if (bit >= 0) shiftBits (1, bit); setBit (bit, shouldBeSet); } bool BigInteger::isZero() const throw() { return getHighestBit() < 0; } bool BigInteger::isOne() const throw() { return getHighestBit() == 0 && ! negative; } bool BigInteger::isNegative() const throw() { return negative && ! isZero(); } void BigInteger::setNegative (const bool neg) throw() { negative = neg; } void BigInteger::negate() throw() { negative = (! negative) && ! isZero(); } int BigInteger::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 BigInteger::getHighestBit() const throw() { for (int i = highestBit + 1; --i >= 0;) if ((values [i >> 5] & (1 << (i & 31))) != 0) return i; return -1; } int BigInteger::findNextSetBit (int i) const throw() { for (; i <= highestBit; ++i) if ((values [i >> 5] & (1 << (i & 31))) != 0) return i; return -1; } int BigInteger::findNextClearBit (int i) const throw() { for (; i <= highestBit; ++i) if ((values [i >> 5] & (1 << (i & 31))) == 0) break; return i; } BigInteger& BigInteger::operator+= (const BigInteger& other) { if (other.isNegative()) return operator-= (-other); if (isNegative()) { if (compareAbsolute (other) < 0) { BigInteger temp (*this); temp.negate(); *this = other; operator-= (temp); } else { negate(); operator-= (other); negate(); } } else { 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(); } return *this; } BigInteger& BigInteger::operator-= (const BigInteger& other) { if (other.isNegative()) return operator+= (-other); if (! isNegative()) { if (compareAbsolute (other) < 0) { BigInteger temp (other); swapWith (temp); operator-= (temp); negate(); return *this; } } else { negate(); operator+= (other); negate(); return *this; } 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; } } return *this; } BigInteger& BigInteger::operator*= (const BigInteger& other) { BigInteger total; highestBit = getHighestBit(); const bool wasNegative = isNegative(); setNegative (false); for (int i = 0; i <= highestBit; ++i) { if (operator[](i)) { BigInteger n (other); n.setNegative (false); n <<= i; total += n; } } total.setNegative (wasNegative ^ other.isNegative()); swapWith (total); return *this; } void BigInteger::divideBy (const BigInteger& divisor, BigInteger& remainder) { 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 { const bool wasNegative = isNegative(); swapWith (remainder); remainder.setNegative (false); clear(); BigInteger temp (divisor); temp.setNegative (false); int leftShift = ourHB - divHB; temp <<= leftShift; while (leftShift >= 0) { if (remainder.compareAbsolute (temp) >= 0) { remainder -= temp; setBit (leftShift); } if (--leftShift >= 0) temp >>= 1; } negative = wasNegative ^ divisor.isNegative(); remainder.setNegative (wasNegative); } } BigInteger& BigInteger::operator/= (const BigInteger& other) { BigInteger remainder; divideBy (other, remainder); return *this; } BigInteger& BigInteger::operator|= (const BigInteger& other) { // this operation doesn't take into account negative values.. jassert (isNegative() == other.isNegative()); if (other.highestBit >= 0) { 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(); } return *this; } BigInteger& BigInteger::operator&= (const BigInteger& other) { // this operation doesn't take into account negative 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(); return *this; } BigInteger& BigInteger::operator^= (const BigInteger& other) { // this operation will only work with the absolute values jassert (isNegative() == other.isNegative()); if (other.highestBit >= 0) { 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(); } return *this; } BigInteger& BigInteger::operator%= (const BigInteger& divisor) { BigInteger remainder; divideBy (divisor, remainder); swapWith (remainder); return *this; } BigInteger& BigInteger::operator<<= (int numBitsToShift) { shiftBits (numBitsToShift, 0); return *this; } BigInteger& BigInteger::operator>>= (int numBitsToShift) { return operator<<= (-numBitsToShift); } BigInteger& BigInteger::operator++() { return operator+= (1); } BigInteger& BigInteger::operator--() { return operator-= (1); } const BigInteger BigInteger::operator++ (int) { const BigInteger old (*this); operator+= (1); return old; } const BigInteger BigInteger::operator-- (int) { const BigInteger old (*this); operator-= (1); return old; } const BigInteger BigInteger::operator+ (const BigInteger& other) const { BigInteger b (*this); return b += other; } const BigInteger BigInteger::operator- (const BigInteger& other) const { BigInteger b (*this); return b -= other; } const BigInteger BigInteger::operator* (const BigInteger& other) const { BigInteger b (*this); return b *= other; } const BigInteger BigInteger::operator/ (const BigInteger& other) const { BigInteger b (*this); return b /= other; } const BigInteger BigInteger::operator| (const BigInteger& other) const { BigInteger b (*this); return b |= other; } const BigInteger BigInteger::operator& (const BigInteger& other) const { BigInteger b (*this); return b &= other; } const BigInteger BigInteger::operator^ (const BigInteger& other) const { BigInteger b (*this); return b ^= other; } const BigInteger BigInteger::operator% (const BigInteger& other) const { BigInteger b (*this); return b %= other; } const BigInteger BigInteger::operator<< (const int numBits) const { BigInteger b (*this); return b <<= numBits; } const BigInteger BigInteger::operator>> (const int numBits) const { BigInteger b (*this); return b >>= numBits; } const BigInteger BigInteger::operator-() const { BigInteger b (*this); b.negate(); return b; } int BigInteger::compare (const BigInteger& other) const throw() { if (isNegative() == other.isNegative()) { const int absComp = compareAbsolute (other); return isNegative() ? -absComp : absComp; } else { return isNegative() ? -1 : 1; } } int BigInteger::compareAbsolute (const BigInteger& 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 BigInteger::operator== (const BigInteger& other) const throw() { return compare (other) == 0; } bool BigInteger::operator!= (const BigInteger& other) const throw() { return compare (other) != 0; } bool BigInteger::operator< (const BigInteger& other) const throw() { return compare (other) < 0; } bool BigInteger::operator<= (const BigInteger& other) const throw() { return compare (other) <= 0; } bool BigInteger::operator> (const BigInteger& other) const throw() { return compare (other) > 0; } bool BigInteger::operator>= (const BigInteger& other) const throw() { return compare (other) >= 0; } void BigInteger::shiftBits (int bits, const int startBit) { 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 BigInteger BigInteger::simpleGCD (BigInteger* m, BigInteger* n) { while (! m->isZero()) { if (n->compareAbsolute (*m) > 0) swapVariables (m, n); *m -= *n; } return *n; } const BigInteger BigInteger::findGreatestCommonDivisor (BigInteger n) const { BigInteger m (*this); while (! n.isZero()) { if (abs (m.getHighestBit() - n.getHighestBit()) <= 16) return simpleGCD (&m, &n); BigInteger temp1 (m), temp2; temp1.divideBy (n, temp2); m = n; n = temp2; } return m; } void BigInteger::exponentModulo (const BigInteger& exponent, const BigInteger& modulus) { BigInteger exp (exponent); exp %= modulus; BigInteger value (1); swapWith (value); value %= modulus; while (! exp.isZero()) { if (exp [0]) { operator*= (value); operator%= (modulus); } value *= value; value %= modulus; exp >>= 1; } } void BigInteger::inverseModulo (const BigInteger& modulus) { if (modulus.isOne() || modulus.isNegative()) { clear(); return; } if (isNegative() || compareAbsolute (modulus) >= 0) operator%= (modulus); if (isOne()) return; if (! (*this)[0]) { // not invertible clear(); return; } BigInteger a1 (modulus); BigInteger a2 (*this); BigInteger b1 (modulus); BigInteger b2 (1); while (! a2.isOne()) { BigInteger temp1, temp2, multiplier (a1); multiplier.divideBy (a2, temp1); temp1 = a2; temp1 *= multiplier; temp2 = a1; temp2 -= temp1; a1 = a2; a2 = temp2; temp1 = b2; temp1 *= multiplier; temp2 = b1; temp2 -= temp1; b1 = b2; b2 = temp2; } while (b2.isNegative()) b2 += modulus; b2 %= modulus; swapWith (b2); } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const BigInteger& value) { return stream << value.toString (10); } const String BigInteger::toString (const int base, const int minimumNumCharacters) const { String s; BigInteger v (*this); if (base == 2 || base == 8 || base == 16) { const int bits = (base == 2) ? 1 : (base == 8 ? 3 : 4); static const char* const hexDigits = "0123456789abcdef"; for (;;) { const int remainder = v.getBitRangeAsInt (0, bits); v >>= bits; if (remainder == 0 && v.isZero()) break; s = String::charToString (hexDigits [remainder]) + s; } } else if (base == 10) { const BigInteger ten (10); BigInteger remainder; for (;;) { v.divideBy (ten, remainder); if (remainder.isZero() && v.isZero()) break; s = String (remainder.getBitRangeAsInt (0, 8)) + s; } } else { jassertfalse; // can't do the specified base! return String::empty; } s = s.paddedLeft ('0', minimumNumCharacters); return isNegative() ? "-" + s : s; } void BigInteger::parseString (const String& text, const int base) { clear(); const juce_wchar* t = text; if (base == 2 || base == 8 || base == 16) { const int bits = (base == 2) ? 1 : (base == 8 ? 3 : 4); for (;;) { const juce_wchar c = *t++; const int digit = CharacterFunctions::getHexDigitValue (c); if (((unsigned int) digit) < (unsigned int) base) { operator<<= (bits); operator+= (digit); } else if (c == 0) { break; } } } else if (base == 10) { const BigInteger ten ((unsigned int) 10); for (;;) { const juce_wchar c = *t++; if (c >= '0' && c <= '9') { operator*= (ten); operator+= ((int) (c - '0')); } else if (c == 0) { break; } } } setNegative (text.trimStart().startsWithChar ('-')); } const MemoryBlock BigInteger::toMemoryBlock() const { const int numBytes = (getHighestBit() + 8) >> 3; MemoryBlock mb ((size_t) numBytes); for (int i = 0; i < numBytes; ++i) mb[i] = (uint8) getBitRangeAsInt (i << 3, 8); return mb; } void BigInteger::loadFromMemoryBlock (const MemoryBlock& data) { clear(); for (int i = (int) data.getSize(); --i >= 0;) this->setBitRangeAsInt ((int) (i << 3), 8, data [i]); } END_JUCE_NAMESPACE /*** End of inlined file: juce_BigInteger.cpp ***/ /*** Start of inlined file: juce_MemoryBlock.cpp ***/ BEGIN_JUCE_NAMESPACE MemoryBlock::MemoryBlock() throw() : size (0) { } MemoryBlock::MemoryBlock (const size_t initialSize, const bool initialiseToZero) { if (initialSize > 0) { size = initialSize; data.allocate (initialSize, initialiseToZero); } else { size = 0; } } MemoryBlock::MemoryBlock (const MemoryBlock& other) : size (other.size) { if (size > 0) { jassert (other.data != 0); data.malloc (size); memcpy (data, other.data, size); } } MemoryBlock::MemoryBlock (const void* const dataToInitialiseFrom, const size_t sizeInBytes) : size (jmax ((size_t) 0, sizeInBytes)) { jassert (sizeInBytes >= 0); if (size > 0) { jassert (dataToInitialiseFrom != 0); // non-zero size, but a zero pointer passed-in? data.malloc (size); if (dataToInitialiseFrom != 0) memcpy (data, dataToInitialiseFrom, size); } } MemoryBlock::~MemoryBlock() throw() { jassert (size >= 0); // should never happen jassert (size == 0 || data != 0); // non-zero size but no data allocated? } MemoryBlock& MemoryBlock::operator= (const MemoryBlock& other) { if (this != &other) { setSize (other.size, false); memcpy (data, other.data, size); } return *this; } bool MemoryBlock::operator== (const MemoryBlock& other) const throw() { return matches (other.data, other.size); } bool MemoryBlock::operator!= (const MemoryBlock& other) const throw() { return ! operator== (other); } bool MemoryBlock::matches (const void* dataToCompare, size_t dataSize) const throw() { return size == dataSize && memcmp (data, dataToCompare, size) == 0; } // this will resize the block to this size void MemoryBlock::setSize (const size_t newSize, const bool initialiseToZero) { if (size != newSize) { if (newSize <= 0) { data.free(); size = 0; } else { if (data != 0) { data.realloc (newSize); if (initialiseToZero && (newSize > size)) zeromem (data + size, newSize - size); } else { data.allocate (newSize, initialiseToZero); } size = newSize; } } } void MemoryBlock::ensureSize (const size_t minimumSize, const bool initialiseToZero) { if (size < minimumSize) setSize (minimumSize, initialiseToZero); } void MemoryBlock::swapWith (MemoryBlock& other) throw() { swapVariables (size, other.size); data.swapWith (other.data); } void MemoryBlock::fillWith (const uint8 value) throw() { memset (data, (int) value, size); } void MemoryBlock::append (const void* const srcData, const size_t numBytes) { if (numBytes > 0) { const size_t oldSize = size; setSize (size + numBytes); memcpy (data + oldSize, srcData, numBytes); } } void MemoryBlock::copyFrom (const void* const src, int offset, size_t num) throw() { const char* d = static_cast (src); if (offset < 0) { d -= offset; num -= offset; offset = 0; } if (offset + num > size) num = size - offset; if (num > 0) memcpy (data + offset, d, num); } void MemoryBlock::copyTo (void* const dst, int offset, size_t num) const throw() { char* d = static_cast (dst); if (offset < 0) { zeromem (d, -offset); d -= offset; num += offset; offset = 0; } if (offset + num > size) { const size_t newNum = size - offset; zeromem (d + newNum, num - newNum); num = newNum; } if (num > 0) memcpy (d, data + offset, num); } void MemoryBlock::removeSection (size_t startByte, size_t numBytesToRemove) { if (startByte < 0) { numBytesToRemove += startByte; startByte = 0; } if (startByte + numBytesToRemove >= size) { setSize (startByte); } else if (numBytesToRemove > 0) { memmove (data + startByte, data + startByte + numBytesToRemove, size - (startByte + numBytesToRemove)); setSize (size - numBytesToRemove); } } const String MemoryBlock::toString() const { return String (static_cast (getData()), size); } int MemoryBlock::getBitRange (const size_t bitRangeStart, size_t numBits) const throw() { int res = 0; size_t byte = bitRangeStart >> 3; int offsetInByte = (int) bitRangeStart & 7; size_t bitsSoFar = 0; while (numBits > 0 && (size_t) byte < size) { const int bitsThisTime = jmin ((int) numBits, 8 - offsetInByte); const int mask = (0xff >> (8 - bitsThisTime)) << offsetInByte; res |= (((data[byte] & mask) >> offsetInByte) << bitsSoFar); bitsSoFar += bitsThisTime; numBits -= bitsThisTime; ++byte; offsetInByte = 0; } return res; } void MemoryBlock::setBitRange (const size_t bitRangeStart, size_t numBits, int bitsToSet) throw() { size_t byte = bitRangeStart >> 3; int offsetInByte = (int) bitRangeStart & 7; unsigned int mask = ~((((unsigned int) 0xffffffff) << (32 - numBits)) >> (32 - numBits)); while (numBits > 0 && (size_t) byte < size) { const int bitsThisTime = jmin ((int) numBits, 8 - offsetInByte); const unsigned int tempMask = (mask << offsetInByte) | ~((((unsigned int) 0xffffffff) >> offsetInByte) << offsetInByte); const unsigned int tempBits = bitsToSet << offsetInByte; data[byte] = (char) ((data[byte] & tempMask) | tempBits); ++byte; numBits -= bitsThisTime; bitsToSet >>= bitsThisTime; mask >>= bitsThisTime; offsetInByte = 0; } } void MemoryBlock::loadFromHexString (const String& hex) { ensureSize (hex.length() >> 1); char* dest = data; int i = 0; for (;;) { int byte = 0; for (int loop = 2; --loop >= 0;) { byte <<= 4; for (;;) { const juce_wchar c = hex [i++]; if (c >= '0' && c <= '9') { byte |= c - '0'; break; } else if (c >= 'a' && c <= 'z') { byte |= c - ('a' - 10); break; } else if (c >= 'A' && c <= 'Z') { byte |= c - ('A' - 10); break; } else if (c == 0) { setSize (static_cast (dest - data)); return; } } } *dest++ = (char) byte; } } const char* const MemoryBlock::encodingTable = ".ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+"; const String MemoryBlock::toBase64Encoding() const { const size_t numChars = ((size << 3) + 5) / 6; String destString ((unsigned int) size); // store the length, followed by a '.', and then the data. const int initialLen = destString.length(); destString.preallocateStorage (initialLen + 2 + numChars); juce_wchar* d = destString; d += initialLen; *d++ = '.'; for (size_t i = 0; i < numChars; ++i) *d++ = encodingTable [getBitRange (i * 6, 6)]; *d++ = 0; return destString; } bool MemoryBlock::fromBase64Encoding (const String& s) { const int startPos = s.indexOfChar ('.') + 1; if (startPos <= 0) return false; const int numBytesNeeded = s.substring (0, startPos - 1).getIntValue(); setSize (numBytesNeeded, true); const int numChars = s.length() - startPos; const juce_wchar* srcChars = s; srcChars += startPos; int pos = 0; for (int i = 0; i < numChars; ++i) { const char c = (char) srcChars[i]; for (int j = 0; j < 64; ++j) { if (encodingTable[j] == c) { setBitRange (pos, 6, j); pos += 6; break; } } } return true; } END_JUCE_NAMESPACE /*** End of inlined file: juce_MemoryBlock.cpp ***/ /*** Start of inlined file: juce_PropertySet.cpp ***/ BEGIN_JUCE_NAMESPACE PropertySet::PropertySet (const bool ignoreCaseOfKeyNames) : properties (ignoreCaseOfKeyNames), fallbackProperties (0), ignoreCaseOfKeys (ignoreCaseOfKeyNames) { } PropertySet::PropertySet (const PropertySet& other) : properties (other.properties), fallbackProperties (other.fallbackProperties), ignoreCaseOfKeys (other.ignoreCaseOfKeys) { } PropertySet& PropertySet::operator= (const PropertySet& other) { properties = other.properties; fallbackProperties = other.fallbackProperties; ignoreCaseOfKeys = other.ignoreCaseOfKeys; propertyChanged(); return *this; } PropertySet::~PropertySet() { } void PropertySet::clear() { const ScopedLock sl (lock); if (properties.size() > 0) { properties.clear(); propertyChanged(); } } const String PropertySet::getValue (const String& keyName, const String& defaultValue) const throw() { const ScopedLock sl (lock); const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); if (index >= 0) return properties.getAllValues() [index]; return fallbackProperties != 0 ? fallbackProperties->getValue (keyName, defaultValue) : defaultValue; } int PropertySet::getIntValue (const String& keyName, const int defaultValue) const throw() { const ScopedLock sl (lock); const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); if (index >= 0) return properties.getAllValues() [index].getIntValue(); return fallbackProperties != 0 ? fallbackProperties->getIntValue (keyName, defaultValue) : defaultValue; } double PropertySet::getDoubleValue (const String& keyName, const double defaultValue) const throw() { const ScopedLock sl (lock); const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); if (index >= 0) return properties.getAllValues()[index].getDoubleValue(); return fallbackProperties != 0 ? fallbackProperties->getDoubleValue (keyName, defaultValue) : defaultValue; } bool PropertySet::getBoolValue (const String& keyName, const bool defaultValue) const throw() { const ScopedLock sl (lock); const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); if (index >= 0) return properties.getAllValues() [index].getIntValue() != 0; return fallbackProperties != 0 ? fallbackProperties->getBoolValue (keyName, defaultValue) : defaultValue; } XmlElement* PropertySet::getXmlValue (const String& keyName) const { XmlDocument doc (getValue (keyName)); return doc.getDocumentElement(); } void PropertySet::setValue (const String& keyName, const var& v) { jassert (keyName.isNotEmpty()); // shouldn't use an empty key name! if (keyName.isNotEmpty()) { const String value (v.toString()); const ScopedLock sl (lock); const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); if (index < 0 || properties.getAllValues() [index] != value) { properties.set (keyName, value); propertyChanged(); } } } void PropertySet::removeValue (const String& keyName) { if (keyName.isNotEmpty()) { const ScopedLock sl (lock); const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); if (index >= 0) { properties.remove (keyName); propertyChanged(); } } } void PropertySet::setValue (const String& keyName, const XmlElement* const xml) { setValue (keyName, xml == 0 ? var::null : var (xml->createDocument (String::empty, true))); } bool PropertySet::containsKey (const String& keyName) const throw() { const ScopedLock sl (lock); return properties.getAllKeys().contains (keyName, ignoreCaseOfKeys); } void PropertySet::setFallbackPropertySet (PropertySet* fallbackProperties_) throw() { const ScopedLock sl (lock); fallbackProperties = fallbackProperties_; } XmlElement* PropertySet::createXml (const String& nodeName) const { const ScopedLock sl (lock); XmlElement* const xml = new XmlElement (nodeName); for (int i = 0; i < properties.getAllKeys().size(); ++i) { XmlElement* const e = xml->createNewChildElement ("VALUE"); e->setAttribute ("name", properties.getAllKeys()[i]); e->setAttribute ("val", properties.getAllValues()[i]); } return xml; } void PropertySet::restoreFromXml (const XmlElement& xml) { const ScopedLock sl (lock); clear(); forEachXmlChildElementWithTagName (xml, e, "VALUE") { if (e->hasAttribute ("name") && e->hasAttribute ("val")) { properties.set (e->getStringAttribute ("name"), e->getStringAttribute ("val")); } } if (properties.size() > 0) propertyChanged(); } void PropertySet::propertyChanged() { } END_JUCE_NAMESPACE /*** End of inlined file: juce_PropertySet.cpp ***/ /*** Start of inlined file: juce_Identifier.cpp ***/ BEGIN_JUCE_NAMESPACE StringPool& Identifier::getPool() { static StringPool pool; return pool; } Identifier::Identifier() throw() : name (0) { } Identifier::Identifier (const Identifier& other) throw() : name (other.name) { } Identifier& Identifier::operator= (const Identifier& other) throw() { name = other.name; return *this; } Identifier::Identifier (const String& name_) : name (Identifier::getPool().getPooledString (name_)) { /* An Identifier string must be suitable for use as a script variable or XML attribute, so it can only contain this limited set of characters.. */ jassert (name_.containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_") && name_.isNotEmpty()); } Identifier::Identifier (const char* const name_) : name (Identifier::getPool().getPooledString (name_)) { /* An Identifier string must be suitable for use as a script variable or XML attribute, so it can only contain this limited set of characters.. */ jassert (toString().containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_") && toString().isNotEmpty()); } Identifier::~Identifier() { } END_JUCE_NAMESPACE /*** End of inlined file: juce_Identifier.cpp ***/ /*** Start of inlined file: juce_Variant.cpp ***/ BEGIN_JUCE_NAMESPACE class var::VariantType { public: VariantType() {} virtual ~VariantType() {} virtual int toInt (const ValueUnion&) const { return 0; } virtual double toDouble (const ValueUnion&) const { return 0; } virtual const String toString (const ValueUnion&) const { return String::empty; } virtual bool toBool (const ValueUnion&) const { return false; } virtual DynamicObject* toObject (const ValueUnion&) const { return 0; } virtual bool isVoid() const throw() { return false; } virtual bool isInt() const throw() { return false; } virtual bool isBool() const throw() { return false; } virtual bool isDouble() const throw() { return false; } virtual bool isString() const throw() { return false; } virtual bool isObject() const throw() { return false; } virtual bool isMethod() const throw() { return false; } virtual void cleanUp (ValueUnion&) const throw() {} virtual void createCopy (ValueUnion& dest, const ValueUnion& source) const { dest = source; } virtual bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const throw() = 0; virtual void writeToStream (const ValueUnion& data, OutputStream& output) const = 0; }; class var::VariantType_Void : public var::VariantType { public: static const VariantType_Void* getInstance() { static const VariantType_Void i; return &i; } bool isVoid() const throw() { return true; } bool equals (const ValueUnion&, const ValueUnion&, const VariantType& otherType) const throw() { return otherType.isVoid(); } void writeToStream (const ValueUnion&, OutputStream& output) const { output.writeCompressedInt (0); } }; class var::VariantType_Int : public var::VariantType { public: static const VariantType_Int* getInstance() { static const VariantType_Int i; return &i; } int toInt (const ValueUnion& data) const { return data.intValue; }; double toDouble (const ValueUnion& data) const { return (double) data.intValue; } const String toString (const ValueUnion& data) const { return String (data.intValue); } bool toBool (const ValueUnion& data) const { return data.intValue != 0; } bool isInt() const throw() { return true; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const throw() { return otherType.toInt (otherData) == data.intValue; } void writeToStream (const ValueUnion& data, OutputStream& output) const { output.writeCompressedInt (5); output.writeByte (1); output.writeInt (data.intValue); } }; class var::VariantType_Double : public var::VariantType { public: static const VariantType_Double* getInstance() { static const VariantType_Double i; return &i; } int toInt (const ValueUnion& data) const { return (int) data.doubleValue; }; double toDouble (const ValueUnion& data) const { return data.doubleValue; } const String toString (const ValueUnion& data) const { return String (data.doubleValue); } bool toBool (const ValueUnion& data) const { return data.doubleValue != 0; } bool isDouble() const throw() { return true; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const throw() { return otherType.toDouble (otherData) == data.doubleValue; } void writeToStream (const ValueUnion& data, OutputStream& output) const { output.writeCompressedInt (9); output.writeByte (4); output.writeDouble (data.doubleValue); } }; class var::VariantType_Bool : public var::VariantType { public: static const VariantType_Bool* getInstance() { static const VariantType_Bool i; return &i; } int toInt (const ValueUnion& data) const { return data.boolValue ? 1 : 0; }; double toDouble (const ValueUnion& data) const { return data.boolValue ? 1.0 : 0.0; } const String toString (const ValueUnion& data) const { return String::charToString (data.boolValue ? '1' : '0'); } bool toBool (const ValueUnion& data) const { return data.boolValue; } bool isBool() const throw() { return true; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const throw() { return otherType.toBool (otherData) == data.boolValue; } void writeToStream (const ValueUnion& data, OutputStream& output) const { output.writeCompressedInt (1); output.writeByte (data.boolValue ? 2 : 3); } }; class var::VariantType_String : public var::VariantType { public: static const VariantType_String* getInstance() { static const VariantType_String i; return &i; } void cleanUp (ValueUnion& data) const throw() { delete data.stringValue; } void createCopy (ValueUnion& dest, const ValueUnion& source) const { dest.stringValue = new String (*source.stringValue); } int toInt (const ValueUnion& data) const { return data.stringValue->getIntValue(); }; double toDouble (const ValueUnion& data) const { return data.stringValue->getDoubleValue(); } const String toString (const ValueUnion& data) const { return *data.stringValue; } bool toBool (const ValueUnion& data) const { return data.stringValue->getIntValue() != 0 || data.stringValue->trim().equalsIgnoreCase ("true") || data.stringValue->trim().equalsIgnoreCase ("yes"); } bool isString() const throw() { return true; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const throw() { return otherType.toString (otherData) == *data.stringValue; } void writeToStream (const ValueUnion& data, OutputStream& output) const { const int len = data.stringValue->getNumBytesAsUTF8() + 1; output.writeCompressedInt (len + 1); output.writeByte (5); HeapBlock temp (len); data.stringValue->copyToUTF8 (temp, len); output.write (temp, len); } }; class var::VariantType_Object : public var::VariantType { public: static const VariantType_Object* getInstance() { static const VariantType_Object i; return &i; } void cleanUp (ValueUnion& data) const throw() { if (data.objectValue != 0) data.objectValue->decReferenceCount(); } void createCopy (ValueUnion& dest, const ValueUnion& source) const { dest.objectValue = source.objectValue; if (dest.objectValue != 0) dest.objectValue->incReferenceCount(); } const String toString (const ValueUnion& data) const { return "Object 0x" + String::toHexString ((int) (pointer_sized_int) data.objectValue); } bool toBool (const ValueUnion& data) const { return data.objectValue != 0; } DynamicObject* toObject (const ValueUnion& data) const { return data.objectValue; } bool isObject() const throw() { return true; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const throw() { return otherType.toObject (otherData) == data.objectValue; } void writeToStream (const ValueUnion&, OutputStream& output) const { jassertfalse; // Can't write an object to a stream! output.writeCompressedInt (0); } }; class var::VariantType_Method : public var::VariantType { public: static const VariantType_Method* getInstance() { static const VariantType_Method i; return &i; } const String toString (const ValueUnion&) const { return "Method"; } bool toBool (const ValueUnion& data) const { return data.methodValue != 0; } bool isMethod() const throw() { return true; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const throw() { return otherType.isMethod() && otherData.methodValue == data.methodValue; } void writeToStream (const ValueUnion&, OutputStream& output) const { jassertfalse; // Can't write a method to a stream! output.writeCompressedInt (0); } }; var::var() throw() : type (VariantType_Void::getInstance()) { } var::~var() throw() { type->cleanUp (value); } const var var::null; var::var (const var& valueToCopy) : type (valueToCopy.type) { type->createCopy (value, valueToCopy.value); } var::var (const int value_) throw() : type (VariantType_Int::getInstance()) { value.intValue = value_; } var::var (const bool value_) throw() : type (VariantType_Bool::getInstance()) { value.boolValue = value_; } var::var (const double value_) throw() : type (VariantType_Double::getInstance()) { value.doubleValue = value_; } var::var (const String& value_) : type (VariantType_String::getInstance()) { value.stringValue = new String (value_); } var::var (const char* const value_) : type (VariantType_String::getInstance()) { value.stringValue = new String (value_); } var::var (const juce_wchar* const value_) : type (VariantType_String::getInstance()) { value.stringValue = new String (value_); } var::var (DynamicObject* const object) : type (VariantType_Object::getInstance()) { value.objectValue = object; if (object != 0) object->incReferenceCount(); } var::var (MethodFunction method_) throw() : type (VariantType_Method::getInstance()) { value.methodValue = method_; } bool var::isVoid() const throw() { return type->isVoid(); } bool var::isInt() const throw() { return type->isInt(); } bool var::isBool() const throw() { return type->isBool(); } bool var::isDouble() const throw() { return type->isDouble(); } bool var::isString() const throw() { return type->isString(); } bool var::isObject() const throw() { return type->isObject(); } bool var::isMethod() const throw() { return type->isMethod(); } var::operator int() const { return type->toInt (value); } var::operator bool() const { return type->toBool (value); } var::operator float() const { return (float) type->toDouble (value); } var::operator double() const { return type->toDouble (value); } const String var::toString() const { return type->toString (value); } var::operator const String() const { return type->toString (value); } DynamicObject* var::getObject() const { return type->toObject (value); } void var::swapWith (var& other) throw() { swapVariables (type, other.type); swapVariables (value, other.value); } var& var::operator= (const var& newValue) { type->cleanUp (value); type = newValue.type; type->createCopy (value, newValue.value); return *this; } var& var::operator= (int newValue) { var v (newValue); swapWith (v); return *this; } var& var::operator= (bool newValue) { var v (newValue); swapWith (v); return *this; } var& var::operator= (double newValue) { var v (newValue); swapWith (v); return *this; } var& var::operator= (const char* newValue) { var v (newValue); swapWith (v); return *this; } var& var::operator= (const juce_wchar* newValue) { var v (newValue); swapWith (v); return *this; } var& var::operator= (const String& newValue) { var v (newValue); swapWith (v); return *this; } var& var::operator= (DynamicObject* newValue) { var v (newValue); swapWith (v); return *this; } var& var::operator= (MethodFunction newValue) { var v (newValue); swapWith (v); return *this; } bool var::equals (const var& other) const throw() { return type->equals (value, other.value, *other.type); } bool operator== (const var& v1, const var& v2) throw() { return v1.equals (v2); } bool operator!= (const var& v1, const var& v2) throw() { return ! v1.equals (v2); } bool operator== (const var& v1, const String& v2) throw() { return v1.toString() == v2; } bool operator!= (const var& v1, const String& v2) throw() { return v1.toString() != v2; } void var::writeToStream (OutputStream& output) const { type->writeToStream (value, output); } const var var::readFromStream (InputStream& input) { const int numBytes = input.readCompressedInt(); if (numBytes > 0) { switch (input.readByte()) { case 1: return var (input.readInt()); case 2: return var (true); case 3: return var (false); case 4: return var (input.readDouble()); case 5: { MemoryOutputStream mo; mo.writeFromInputStream (input, numBytes - 1); return var (mo.toUTF8()); } default: input.skipNextBytes (numBytes - 1); break; } } return var::null; } const var var::operator[] (const Identifier& propertyName) const { DynamicObject* const o = getObject(); return o != 0 ? o->getProperty (propertyName) : var::null; } const var var::invoke (const Identifier& method, const var* arguments, int numArguments) const { DynamicObject* const o = getObject(); return o != 0 ? o->invokeMethod (method, arguments, numArguments) : var::null; } const var var::invoke (const var& targetObject, const var* arguments, int numArguments) const { if (isMethod()) { DynamicObject* const target = targetObject.getObject(); if (target != 0) return (target->*(value.methodValue)) (arguments, numArguments); } return var::null; } const var var::call (const Identifier& method) const { return invoke (method, 0, 0); } const var var::call (const Identifier& method, const var& arg1) const { return invoke (method, &arg1, 1); } const var var::call (const Identifier& method, const var& arg1, const var& arg2) const { var args[] = { arg1, arg2 }; return invoke (method, args, 2); } const var var::call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3) { var args[] = { arg1, arg2, arg3 }; return invoke (method, args, 3); } const var var::call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4) const { var args[] = { arg1, arg2, arg3, arg4 }; return invoke (method, args, 4); } const var var::call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4, const var& arg5) const { var args[] = { arg1, arg2, arg3, arg4, arg5 }; return invoke (method, args, 5); } END_JUCE_NAMESPACE /*** End of inlined file: juce_Variant.cpp ***/ /*** Start of inlined file: juce_NamedValueSet.cpp ***/ BEGIN_JUCE_NAMESPACE NamedValueSet::NamedValue::NamedValue() throw() { } inline NamedValueSet::NamedValue::NamedValue (const Identifier& name_, const var& value_) : name (name_), value (value_) { } bool NamedValueSet::NamedValue::operator== (const NamedValueSet::NamedValue& other) const throw() { return name == other.name && value == other.value; } NamedValueSet::NamedValueSet() throw() { } NamedValueSet::NamedValueSet (const NamedValueSet& other) : values (other.values) { } NamedValueSet& NamedValueSet::operator= (const NamedValueSet& other) { values = other.values; return *this; } NamedValueSet::~NamedValueSet() { } bool NamedValueSet::operator== (const NamedValueSet& other) const { return values == other.values; } bool NamedValueSet::operator!= (const NamedValueSet& other) const { return ! operator== (other); } int NamedValueSet::size() const throw() { return values.size(); } const var& NamedValueSet::operator[] (const Identifier& name) const { for (int i = values.size(); --i >= 0;) { const NamedValue& v = values.getReference(i); if (v.name == name) return v.value; } return var::null; } const var NamedValueSet::getWithDefault (const Identifier& name, const var& defaultReturnValue) const { const var* v = getItem (name); return v != 0 ? *v : defaultReturnValue; } var* NamedValueSet::getItem (const Identifier& name) const { for (int i = values.size(); --i >= 0;) { NamedValue& v = values.getReference(i); if (v.name == name) return &(v.value); } return 0; } bool NamedValueSet::set (const Identifier& name, const var& newValue) { for (int i = values.size(); --i >= 0;) { NamedValue& v = values.getReference(i); if (v.name == name) { if (v.value == newValue) return false; v.value = newValue; return true; } } values.add (NamedValue (name, newValue)); return true; } bool NamedValueSet::contains (const Identifier& name) const { return getItem (name) != 0; } bool NamedValueSet::remove (const Identifier& name) { for (int i = values.size(); --i >= 0;) { if (values.getReference(i).name == name) { values.remove (i); return true; } } return false; } const Identifier NamedValueSet::getName (const int index) const { jassert (((unsigned int) index) < (unsigned int) values.size()); return values [index].name; } const var NamedValueSet::getValueAt (const int index) const { jassert (((unsigned int) index) < (unsigned int) values.size()); return values [index].value; } void NamedValueSet::clear() { values.clear(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_NamedValueSet.cpp ***/ /*** Start of inlined file: juce_DynamicObject.cpp ***/ BEGIN_JUCE_NAMESPACE DynamicObject::DynamicObject() { } DynamicObject::~DynamicObject() { } bool DynamicObject::hasProperty (const Identifier& propertyName) const { var* const v = properties.getItem (propertyName); return v != 0 && ! v->isMethod(); } const var DynamicObject::getProperty (const Identifier& propertyName) const { return properties [propertyName]; } void DynamicObject::setProperty (const Identifier& propertyName, const var& newValue) { properties.set (propertyName, newValue); } void DynamicObject::removeProperty (const Identifier& propertyName) { properties.remove (propertyName); } bool DynamicObject::hasMethod (const Identifier& methodName) const { return getProperty (methodName).isMethod(); } const var DynamicObject::invokeMethod (const Identifier& methodName, const var* parameters, int numParameters) { return properties [methodName].invoke (var (this), parameters, numParameters); } void DynamicObject::setMethod (const Identifier& name, var::MethodFunction methodFunction) { properties.set (name, var (methodFunction)); } void DynamicObject::clear() { properties.clear(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_DynamicObject.cpp ***/ /*** Start of inlined file: juce_Expression.cpp ***/ BEGIN_JUCE_NAMESPACE class Expression::Helpers { public: typedef ReferenceCountedObjectPtr TermPtr; class Constant : public Term { public: Constant (const double value_, bool isResolutionTarget_) : value (value_), isResolutionTarget (isResolutionTarget_) {} Type getType() const throw() { return constantType; } Term* clone() const { return new Constant (value, isResolutionTarget); } double evaluate (const EvaluationContext&, int) const { return value; } int getNumInputs() const { return 0; } Term* getInput (int) const { return 0; } const TermPtr negated() { return new Constant (-value, isResolutionTarget); } const String toString() const { if (isResolutionTarget) return "@" + String (value); return String (value); } double value; bool isResolutionTarget; }; class Symbol : public Term { public: explicit Symbol (const String& symbol_) : mainSymbol (symbol_.upToFirstOccurrenceOf (".", false, false).trim()), member (symbol_.fromFirstOccurrenceOf (".", false, false).trim()) {} Symbol (const String& symbol_, const String& member_) : mainSymbol (symbol_), member (member_) {} double evaluate (const EvaluationContext& c, int recursionDepth) const { if (++recursionDepth > 256) throw EvaluationError ("Recursive symbol references"); try { return c.getSymbolValue (mainSymbol, member).term->evaluate (c, recursionDepth); } catch (...) {} return 0; } Type getType() const throw() { return symbolType; } Term* clone() const { return new Symbol (mainSymbol, member); } int getNumInputs() const { return 0; } Term* getInput (int) const { return 0; } const String getFunctionName() const { return toString(); } const String toString() const { return member.isEmpty() ? mainSymbol : mainSymbol + "." + member; } bool referencesSymbol (const String& s, const EvaluationContext& c, int recursionDepth) const { if (s == mainSymbol) return true; if (++recursionDepth > 256) throw EvaluationError ("Recursive symbol references"); try { return c.getSymbolValue (mainSymbol, member).term->referencesSymbol (s, c, recursionDepth); } catch (EvaluationError&) { return false; } } String mainSymbol, member; }; class Function : public Term { public: Function (const String& functionName_, const ReferenceCountedArray& parameters_) : functionName (functionName_), parameters (parameters_) {} Term* clone() const { return new Function (functionName, parameters); } double evaluate (const EvaluationContext& c, int recursionDepth) const { HeapBlock params (parameters.size()); for (int i = 0; i < parameters.size(); ++i) params[i] = parameters.getUnchecked(i)->evaluate (c, recursionDepth); return c.evaluateFunction (functionName, params, parameters.size()); } Type getType() const throw() { return functionType; } int getInputIndexFor (const Term* possibleInput) const { return parameters.indexOf (possibleInput); } int getNumInputs() const { return parameters.size(); } Term* getInput (int i) const { return parameters [i]; } const String getFunctionName() const { return functionName; } bool referencesSymbol (const String& s, const EvaluationContext& c, int recursionDepth) const { for (int i = 0; i < parameters.size(); ++i) if (parameters.getUnchecked(i)->referencesSymbol (s, c, recursionDepth)) return true; return false; } const String toString() const { if (parameters.size() == 0) return functionName + "()"; String s (functionName + " ("); for (int i = 0; i < parameters.size(); ++i) { s << parameters.getUnchecked(i)->toString(); if (i < parameters.size() - 1) s << ", "; } s << ')'; return s; } const String functionName; ReferenceCountedArray parameters; }; class Negate : public Term { public: Negate (const TermPtr& input_) : input (input_) { jassert (input_ != 0); } Type getType() const throw() { return operatorType; } int getInputIndexFor (const Term* possibleInput) const { return possibleInput == input ? 0 : -1; } int getNumInputs() const { return 1; } Term* getInput (int index) const { return index == 0 ? static_cast (input) : 0; } Term* clone() const { return new Negate (input->clone()); } double evaluate (const EvaluationContext& c, int recursionDepth) const { return -input->evaluate (c, recursionDepth); } const String getFunctionName() const { return "-"; } const TermPtr negated() { return input; } const TermPtr createTermToEvaluateInput (const EvaluationContext& context, const Term* input_, double overallTarget, Term* topLevelTerm) const { jassert (input_ == input); const Term* const dest = findDestinationFor (topLevelTerm, this); return new Negate (dest == 0 ? new Constant (overallTarget, false) : dest->createTermToEvaluateInput (context, this, overallTarget, topLevelTerm)); } const String toString() const { if (input->getOperatorPrecedence() > 0) return "-(" + input->toString() + ")"; else return "-" + input->toString(); } bool referencesSymbol (const String& s, const EvaluationContext& c, int recursionDepth) const { return input->referencesSymbol (s, c, recursionDepth); } private: const TermPtr input; }; class BinaryTerm : public Term { public: BinaryTerm (Term* const left_, Term* const right_) : left (left_), right (right_) { jassert (left_ != 0 && right_ != 0); } int getInputIndexFor (const Term* possibleInput) const { return possibleInput == left ? 0 : (possibleInput == right ? 1 : -1); } Type getType() const throw() { return operatorType; } int getNumInputs() const { return 2; } Term* getInput (int index) const { return index == 0 ? static_cast (left) : (index == 1 ? static_cast (right) : 0); } bool referencesSymbol (const String& s, const EvaluationContext& c, int recursionDepth) const { return left->referencesSymbol (s, c, recursionDepth) || right->referencesSymbol (s, c, recursionDepth); } const String toString() const { String s; const int ourPrecendence = getOperatorPrecedence(); if (left->getOperatorPrecedence() > ourPrecendence) s << '(' << left->toString() << ')'; else s = left->toString(); s << ' ' << getFunctionName() << ' '; if (right->getOperatorPrecedence() >= ourPrecendence) s << '(' << right->toString() << ')'; else s << right->toString(); return s; } protected: const TermPtr left, right; const TermPtr createDestinationTerm (const EvaluationContext& context, const Term* input, double overallTarget, Term* topLevelTerm) const { jassert (input == left || input == right); if (input != left && input != right) return 0; const Term* const dest = findDestinationFor (topLevelTerm, this); if (dest == 0) return new Constant (overallTarget, false); return dest->createTermToEvaluateInput (context, this, overallTarget, topLevelTerm); } }; class Add : public BinaryTerm { public: Add (Term* const left_, Term* const right_) : BinaryTerm (left_, right_) {} Term* clone() const { return new Add (left->clone(), right->clone()); } double evaluate (const EvaluationContext& c, int recursionDepth) const { return left->evaluate (c, recursionDepth) + right->evaluate (c, recursionDepth); } int getOperatorPrecedence() const { return 2; } const String getFunctionName() const { return "+"; } const TermPtr createTermToEvaluateInput (const EvaluationContext& c, const Term* input, double overallTarget, Term* topLevelTerm) const { const TermPtr newDest (createDestinationTerm (c, input, overallTarget, topLevelTerm)); if (newDest == 0) return 0; return new Subtract (newDest, (input == left ? right : left)->clone()); } }; class Subtract : public BinaryTerm { public: Subtract (Term* const left_, Term* const right_) : BinaryTerm (left_, right_) {} Term* clone() const { return new Subtract (left->clone(), right->clone()); } double evaluate (const EvaluationContext& c, int recursionDepth) const { return left->evaluate (c, recursionDepth) - right->evaluate (c, recursionDepth); } int getOperatorPrecedence() const { return 2; } const String getFunctionName() const { return "-"; } const TermPtr createTermToEvaluateInput (const EvaluationContext& c, const Term* input, double overallTarget, Term* topLevelTerm) const { const TermPtr newDest (createDestinationTerm (c, input, overallTarget, topLevelTerm)); if (newDest == 0) return 0; if (input == left) return new Add (newDest, right->clone()); else return new Subtract (left->clone(), newDest); } }; class Multiply : public BinaryTerm { public: Multiply (Term* const left_, Term* const right_) : BinaryTerm (left_, right_) {} Term* clone() const { return new Multiply (left->clone(), right->clone()); } double evaluate (const EvaluationContext& c, int recursionDepth) const { return left->evaluate (c, recursionDepth) * right->evaluate (c, recursionDepth); } const String getFunctionName() const { return "*"; } int getOperatorPrecedence() const { return 1; } const TermPtr createTermToEvaluateInput (const EvaluationContext& c, const Term* input, double overallTarget, Term* topLevelTerm) const { const TermPtr newDest (createDestinationTerm (c, input, overallTarget, topLevelTerm)); if (newDest == 0) return 0; return new Divide (newDest, (input == left ? right : left)->clone()); } }; class Divide : public BinaryTerm { public: Divide (Term* const left_, Term* const right_) : BinaryTerm (left_, right_) {} Term* clone() const { return new Divide (left->clone(), right->clone()); } double evaluate (const EvaluationContext& c, int recursionDepth) const { return left->evaluate (c, recursionDepth) / right->evaluate (c, recursionDepth); } const String getFunctionName() const { return "/"; } int getOperatorPrecedence() const { return 1; } const TermPtr createTermToEvaluateInput (const EvaluationContext& c, const Term* input, double overallTarget, Term* topLevelTerm) const { const TermPtr newDest (createDestinationTerm (c, input, overallTarget, topLevelTerm)); if (newDest == 0) return 0; if (input == left) return new Multiply (newDest, right->clone()); else return new Divide (left->clone(), newDest); } }; static Term* findDestinationFor (Term* const topLevel, const Term* const inputTerm) { const int inputIndex = topLevel->getInputIndexFor (inputTerm); if (inputIndex >= 0) return topLevel; for (int i = topLevel->getNumInputs(); --i >= 0;) { Term* t = findDestinationFor (topLevel->getInput (i), inputTerm); if (t != 0) return t; } return 0; } static Constant* findTermToAdjust (Term* const term, const bool mustBeFlagged) { Constant* c = dynamic_cast (term); if (c != 0 && (c->isResolutionTarget || ! mustBeFlagged)) return c; if (dynamic_cast (term) != 0) return 0; int i; const int numIns = term->getNumInputs(); for (i = 0; i < numIns; ++i) { Constant* c = dynamic_cast (term->getInput (i)); if (c != 0 && (c->isResolutionTarget || ! mustBeFlagged)) return c; } for (i = 0; i < numIns; ++i) { Constant* c = findTermToAdjust (term->getInput (i), mustBeFlagged); if (c != 0) return c; } return 0; } static bool containsAnySymbols (const Term* const t) { if (dynamic_cast (t) != 0) return true; for (int i = t->getNumInputs(); --i >= 0;) if (containsAnySymbols (t->getInput (i))) return true; return false; } static bool renameSymbol (Term* const t, const String& oldName, const String& newName) { Symbol* const sym = dynamic_cast (t); if (sym != 0 && sym->mainSymbol == oldName) { sym->mainSymbol = newName; return true; } bool anyChanged = false; for (int i = t->getNumInputs(); --i >= 0;) if (renameSymbol (t->getInput (i), oldName, newName)) anyChanged = true; return anyChanged; } class Parser { public: Parser (const String& stringToParse, int& textIndex_) : textString (stringToParse), textIndex (textIndex_) { text = textString; } const TermPtr readExpression() { TermPtr lhs (readMultiplyOrDivideExpression()); char opType; while (lhs != 0 && readOperator ("+-", &opType)) { TermPtr rhs (readMultiplyOrDivideExpression()); if (rhs == 0) throw ParseError ("Expected expression after \"" + String::charToString (opType) + "\""); if (opType == '+') lhs = new Add (lhs, rhs); else lhs = new Subtract (lhs, rhs); } return lhs; } private: const String textString; const juce_wchar* text; int& textIndex; static inline bool isDecimalDigit (const juce_wchar c) throw() { return c >= '0' && c <= '9'; } void skipWhitespace (int& i) { while (CharacterFunctions::isWhitespace (text [i])) ++i; } bool readChar (const juce_wchar required) { if (text[textIndex] == required) { ++textIndex; return true; } return false; } bool readOperator (const char* ops, char* const opType = 0) { skipWhitespace (textIndex); while (*ops != 0) { if (readChar (*ops)) { if (opType != 0) *opType = *ops; return true; } ++ops; } return false; } bool readIdentifier (String& identifier) { skipWhitespace (textIndex); int i = textIndex; if (CharacterFunctions::isLetter (text[i]) || text[i] == '_') { ++i; while (CharacterFunctions::isLetterOrDigit (text[i]) || text[i] == '_' || text[i] == '.') ++i; } if (i > textIndex) { identifier = String (text + textIndex, i - textIndex); textIndex = i; return true; } return false; } Term* readNumber() { skipWhitespace (textIndex); int i = textIndex; const bool isResolutionTarget = (text[i] == '@'); if (isResolutionTarget) { ++i; skipWhitespace (i); textIndex = i; } if (text[i] == '-') { ++i; skipWhitespace (i); } int numDigits = 0; while (isDecimalDigit (text[i])) { ++i; ++numDigits; } const bool hasPoint = (text[i] == '.'); if (hasPoint) { ++i; while (isDecimalDigit (text[i])) { ++i; ++numDigits; } } if (numDigits == 0) return 0; juce_wchar c = text[i]; const bool hasExponent = (c == 'e' || c == 'E'); if (hasExponent) { ++i; c = text[i]; if (c == '+' || c == '-') ++i; int numExpDigits = 0; while (isDecimalDigit (text[i])) { ++i; ++numExpDigits; } if (numExpDigits == 0) return 0; } const int start = textIndex; textIndex = i; return new Constant (String (text + start, i - start).getDoubleValue(), isResolutionTarget); } const TermPtr readMultiplyOrDivideExpression() { TermPtr lhs (readUnaryExpression()); char opType; while (lhs != 0 && readOperator ("*/", &opType)) { TermPtr rhs (readUnaryExpression()); if (rhs == 0) throw ParseError ("Expected expression after \"" + String::charToString (opType) + "\""); if (opType == '*') lhs = new Multiply (lhs, rhs); else lhs = new Divide (lhs, rhs); } return lhs; } const TermPtr readUnaryExpression() { char opType; if (readOperator ("+-", &opType)) { TermPtr term (readUnaryExpression()); if (term == 0) throw ParseError ("Expected expression after \"" + String::charToString (opType) + "\""); if (opType == '-') term = term->negated(); return term; } return readPrimaryExpression(); } const TermPtr readPrimaryExpression() { TermPtr e (readParenthesisedExpression()); if (e != 0) return e; e = readNumber(); if (e != 0) return e; String identifier; if (readIdentifier (identifier)) { if (readOperator ("(")) // method call... { Function* f = new Function (identifier, ReferenceCountedArray()); ScopedPointer func (f); // (can't use ScopedPointer in MSVC) TermPtr param (readExpression()); if (param == 0) { if (readOperator (")")) return func.release(); throw ParseError ("Expected parameters after \"" + identifier + " (\""); } f->parameters.add (param); while (readOperator (",")) { param = readExpression(); if (param == 0) throw ParseError ("Expected expression after \",\""); f->parameters.add (param); } if (readOperator (")")) return func.release(); throw ParseError ("Expected \")\""); } else // just a symbol.. { return new Symbol (identifier); } } return 0; } const TermPtr readParenthesisedExpression() { if (! readOperator ("(")) return 0; const TermPtr e (readExpression()); if (e == 0 || ! readOperator (")")) return 0; return e; } Parser (const Parser&); Parser& operator= (const Parser&); }; }; Expression::Expression() : term (new Expression::Helpers::Constant (0, false)) { } Expression::~Expression() { } Expression::Expression (Term* const term_) : term (term_) { jassert (term != 0); } Expression::Expression (const double constant) : term (new Expression::Helpers::Constant (constant, false)) { } Expression::Expression (const Expression& other) : term (other.term) { } Expression& Expression::operator= (const Expression& other) { term = other.term; return *this; } Expression::Expression (const String& stringToParse) { int i = 0; Helpers::Parser parser (stringToParse, i); term = parser.readExpression(); if (term == 0) term = new Helpers::Constant (0, false); } const Expression Expression::parse (const String& stringToParse, int& textIndexToStartFrom) { Helpers::Parser parser (stringToParse, textIndexToStartFrom); const Helpers::TermPtr term (parser.readExpression()); if (term != 0) return Expression (term); return Expression(); } double Expression::evaluate() const { return evaluate (Expression::EvaluationContext()); } double Expression::evaluate (const Expression::EvaluationContext& context) const { return term->evaluate (context, 0); } const Expression Expression::operator+ (const Expression& other) const { return Expression (new Helpers::Add (term, other.term)); } const Expression Expression::operator- (const Expression& other) const { return Expression (new Helpers::Subtract (term, other.term)); } const Expression Expression::operator* (const Expression& other) const { return Expression (new Helpers::Multiply (term, other.term)); } const Expression Expression::operator/ (const Expression& other) const { return Expression (new Helpers::Divide (term, other.term)); } const Expression Expression::operator-() const { return Expression (term->negated()); } const String Expression::toString() const { return term->toString(); } const Expression Expression::symbol (const String& symbol) { return Expression (new Helpers::Symbol (symbol)); } const Expression Expression::function (const String& functionName, const Array& parameters) { ReferenceCountedArray params; for (int i = 0; i < parameters.size(); ++i) params.add (parameters.getReference(i).term); return Expression (new Helpers::Function (functionName, params)); } const Expression Expression::adjustedToGiveNewResult (const double targetValue, const Expression::EvaluationContext& context) const { ScopedPointer newTerm (term->clone()); Helpers::Constant* termToAdjust = Helpers::findTermToAdjust (newTerm, true); if (termToAdjust == 0) termToAdjust = Helpers::findTermToAdjust (newTerm, false); if (termToAdjust == 0) { newTerm = new Helpers::Add (newTerm.release(), new Helpers::Constant (0, false)); termToAdjust = Helpers::findTermToAdjust (newTerm, false); } jassert (termToAdjust != 0); const Term* parent = Helpers::findDestinationFor (newTerm, termToAdjust); if (parent == 0) { termToAdjust->value = targetValue; } else { const Helpers::TermPtr reverseTerm (parent->createTermToEvaluateInput (context, termToAdjust, targetValue, newTerm)); if (reverseTerm == 0) return Expression (targetValue); termToAdjust->value = reverseTerm->evaluate (context, 0); } return Expression (newTerm.release()); } const Expression Expression::withRenamedSymbol (const String& oldSymbol, const String& newSymbol) const { jassert (newSymbol.toLowerCase().containsOnly ("abcdefghijklmnopqrstuvwxyz0123456789_")); Expression newExpression (term->clone()); Helpers::renameSymbol (newExpression.term, oldSymbol, newSymbol); return newExpression; } bool Expression::referencesSymbol (const String& symbol, const EvaluationContext& context) const { return term->referencesSymbol (symbol, context, 0); } bool Expression::usesAnySymbols() const { return Helpers::containsAnySymbols (term); } Expression::Type Expression::getType() const throw() { return term->getType(); } const String Expression::getSymbol() const { return term->getSymbolName(); } const String Expression::getFunction() const { return term->getFunctionName(); } const String Expression::getOperator() const { return term->getFunctionName(); } int Expression::getNumInputs() const { return term->getNumInputs(); } const Expression Expression::getInput (int index) const { return Expression (term->getInput (index)); } int Expression::Term::getOperatorPrecedence() const { return 0; } bool Expression::Term::referencesSymbol (const String&, const EvaluationContext&, int) const { return false; } int Expression::Term::getInputIndexFor (const Term*) const { return -1; } const ReferenceCountedObjectPtr Expression::Term::createTermToEvaluateInput (const EvaluationContext&, const Term*, double, Term*) const { jassertfalse; return 0; } const ReferenceCountedObjectPtr Expression::Term::negated() { return new Helpers::Negate (this); } const String Expression::Term::getSymbolName() const { jassertfalse; // You should only call getSymbol() on an expression that's actually a symbol! return String::empty; } const String Expression::Term::getFunctionName() const { jassertfalse; // You shouldn't call this for an expression that's not actually a function! return String::empty; } Expression::ParseError::ParseError (const String& message) : description (message) { DBG ("Expression::ParseError: " + message); } Expression::EvaluationError::EvaluationError (const String& message) : description (message) { DBG ("Expression::EvaluationError: " + message); } Expression::EvaluationContext::EvaluationContext() {} Expression::EvaluationContext::~EvaluationContext() {} const Expression Expression::EvaluationContext::getSymbolValue (const String& symbol, const String& member) const { throw EvaluationError ("Unknown symbol: \"" + symbol + (member.isEmpty() ? "\"" : ("." + member + "\""))); } double Expression::EvaluationContext::evaluateFunction (const String& functionName, const double* parameters, int numParams) const { if (numParams > 0) { if (functionName == "min") { double v = parameters[0]; for (int i = 1; i < numParams; ++i) v = jmin (v, parameters[i]); return v; } else if (functionName == "max") { double v = parameters[0]; for (int i = 1; i < numParams; ++i) v = jmax (v, parameters[i]); return v; } else if (numParams == 1) { if (functionName == "sin") return sin (parameters[0]); else if (functionName == "cos") return cos (parameters[0]); else if (functionName == "tan") return tan (parameters[0]); else if (functionName == "abs") return std::abs (parameters[0]); } } throw EvaluationError ("Unknown function: \"" + functionName + "\""); } END_JUCE_NAMESPACE /*** End of inlined file: juce_Expression.cpp ***/ /*** Start of inlined file: juce_BlowFish.cpp ***/ BEGIN_JUCE_NAMESPACE BlowFish::BlowFish (const void* const keyData, const int keyBytes) { jassert (keyData != 0); jassert (keyBytes > 0); static const uint32 initialPValues [18] = { 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b }; static const uint32 initialSValues [4 * 256] = { 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a, 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7, 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0, 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 }; memcpy (p, initialPValues, sizeof (p)); int i, j = 0; for (i = 4; --i >= 0;) { s[i].malloc (256); memcpy (s[i], initialSValues + i * 256, 256 * sizeof (uint32)); } for (i = 0; i < 18; ++i) { uint32 d = 0; for (int k = 0; k < 4; ++k) { d = (d << 8) | static_cast (keyData)[j]; if (++j >= keyBytes) j = 0; } p[i] = initialPValues[i] ^ d; } uint32 l = 0, r = 0; for (i = 0; i < 18; i += 2) { encrypt (l, r); p[i] = l; p[i + 1] = r; } for (i = 0; i < 4; ++i) { for (j = 0; j < 256; j += 2) { encrypt (l, r); s[i][j] = l; s[i][j + 1] = r; } } } BlowFish::BlowFish (const BlowFish& other) { for (int i = 4; --i >= 0;) s[i].malloc (256); operator= (other); } BlowFish& BlowFish::operator= (const BlowFish& other) { memcpy (p, other.p, sizeof (p)); for (int i = 4; --i >= 0;) memcpy (s[i], other.s[i], 256 * sizeof (uint32)); return *this; } BlowFish::~BlowFish() { } uint32 BlowFish::F (const uint32 x) const throw() { return ((s[0][(x >> 24) & 0xff] + s[1][(x >> 16) & 0xff]) ^ s[2][(x >> 8) & 0xff]) + s[3][x & 0xff]; } void BlowFish::encrypt (uint32& data1, uint32& data2) const throw() { uint32 l = data1; uint32 r = data2; for (int i = 0; i < 16; ++i) { l ^= p[i]; r ^= F(l); swapVariables (l, r); } data1 = r ^ p[17]; data2 = l ^ p[16]; } void BlowFish::decrypt (uint32& data1, uint32& data2) const throw() { uint32 l = data1; uint32 r = data2; for (int i = 17; i > 1; --i) { l ^= p[i]; r ^= F(l); swapVariables (l, r); } data1 = r ^ p[0]; data2 = l ^ p[1]; } END_JUCE_NAMESPACE /*** End of inlined file: juce_BlowFish.cpp ***/ /*** Start of inlined file: juce_MD5.cpp ***/ BEGIN_JUCE_NAMESPACE MD5::MD5() { zerostruct (result); } MD5::MD5 (const MD5& other) { memcpy (result, other.result, sizeof (result)); } MD5& MD5::operator= (const MD5& other) { memcpy (result, other.result, sizeof (result)); return *this; } MD5::MD5 (const MemoryBlock& data) { ProcessContext context; context.processBlock (data.getData(), data.getSize()); context.finish (result); } MD5::MD5 (const void* data, const size_t numBytes) { ProcessContext context; context.processBlock (data, numBytes); context.finish (result); } MD5::MD5 (const String& text) { ProcessContext context; const int len = text.length(); const juce_wchar* const t = text; for (int i = 0; i < len; ++i) { // force the string into integer-sized unicode characters, to try to make it // get the same results on all platforms + compilers. uint32 unicodeChar = ByteOrder::swapIfBigEndian ((uint32) t[i]); context.processBlock (&unicodeChar, sizeof (unicodeChar)); } context.finish (result); } void MD5::processStream (InputStream& input, int64 numBytesToRead) { ProcessContext context; if (numBytesToRead < 0) numBytesToRead = std::numeric_limits::max(); while (numBytesToRead > 0) { uint8 tempBuffer [512]; const int bytesRead = input.read (tempBuffer, (int) jmin (numBytesToRead, (int64) sizeof (tempBuffer))); if (bytesRead <= 0) break; numBytesToRead -= bytesRead; context.processBlock (tempBuffer, bytesRead); } context.finish (result); } MD5::MD5 (InputStream& input, int64 numBytesToRead) { processStream (input, numBytesToRead); } MD5::MD5 (const File& file) { const ScopedPointer fin (file.createInputStream()); if (fin != 0) processStream (*fin, -1); else zerostruct (result); } MD5::~MD5() { } namespace MD5Functions { static void encode (void* const output, const void* const input, const int numBytes) throw() { for (int i = 0; i < (numBytes >> 2); ++i) static_cast (output)[i] = ByteOrder::swapIfBigEndian (static_cast (input) [i]); } static inline uint32 F (const uint32 x, const uint32 y, const uint32 z) throw() { return (x & y) | (~x & z); } static inline uint32 G (const uint32 x, const uint32 y, const uint32 z) throw() { return (x & z) | (y & ~z); } static inline uint32 H (const uint32 x, const uint32 y, const uint32 z) throw() { return x ^ y ^ z; } static inline uint32 I (const uint32 x, const uint32 y, const uint32 z) throw() { return y ^ (x | ~z); } static inline uint32 rotateLeft (const uint32 x, const uint32 n) throw() { return (x << n) | (x >> (32 - n)); } static void FF (uint32& a, const uint32 b, const uint32 c, const uint32 d, const uint32 x, const uint32 s, const uint32 ac) throw() { a += F (b, c, d) + x + ac; a = rotateLeft (a, s) + b; } static void GG (uint32& a, const uint32 b, const uint32 c, const uint32 d, const uint32 x, const uint32 s, const uint32 ac) throw() { a += G (b, c, d) + x + ac; a = rotateLeft (a, s) + b; } static void HH (uint32& a, const uint32 b, const uint32 c, const uint32 d, const uint32 x, const uint32 s, const uint32 ac) throw() { a += H (b, c, d) + x + ac; a = rotateLeft (a, s) + b; } static void II (uint32& a, const uint32 b, const uint32 c, const uint32 d, const uint32 x, const uint32 s, const uint32 ac) throw() { a += I (b, c, d) + x + ac; a = rotateLeft (a, s) + b; } } MD5::ProcessContext::ProcessContext() { state[0] = 0x67452301; state[1] = 0xefcdab89; state[2] = 0x98badcfe; state[3] = 0x10325476; count[0] = 0; count[1] = 0; } void MD5::ProcessContext::processBlock (const void* const data, const size_t dataSize) { int bufferPos = ((count[0] >> 3) & 0x3F); count[0] += (uint32) (dataSize << 3); if (count[0] < ((uint32) dataSize << 3)) count[1]++; count[1] += (uint32) (dataSize >> 29); const size_t spaceLeft = 64 - bufferPos; size_t i = 0; if (dataSize >= spaceLeft) { memcpy (buffer + bufferPos, data, spaceLeft); transform (buffer); for (i = spaceLeft; i + 64 <= dataSize; i += 64) transform (static_cast (data) + i); bufferPos = 0; } memcpy (buffer + bufferPos, static_cast (data) + i, dataSize - i); } void MD5::ProcessContext::finish (void* const result) { unsigned char encodedLength[8]; MD5Functions::encode (encodedLength, count, 8); // Pad out to 56 mod 64. const int index = (uint32) ((count[0] >> 3) & 0x3f); const int paddingLength = (index < 56) ? (56 - index) : (120 - index); uint8 paddingBuffer [64]; zeromem (paddingBuffer, paddingLength); paddingBuffer [0] = 0x80; processBlock (paddingBuffer, paddingLength); processBlock (encodedLength, 8); MD5Functions::encode (result, state, 16); zerostruct (buffer); } void MD5::ProcessContext::transform (const void* const bufferToTransform) { using namespace MD5Functions; uint32 a = state[0]; uint32 b = state[1]; uint32 c = state[2]; uint32 d = state[3]; uint32 x[16]; encode (x, bufferToTransform, 64); enum Constants { S11 = 7, S12 = 12, S13 = 17, S14 = 22, S21 = 5, S22 = 9, S23 = 14, S24 = 20, S31 = 4, S32 = 11, S33 = 16, S34 = 23, S41 = 6, S42 = 10, S43 = 15, S44 = 21 }; FF (a, b, c, d, x[ 0], S11, 0xd76aa478); FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); FF (c, d, a, b, x[ 2], S13, 0x242070db); FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); FF (d, a, b, c, x[ 5], S12, 0x4787c62a); FF (c, d, a, b, x[ 6], S13, 0xa8304613); FF (b, c, d, a, x[ 7], S14, 0xfd469501); FF (a, b, c, d, x[ 8], S11, 0x698098d8); FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); FF (c, d, a, b, x[10], S13, 0xffff5bb1); FF (b, c, d, a, x[11], S14, 0x895cd7be); FF (a, b, c, d, x[12], S11, 0x6b901122); FF (d, a, b, c, x[13], S12, 0xfd987193); FF (c, d, a, b, x[14], S13, 0xa679438e); FF (b, c, d, a, x[15], S14, 0x49b40821); GG (a, b, c, d, x[ 1], S21, 0xf61e2562); GG (d, a, b, c, x[ 6], S22, 0xc040b340); GG (c, d, a, b, x[11], S23, 0x265e5a51); GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); GG (a, b, c, d, x[ 5], S21, 0xd62f105d); GG (d, a, b, c, x[10], S22, 0x02441453); GG (c, d, a, b, x[15], S23, 0xd8a1e681); GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); GG (d, a, b, c, x[14], S22, 0xc33707d6); GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); GG (b, c, d, a, x[ 8], S24, 0x455a14ed); GG (a, b, c, d, x[13], S21, 0xa9e3e905); GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); GG (c, d, a, b, x[ 7], S23, 0x676f02d9); GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); HH (a, b, c, d, x[ 5], S31, 0xfffa3942); HH (d, a, b, c, x[ 8], S32, 0x8771f681); HH (c, d, a, b, x[11], S33, 0x6d9d6122); HH (b, c, d, a, x[14], S34, 0xfde5380c); HH (a, b, c, d, x[ 1], S31, 0xa4beea44); HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); HH (b, c, d, a, x[10], S34, 0xbebfbc70); HH (a, b, c, d, x[13], S31, 0x289b7ec6); HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); HH (b, c, d, a, x[ 6], S34, 0x04881d05); HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); HH (d, a, b, c, x[12], S32, 0xe6db99e5); HH (c, d, a, b, x[15], S33, 0x1fa27cf8); HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); II (a, b, c, d, x[ 0], S41, 0xf4292244); II (d, a, b, c, x[ 7], S42, 0x432aff97); II (c, d, a, b, x[14], S43, 0xab9423a7); II (b, c, d, a, x[ 5], S44, 0xfc93a039); II (a, b, c, d, x[12], S41, 0x655b59c3); II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); II (c, d, a, b, x[10], S43, 0xffeff47d); II (b, c, d, a, x[ 1], S44, 0x85845dd1); II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); II (d, a, b, c, x[15], S42, 0xfe2ce6e0); II (c, d, a, b, x[ 6], S43, 0xa3014314); II (b, c, d, a, x[13], S44, 0x4e0811a1); II (a, b, c, d, x[ 4], S41, 0xf7537e82); II (d, a, b, c, x[11], S42, 0xbd3af235); II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); II (b, c, d, a, x[ 9], S44, 0xeb86d391); state[0] += a; state[1] += b; state[2] += c; state[3] += d; zerostruct (x); } const MemoryBlock MD5::getRawChecksumData() const { return MemoryBlock (result, sizeof (result)); } const String MD5::toHexString() const { return String::toHexString (result, sizeof (result), 0); } bool MD5::operator== (const MD5& other) const { return memcmp (result, other.result, sizeof (result)) == 0; } bool MD5::operator!= (const MD5& other) const { return ! operator== (other); } END_JUCE_NAMESPACE /*** End of inlined file: juce_MD5.cpp ***/ /*** Start of inlined file: juce_Primes.cpp ***/ BEGIN_JUCE_NAMESPACE namespace PrimesHelpers { static void createSmallSieve (const int numBits, BigInteger& result) { result.setBit (numBits); result.clearBit (numBits); // to enlarge the array result.setBit (0); int n = 2; do { for (int i = n + n; i < numBits; i += n) result.setBit (i); n = result.findNextClearBit (n + 1); } while (n <= (numBits >> 1)); } static void bigSieve (const BigInteger& base, const int numBits, BigInteger& result, const BigInteger& smallSieve, const int smallSieveSize) { jassert (! base[0]); // must be even! result.setBit (numBits); result.clearBit (numBits); // to enlarge the array int index = smallSieve.findNextClearBit (0); do { const int prime = (index << 1) + 1; BigInteger r (base), remainder; r.divideBy (prime, remainder); int i = prime - remainder.getBitRangeAsInt (0, 32); if (r.isZero()) i += prime; if ((i & 1) == 0) i += prime; i = (i - 1) >> 1; while (i < numBits) { result.setBit (i); i += prime; } index = smallSieve.findNextClearBit (index + 1); } while (index < smallSieveSize); } static bool findCandidate (const BigInteger& base, const BigInteger& sieve, const int numBits, BigInteger& result, const int certainty) { for (int i = 0; i < numBits; ++i) { if (! sieve[i]) { result = base + (unsigned int) ((i << 1) + 1); if (Primes::isProbablyPrime (result, certainty)) return true; } } return false; } static bool passesMillerRabin (const BigInteger& n, int iterations) { const BigInteger one (1), two (2); const BigInteger nMinusOne (n - one); BigInteger d (nMinusOne); const int s = d.findNextSetBit (0); d >>= s; BigInteger smallPrimes; int numBitsInSmallPrimes = 0; for (;;) { numBitsInSmallPrimes += 256; createSmallSieve (numBitsInSmallPrimes, smallPrimes); const int numPrimesFound = numBitsInSmallPrimes - smallPrimes.countNumberOfSetBits(); if (numPrimesFound > iterations + 1) break; } int smallPrime = 2; while (--iterations >= 0) { smallPrime = smallPrimes.findNextClearBit (smallPrime + 1); BigInteger r (smallPrime); r.exponentModulo (d, n); if (r != one && r != nMinusOne) { for (int j = 0; j < s; ++j) { r.exponentModulo (two, n); if (r == nMinusOne) break; } if (r != nMinusOne) return false; } } return true; } } const BigInteger Primes::createProbablePrime (const int bitLength, const int certainty, const int* randomSeeds, int numRandomSeeds) { using namespace PrimesHelpers; int defaultSeeds [16]; if (numRandomSeeds <= 0) { randomSeeds = defaultSeeds; numRandomSeeds = numElementsInArray (defaultSeeds); Random r (0); for (int j = 10; --j >= 0;) { r.setSeedRandomly(); for (int i = numRandomSeeds; --i >= 0;) defaultSeeds[i] ^= r.nextInt() ^ Random::getSystemRandom().nextInt(); } } BigInteger smallSieve; const int smallSieveSize = 15000; createSmallSieve (smallSieveSize, smallSieve); BigInteger p; for (int i = numRandomSeeds; --i >= 0;) { BigInteger p2; Random r (randomSeeds[i]); r.fillBitsRandomly (p2, 0, bitLength); p ^= p2; } p.setBit (bitLength - 1); p.clearBit (0); const int searchLen = jmax (1024, (bitLength / 20) * 64); while (p.getHighestBit() < bitLength) { p += 2 * searchLen; BigInteger sieve; bigSieve (p, searchLen, sieve, smallSieve, smallSieveSize); BigInteger candidate; if (findCandidate (p, sieve, searchLen, candidate, certainty)) return candidate; } jassertfalse; return BigInteger(); } bool Primes::isProbablyPrime (const BigInteger& number, const int certainty) { using namespace PrimesHelpers; if (! number[0]) return false; if (number.getHighestBit() <= 10) { const int num = number.getBitRangeAsInt (0, 10); for (int i = num / 2; --i > 1;) if (num % i == 0) return false; return true; } else { if (number.findGreatestCommonDivisor (2 * 3 * 5 * 7 * 11 * 13 * 17 * 19 * 23) != 1) return false; return passesMillerRabin (number, certainty); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_Primes.cpp ***/ /*** Start of inlined file: juce_RSAKey.cpp ***/ BEGIN_JUCE_NAMESPACE RSAKey::RSAKey() { } RSAKey::RSAKey (const String& s) { if (s.containsChar (',')) { part1.parseString (s.upToFirstOccurrenceOf (",", false, false), 16); part2.parseString (s.fromFirstOccurrenceOf (",", false, false), 16); } else { // the string needs to be two hex numbers, comma-separated.. jassertfalse; } } RSAKey::~RSAKey() { } bool RSAKey::operator== (const RSAKey& other) const throw() { return part1 == other.part1 && part2 == other.part2; } bool RSAKey::operator!= (const RSAKey& other) const throw() { return ! operator== (other); } const String RSAKey::toString() const { return part1.toString (16) + "," + part2.toString (16); } bool RSAKey::applyToValue (BigInteger& value) const { if (part1.isZero() || part2.isZero() || value <= 0) { jassertfalse; // using an uninitialised key value.clear(); return false; } BigInteger result; while (! value.isZero()) { result *= part2; BigInteger remainder; value.divideBy (part2, remainder); remainder.exponentModulo (part1, part2); result += remainder; } value.swapWith (result); return true; } const BigInteger RSAKey::findBestCommonDivisor (const BigInteger& p, const BigInteger& q) { // try 3, 5, 9, 17, etc first because these only contain 2 bits and so // are fast to divide + multiply for (int i = 2; i <= 65536; i *= 2) { const BigInteger e (1 + i); if (e.findGreatestCommonDivisor (p).isOne() && e.findGreatestCommonDivisor (q).isOne()) return e; } BigInteger e (4); while (! (e.findGreatestCommonDivisor (p).isOne() && e.findGreatestCommonDivisor (q).isOne())) ++e; return e; } void RSAKey::createKeyPair (RSAKey& publicKey, RSAKey& privateKey, const int numBits, const int* randomSeeds, const int numRandomSeeds) { jassert (numBits > 16); // not much point using less than this.. jassert (numRandomSeeds == 0 || numRandomSeeds >= 2); // you need to provide plenty of seeds here! BigInteger p (Primes::createProbablePrime (numBits / 2, 30, randomSeeds, numRandomSeeds / 2)); BigInteger q (Primes::createProbablePrime (numBits - numBits / 2, 30, randomSeeds == 0 ? 0 : (randomSeeds + numRandomSeeds / 2), numRandomSeeds - numRandomSeeds / 2)); const BigInteger n (p * q); const BigInteger m (--p * --q); const BigInteger e (findBestCommonDivisor (p, q)); BigInteger d (e); d.inverseModulo (m); publicKey.part1 = e; publicKey.part2 = n; privateKey.part1 = d; privateKey.part2 = n; } END_JUCE_NAMESPACE /*** End of inlined file: juce_RSAKey.cpp ***/ /*** Start of inlined file: juce_InputStream.cpp ***/ BEGIN_JUCE_NAMESPACE char InputStream::readByte() { char temp = 0; read (&temp, 1); return temp; } bool InputStream::readBool() { return readByte() != 0; } short InputStream::readShort() { char temp[2]; if (read (temp, 2) == 2) return (short) ByteOrder::littleEndianShort (temp); return 0; } short InputStream::readShortBigEndian() { char temp[2]; if (read (temp, 2) == 2) return (short) ByteOrder::bigEndianShort (temp); return 0; } int InputStream::readInt() { char temp[4]; if (read (temp, 4) == 4) return (int) ByteOrder::littleEndianInt (temp); return 0; } int InputStream::readIntBigEndian() { char temp[4]; if (read (temp, 4) == 4) return (int) ByteOrder::bigEndianInt (temp); return 0; } int InputStream::readCompressedInt() { const unsigned char sizeByte = readByte(); if (sizeByte == 0) return 0; const int numBytes = (sizeByte & 0x7f); if (numBytes > 4) { jassertfalse; // trying to read corrupt data - this method must only be used // to read data that was written by OutputStream::writeCompressedInt() return 0; } char bytes[4] = { 0, 0, 0, 0 }; if (read (bytes, numBytes) != numBytes) return 0; const int num = (int) ByteOrder::littleEndianInt (bytes); return (sizeByte >> 7) ? -num : num; } int64 InputStream::readInt64() { union { uint8 asBytes[8]; uint64 asInt64; } n; if (read (n.asBytes, 8) == 8) return (int64) ByteOrder::swapIfBigEndian (n.asInt64); return 0; } int64 InputStream::readInt64BigEndian() { union { uint8 asBytes[8]; uint64 asInt64; } n; if (read (n.asBytes, 8) == 8) return (int64) ByteOrder::swapIfLittleEndian (n.asInt64); return 0; } float InputStream::readFloat() { // the union below relies on these types being the same size... static_jassert (sizeof (int32) == sizeof (float)); union { int32 asInt; float asFloat; } n; n.asInt = (int32) readInt(); return n.asFloat; } float InputStream::readFloatBigEndian() { union { int32 asInt; float asFloat; } n; n.asInt = (int32) readIntBigEndian(); return n.asFloat; } double InputStream::readDouble() { union { int64 asInt; double asDouble; } n; n.asInt = readInt64(); return n.asDouble; } double InputStream::readDoubleBigEndian() { union { int64 asInt; double asDouble; } n; n.asInt = readInt64BigEndian(); return n.asDouble; } const String InputStream::readString() { MemoryBlock buffer (256); char* data = static_cast (buffer.getData()); size_t i = 0; while ((data[i] = readByte()) != 0) { if (++i >= buffer.getSize()) { buffer.setSize (buffer.getSize() + 512); data = static_cast (buffer.getData()); } } return String::fromUTF8 (data, (int) i); } const String InputStream::readNextLine() { MemoryBlock buffer (256); char* data = static_cast (buffer.getData()); size_t i = 0; while ((data[i] = readByte()) != 0) { if (data[i] == '\n') break; if (data[i] == '\r') { const int64 lastPos = getPosition(); if (readByte() != '\n') setPosition (lastPos); break; } if (++i >= buffer.getSize()) { buffer.setSize (buffer.getSize() + 512); data = static_cast (buffer.getData()); } } return String::fromUTF8 (data, (int) i); } int InputStream::readIntoMemoryBlock (MemoryBlock& block, int numBytes) { MemoryOutputStream mo (block, true); return mo.writeFromInputStream (*this, numBytes); } const String InputStream::readEntireStreamAsString() { MemoryOutputStream mo; mo.writeFromInputStream (*this, -1); return mo.toString(); } void InputStream::skipNextBytes (int64 numBytesToSkip) { if (numBytesToSkip > 0) { const int skipBufferSize = (int) jmin (numBytesToSkip, (int64) 16384); HeapBlock temp (skipBufferSize); while (numBytesToSkip > 0 && ! isExhausted()) numBytesToSkip -= read (temp, (int) jmin (numBytesToSkip, (int64) skipBufferSize)); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_InputStream.cpp ***/ /*** Start of inlined file: juce_OutputStream.cpp ***/ BEGIN_JUCE_NAMESPACE #if JUCE_DEBUG static Array activeStreams; void juce_CheckForDanglingStreams() { /* It's always a bad idea to leak any object, but if you're leaking output streams, then there's a good chance that you're failing to flush a file to disk properly, which could result in corrupted data and other similar nastiness.. */ jassert (activeStreams.size() == 0); }; #endif OutputStream::OutputStream() { #if JUCE_DEBUG activeStreams.add (this); #endif } OutputStream::~OutputStream() { #if JUCE_DEBUG activeStreams.removeValue (this); #endif } void OutputStream::writeBool (const bool b) { writeByte (b ? (char) 1 : (char) 0); } void OutputStream::writeByte (char byte) { write (&byte, 1); } void OutputStream::writeShort (short value) { const unsigned short v = ByteOrder::swapIfBigEndian ((unsigned short) value); write (&v, 2); } void OutputStream::writeShortBigEndian (short value) { const unsigned short v = ByteOrder::swapIfLittleEndian ((unsigned short) value); write (&v, 2); } void OutputStream::writeInt (int value) { const unsigned int v = ByteOrder::swapIfBigEndian ((unsigned int) value); write (&v, 4); } void OutputStream::writeIntBigEndian (int value) { const unsigned int v = ByteOrder::swapIfLittleEndian ((unsigned int) value); write (&v, 4); } void OutputStream::writeCompressedInt (int value) { unsigned int un = (value < 0) ? (unsigned int) -value : (unsigned int) value; uint8 data[5]; int num = 0; while (un > 0) { data[++num] = (uint8) un; un >>= 8; } data[0] = (uint8) num; if (value < 0) data[0] |= 0x80; write (data, num + 1); } void OutputStream::writeInt64 (int64 value) { const uint64 v = ByteOrder::swapIfBigEndian ((uint64) value); write (&v, 8); } void OutputStream::writeInt64BigEndian (int64 value) { const uint64 v = ByteOrder::swapIfLittleEndian ((uint64) value); write (&v, 8); } void OutputStream::writeFloat (float value) { union { int asInt; float asFloat; } n; n.asFloat = value; writeInt (n.asInt); } void OutputStream::writeFloatBigEndian (float value) { union { int asInt; float asFloat; } n; n.asFloat = value; writeIntBigEndian (n.asInt); } void OutputStream::writeDouble (double value) { union { int64 asInt; double asDouble; } n; n.asDouble = value; writeInt64 (n.asInt); } void OutputStream::writeDoubleBigEndian (double value) { union { int64 asInt; double asDouble; } n; n.asDouble = value; writeInt64BigEndian (n.asInt); } void OutputStream::writeString (const String& text) { // (This avoids using toUTF8() to prevent the memory bloat that it would leave behind // if lots of large, persistent strings were to be written to streams). const int numBytes = text.getNumBytesAsUTF8() + 1; HeapBlock temp (numBytes); text.copyToUTF8 (temp, numBytes); write (temp, numBytes); } void OutputStream::writeText (const String& text, const bool asUnicode, const bool writeUnicodeHeaderBytes) { if (asUnicode) { if (writeUnicodeHeaderBytes) write ("\x0ff\x0fe", 2); const juce_wchar* src = text; bool lastCharWasReturn = false; while (*src != 0) { if (*src == L'\n' && ! lastCharWasReturn) writeShort ((short) L'\r'); lastCharWasReturn = (*src == L'\r'); writeShort ((short) *src++); } } else { const char* src = text.toUTF8(); const char* t = src; for (;;) { if (*t == '\n') { if (t > src) write (src, (int) (t - src)); write ("\r\n", 2); src = t + 1; } else if (*t == '\r') { if (t[1] == '\n') ++t; } else if (*t == 0) { if (t > src) write (src, (int) (t - src)); break; } ++t; } } } int OutputStream::writeFromInputStream (InputStream& source, int64 numBytesToWrite) { if (numBytesToWrite < 0) numBytesToWrite = std::numeric_limits::max(); int numWritten = 0; while (numBytesToWrite > 0 && ! source.isExhausted()) { char buffer [8192]; const int num = source.read (buffer, (int) jmin (numBytesToWrite, (int64) sizeof (buffer))); if (num <= 0) break; write (buffer, num); numBytesToWrite -= num; numWritten += num; } return numWritten; } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const int number) { return stream << String (number); } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const double number) { return stream << String (number); } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const char character) { stream.writeByte (character); return stream; } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const char* const text) { stream.write (text, (int) strlen (text)); return stream; } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const MemoryBlock& data) { stream.write (data.getData(), (int) data.getSize()); return stream; } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const File& fileToRead) { const ScopedPointer in (fileToRead.createInputStream()); if (in != 0) stream.writeFromInputStream (*in, -1); return stream; } END_JUCE_NAMESPACE /*** End of inlined file: juce_OutputStream.cpp ***/ /*** Start of inlined file: juce_DirectoryIterator.cpp ***/ BEGIN_JUCE_NAMESPACE DirectoryIterator::DirectoryIterator (const File& directory, bool isRecursive_, const String& wildCard_, const int whatToLookFor_) : fileFinder (directory, isRecursive_ ? "*" : wildCard_), wildCard (wildCard_), path (File::addTrailingSeparator (directory.getFullPathName())), index (-1), totalNumFiles (-1), whatToLookFor (whatToLookFor_), isRecursive (isRecursive_) { // you have to specify the type of files you're looking for! jassert ((whatToLookFor_ & (File::findFiles | File::findDirectories)) != 0); jassert (whatToLookFor_ > 0 && whatToLookFor_ <= 7); } DirectoryIterator::~DirectoryIterator() { } bool DirectoryIterator::next() { return next (0, 0, 0, 0, 0, 0); } bool DirectoryIterator::next (bool* const isDirResult, bool* const isHiddenResult, int64* const fileSize, Time* const modTime, Time* const creationTime, bool* const isReadOnly) { if (subIterator != 0) { if (subIterator->next (isDirResult, isHiddenResult, fileSize, modTime, creationTime, isReadOnly)) return true; subIterator = 0; } String filename; bool isDirectory, isHidden; while (fileFinder.next (filename, &isDirectory, &isHidden, fileSize, modTime, creationTime, isReadOnly)) { ++index; if (! filename.containsOnly (".")) { const File fileFound (path + filename, 0); bool matches = false; if (isDirectory) { if (isRecursive && ((whatToLookFor & File::ignoreHiddenFiles) == 0 || ! isHidden)) subIterator = new DirectoryIterator (fileFound, true, wildCard, whatToLookFor); matches = (whatToLookFor & File::findDirectories) != 0; } else { matches = (whatToLookFor & File::findFiles) != 0; } // if recursive, we're not relying on the OS iterator to do the wildcard match, so do it now.. if (matches && isRecursive) matches = filename.matchesWildcard (wildCard, ! File::areFileNamesCaseSensitive()); if (matches && (whatToLookFor & File::ignoreHiddenFiles) != 0) matches = ! isHidden; if (matches) { currentFile = fileFound; if (isHiddenResult != 0) *isHiddenResult = isHidden; if (isDirResult != 0) *isDirResult = isDirectory; return true; } else if (subIterator != 0) { return next(); } } } return false; } const File DirectoryIterator::getFile() const { if (subIterator != 0) return subIterator->getFile(); return currentFile; } float DirectoryIterator::getEstimatedProgress() const { if (totalNumFiles < 0) totalNumFiles = File (path).getNumberOfChildFiles (File::findFilesAndDirectories); if (totalNumFiles <= 0) return 0.0f; const float detailedIndex = (subIterator != 0) ? index + subIterator->getEstimatedProgress() : (float) index; return detailedIndex / totalNumFiles; } END_JUCE_NAMESPACE /*** End of inlined file: juce_DirectoryIterator.cpp ***/ /*** Start of inlined file: juce_File.cpp ***/ #if ! JUCE_WINDOWS #include #endif BEGIN_JUCE_NAMESPACE File::File (const String& fullPathName) : fullPath (parseAbsolutePath (fullPathName)) { } File::File (const String& path, int) : fullPath (path) { } const File File::createFileWithoutCheckingPath (const String& path) { return File (path, 0); } File::File (const File& other) : fullPath (other.fullPath) { } File& File::operator= (const String& newPath) { fullPath = parseAbsolutePath (newPath); return *this; } File& File::operator= (const File& other) { fullPath = other.fullPath; return *this; } const File File::nonexistent; const String File::parseAbsolutePath (const String& p) { if (p.isEmpty()) return String::empty; #if JUCE_WINDOWS // Windows.. String path (p.replaceCharacter ('/', '\\')); if (path.startsWithChar (File::separator)) { if (path[1] != File::separator) { /* When you supply a raw string to the File object constructor, it must be an absolute path. If you're trying to parse a string that may be either a relative path or an absolute path, you MUST provide a context against which the partial path can be evaluated - you can do this by simply using File::getChildFile() instead of the File constructor. E.g. saying "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute path if that's what was supplied, or would evaluate a partial path relative to the CWD. */ jassertfalse; path = File::getCurrentWorkingDirectory().getFullPathName().substring (0, 2) + path; } } else if (! path.containsChar (':')) { /* When you supply a raw string to the File object constructor, it must be an absolute path. If you're trying to parse a string that may be either a relative path or an absolute path, you MUST provide a context against which the partial path can be evaluated - you can do this by simply using File::getChildFile() instead of the File constructor. E.g. saying "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute path if that's what was supplied, or would evaluate a partial path relative to the CWD. */ jassertfalse; return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName(); } #else // Mac or Linux.. String path (p.replaceCharacter ('\\', '/')); if (path.startsWithChar ('~')) { if (path[1] == File::separator || path[1] == 0) { // expand a name of the form "~/abc" path = File::getSpecialLocation (File::userHomeDirectory).getFullPathName() + path.substring (1); } else { // expand a name of type "~dave/abc" const String userName (path.substring (1).upToFirstOccurrenceOf ("/", false, false)); struct passwd* const pw = getpwnam (userName.toUTF8()); if (pw != 0) path = addTrailingSeparator (pw->pw_dir) + path.fromFirstOccurrenceOf ("/", false, false); } } else if (! path.startsWithChar (File::separator)) { /* When you supply a raw string to the File object constructor, it must be an absolute path. If you're trying to parse a string that may be either a relative path or an absolute path, you MUST provide a context against which the partial path can be evaluated - you can do this by simply using File::getChildFile() instead of the File constructor. E.g. saying "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute path if that's what was supplied, or would evaluate a partial path relative to the CWD. */ jassert (path.startsWith ("./") || path.startsWith ("../")); // (assume that a path "./xyz" is deliberately intended to be relative to the CWD) return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName(); } #endif while (path.endsWithChar (separator) && path != separatorString) // careful not to turn a single "/" into an empty string. path = path.dropLastCharacters (1); return path; } const String File::addTrailingSeparator (const String& path) { return path.endsWithChar (File::separator) ? path : path + File::separator; } #if JUCE_LINUX #define NAMES_ARE_CASE_SENSITIVE 1 #endif bool File::areFileNamesCaseSensitive() { #if NAMES_ARE_CASE_SENSITIVE return true; #else return false; #endif } bool File::operator== (const File& other) const { #if NAMES_ARE_CASE_SENSITIVE return fullPath == other.fullPath; #else return fullPath.equalsIgnoreCase (other.fullPath); #endif } bool File::operator!= (const File& other) const { return ! operator== (other); } bool File::operator< (const File& other) const { #if NAMES_ARE_CASE_SENSITIVE return fullPath < other.fullPath; #else return fullPath.compareIgnoreCase (other.fullPath) < 0; #endif } bool File::operator> (const File& other) const { #if NAMES_ARE_CASE_SENSITIVE return fullPath > other.fullPath; #else return fullPath.compareIgnoreCase (other.fullPath) > 0; #endif } bool File::setReadOnly (const bool shouldBeReadOnly, const bool applyRecursively) const { bool worked = true; if (applyRecursively && isDirectory()) { Array subFiles; findChildFiles (subFiles, File::findFilesAndDirectories, false); for (int i = subFiles.size(); --i >= 0;) worked = subFiles.getReference(i).setReadOnly (shouldBeReadOnly, true) && worked; } return setFileReadOnlyInternal (shouldBeReadOnly) && worked; } bool File::deleteRecursively() const { bool worked = true; if (isDirectory()) { Array subFiles; findChildFiles (subFiles, File::findFilesAndDirectories, false); for (int i = subFiles.size(); --i >= 0;) worked = subFiles.getReference(i).deleteRecursively() && worked; } return deleteFile() && worked; } bool File::moveFileTo (const File& newFile) const { if (newFile.fullPath == fullPath) return true; #if ! NAMES_ARE_CASE_SENSITIVE if (*this != newFile) #endif if (! newFile.deleteFile()) return false; return moveInternal (newFile); } bool File::copyFileTo (const File& newFile) const { return (*this == newFile) || (exists() && newFile.deleteFile() && copyInternal (newFile)); } bool File::copyDirectoryTo (const File& newDirectory) const { if (isDirectory() && newDirectory.createDirectory()) { Array subFiles; findChildFiles (subFiles, File::findFiles, false); int i; for (i = 0; i < subFiles.size(); ++i) if (! subFiles.getReference(i).copyFileTo (newDirectory.getChildFile (subFiles.getReference(i).getFileName()))) return false; subFiles.clear(); findChildFiles (subFiles, File::findDirectories, false); for (i = 0; i < subFiles.size(); ++i) if (! subFiles.getReference(i).copyDirectoryTo (newDirectory.getChildFile (subFiles.getReference(i).getFileName()))) return false; return true; } return false; } const String File::getPathUpToLastSlash() const { const int lastSlash = fullPath.lastIndexOfChar (separator); if (lastSlash > 0) return fullPath.substring (0, lastSlash); else if (lastSlash == 0) return separatorString; else return fullPath; } const File File::getParentDirectory() const { return File (getPathUpToLastSlash(), (int) 0); } const String File::getFileName() const { return fullPath.substring (fullPath.lastIndexOfChar (separator) + 1); } int File::hashCode() const { return fullPath.hashCode(); } int64 File::hashCode64() const { return fullPath.hashCode64(); } const String File::getFileNameWithoutExtension() const { const int lastSlash = fullPath.lastIndexOfChar (separator) + 1; const int lastDot = fullPath.lastIndexOfChar ('.'); if (lastDot > lastSlash) return fullPath.substring (lastSlash, lastDot); else return fullPath.substring (lastSlash); } bool File::isAChildOf (const File& potentialParent) const { if (potentialParent == File::nonexistent) return false; const String ourPath (getPathUpToLastSlash()); #if NAMES_ARE_CASE_SENSITIVE if (potentialParent.fullPath == ourPath) #else if (potentialParent.fullPath.equalsIgnoreCase (ourPath)) #endif { return true; } else if (potentialParent.fullPath.length() >= ourPath.length()) { return false; } else { return getParentDirectory().isAChildOf (potentialParent); } } bool File::isAbsolutePath (const String& path) { return path.startsWithChar ('/') || path.startsWithChar ('\\') #if JUCE_WINDOWS || (path.isNotEmpty() && path[1] == ':'); #else || path.startsWithChar ('~'); #endif } const File File::getChildFile (String relativePath) const { if (isAbsolutePath (relativePath)) { // the path is really absolute.. return File (relativePath); } else { // it's relative, so remove any ../ or ./ bits at the start. String path (fullPath); if (relativePath[0] == '.') { #if JUCE_WINDOWS relativePath = relativePath.replaceCharacter ('/', '\\').trimStart(); #else relativePath = relativePath.replaceCharacter ('\\', '/').trimStart(); #endif while (relativePath[0] == '.') { if (relativePath[1] == '.') { if (relativePath [2] == 0 || relativePath[2] == separator) { const int lastSlash = path.lastIndexOfChar (separator); if (lastSlash >= 0) path = path.substring (0, lastSlash); relativePath = relativePath.substring (3); } else { break; } } else if (relativePath[1] == separator) { relativePath = relativePath.substring (2); } else { break; } } } return File (addTrailingSeparator (path) + relativePath); } } const File File::getSiblingFile (const String& fileName) const { return getParentDirectory().getChildFile (fileName); } const String File::descriptionOfSizeInBytes (const int64 bytes) { if (bytes == 1) { return "1 byte"; } else if (bytes < 1024) { return String ((int) bytes) + " bytes"; } else if (bytes < 1024 * 1024) { return String (bytes / 1024.0, 1) + " KB"; } else if (bytes < 1024 * 1024 * 1024) { return String (bytes / (1024.0 * 1024.0), 1) + " MB"; } else { return String (bytes / (1024.0 * 1024.0 * 1024.0), 1) + " GB"; } } bool File::create() const { if (exists()) return true; { const File parentDir (getParentDirectory()); if (parentDir == *this || ! parentDir.createDirectory()) return false; FileOutputStream fo (*this, 8); } return exists(); } bool File::createDirectory() const { if (! isDirectory()) { const File parentDir (getParentDirectory()); if (parentDir == *this || ! parentDir.createDirectory()) return false; createDirectoryInternal (fullPath.trimCharactersAtEnd (separatorString)); return isDirectory(); } return true; } const Time File::getCreationTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (c); } const Time File::getLastModificationTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (m); } const Time File::getLastAccessTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (a); } bool File::setLastModificationTime (const Time& t) const { return setFileTimesInternal (t.toMilliseconds(), 0, 0); } bool File::setLastAccessTime (const Time& t) const { return setFileTimesInternal (0, t.toMilliseconds(), 0); } bool File::setCreationTime (const Time& t) const { return setFileTimesInternal (0, 0, t.toMilliseconds()); } bool File::loadFileAsData (MemoryBlock& destBlock) const { if (! existsAsFile()) return false; FileInputStream in (*this); return getSize() == in.readIntoMemoryBlock (destBlock); } const String File::loadFileAsString() const { if (! existsAsFile()) return String::empty; FileInputStream in (*this); return in.readEntireStreamAsString(); } int File::findChildFiles (Array& results, const int whatToLookFor, const bool searchRecursively, const String& wildCardPattern) const { DirectoryIterator di (*this, searchRecursively, wildCardPattern, whatToLookFor); int total = 0; while (di.next()) { results.add (di.getFile()); ++total; } return total; } int File::getNumberOfChildFiles (const int whatToLookFor, const String& wildCardPattern) const { DirectoryIterator di (*this, false, "*", whatToLookFor); int total = 0; while (di.next()) ++total; return total; } bool File::containsSubDirectories() const { if (isDirectory()) { DirectoryIterator di (*this, false, "*", findDirectories); return di.next(); } return false; } const File File::getNonexistentChildFile (const String& prefix_, const String& suffix, bool putNumbersInBrackets) const { File f (getChildFile (prefix_ + suffix)); if (f.exists()) { int num = 2; String prefix (prefix_); // remove any bracketed numbers that may already be on the end.. if (prefix.trim().endsWithChar (')')) { putNumbersInBrackets = true; const int openBracks = prefix.lastIndexOfChar ('('); const int closeBracks = prefix.lastIndexOfChar (')'); if (openBracks > 0 && closeBracks > openBracks && prefix.substring (openBracks + 1, closeBracks).containsOnly ("0123456789")) { num = prefix.substring (openBracks + 1, closeBracks).getIntValue() + 1; prefix = prefix.substring (0, openBracks); } } // also use brackets if it ends in a digit. putNumbersInBrackets = putNumbersInBrackets || CharacterFunctions::isDigit (prefix.getLastCharacter()); do { if (putNumbersInBrackets) f = getChildFile (prefix + '(' + String (num++) + ')' + suffix); else f = getChildFile (prefix + String (num++) + suffix); } while (f.exists()); } return f; } const File File::getNonexistentSibling (const bool putNumbersInBrackets) const { if (exists()) { return getParentDirectory() .getNonexistentChildFile (getFileNameWithoutExtension(), getFileExtension(), putNumbersInBrackets); } else { return *this; } } const String File::getFileExtension() const { String ext; if (! isDirectory()) { const int indexOfDot = fullPath.lastIndexOfChar ('.'); if (indexOfDot > fullPath.lastIndexOfChar (separator)) ext = fullPath.substring (indexOfDot); } return ext; } bool File::hasFileExtension (const String& possibleSuffix) const { if (possibleSuffix.isEmpty()) return fullPath.lastIndexOfChar ('.') <= fullPath.lastIndexOfChar (separator); const int semicolon = possibleSuffix.indexOfChar (0, ';'); if (semicolon >= 0) { return hasFileExtension (possibleSuffix.substring (0, semicolon).trimEnd()) || hasFileExtension (possibleSuffix.substring (semicolon + 1).trimStart()); } else { if (fullPath.endsWithIgnoreCase (possibleSuffix)) { if (possibleSuffix.startsWithChar ('.')) return true; const int dotPos = fullPath.length() - possibleSuffix.length() - 1; if (dotPos >= 0) return fullPath [dotPos] == '.'; } } return false; } const File File::withFileExtension (const String& newExtension) const { if (fullPath.isEmpty()) return File::nonexistent; String filePart (getFileName()); int i = filePart.lastIndexOfChar ('.'); if (i >= 0) filePart = filePart.substring (0, i); if (newExtension.isNotEmpty() && ! newExtension.startsWithChar ('.')) filePart << '.'; return getSiblingFile (filePart + newExtension); } bool File::startAsProcess (const String& parameters) const { return exists() && PlatformUtilities::openDocument (fullPath, parameters); } FileInputStream* File::createInputStream() const { if (existsAsFile()) return new FileInputStream (*this); return 0; } FileOutputStream* File::createOutputStream (const int bufferSize) const { ScopedPointer out (new FileOutputStream (*this, bufferSize)); if (out->failedToOpen()) return 0; return out.release(); } bool File::appendData (const void* const dataToAppend, const int numberOfBytes) const { if (numberOfBytes > 0) { const ScopedPointer out (createOutputStream()); if (out == 0) return false; out->write (dataToAppend, numberOfBytes); } return true; } bool File::replaceWithData (const void* const dataToWrite, const int numberOfBytes) const { jassert (numberOfBytes >= 0); // a negative number of bytes?? if (numberOfBytes <= 0) return deleteFile(); TemporaryFile tempFile (*this, TemporaryFile::useHiddenFile); tempFile.getFile().appendData (dataToWrite, numberOfBytes); return tempFile.overwriteTargetFileWithTemporary(); } bool File::appendText (const String& text, const bool asUnicode, const bool writeUnicodeHeaderBytes) const { const ScopedPointer out (createOutputStream()); if (out != 0) { out->writeText (text, asUnicode, writeUnicodeHeaderBytes); return true; } return false; } bool File::replaceWithText (const String& textToWrite, const bool asUnicode, const bool writeUnicodeHeaderBytes) const { TemporaryFile tempFile (*this, TemporaryFile::useHiddenFile); tempFile.getFile().appendText (textToWrite, asUnicode, writeUnicodeHeaderBytes); return tempFile.overwriteTargetFileWithTemporary(); } bool File::hasIdenticalContentTo (const File& other) const { if (other == *this) return true; if (getSize() == other.getSize() && existsAsFile() && other.existsAsFile()) { FileInputStream in1 (*this), in2 (other); const int bufferSize = 4096; HeapBlock buffer1, buffer2; buffer1.malloc (bufferSize); buffer2.malloc (bufferSize); for (;;) { const int num1 = in1.read (buffer1, bufferSize); const int num2 = in2.read (buffer2, bufferSize); if (num1 != num2) break; if (num1 <= 0) return true; if (memcmp (buffer1, buffer2, num1) != 0) break; } } return false; } const String File::createLegalPathName (const String& original) { String s (original); String start; if (s[1] == ':') { start = s.substring (0, 2); s = s.substring (2); } return start + s.removeCharacters ("\"#@,;:<>*^|?") .substring (0, 1024); } const String File::createLegalFileName (const String& original) { String s (original.removeCharacters ("\"#@,;:<>*^|?\\/")); const int maxLength = 128; // only the length of the filename, not the whole path const int len = s.length(); if (len > maxLength) { const int lastDot = s.lastIndexOfChar ('.'); if (lastDot > jmax (0, len - 12)) { s = s.substring (0, maxLength - (len - lastDot)) + s.substring (lastDot); } else { s = s.substring (0, maxLength); } } return s; } const String File::getRelativePathFrom (const File& dir) const { String thisPath (fullPath); { int len = thisPath.length(); while (--len >= 0 && thisPath [len] == File::separator) thisPath [len] = 0; } String dirPath (addTrailingSeparator (dir.existsAsFile() ? dir.getParentDirectory().getFullPathName() : dir.fullPath)); const int len = jmin (thisPath.length(), dirPath.length()); int commonBitLength = 0; for (int i = 0; i < len; ++i) { #if NAMES_ARE_CASE_SENSITIVE if (thisPath[i] != dirPath[i]) #else if (CharacterFunctions::toLowerCase (thisPath[i]) != CharacterFunctions::toLowerCase (dirPath[i])) #endif { break; } ++commonBitLength; } while (commonBitLength > 0 && thisPath [commonBitLength - 1] != File::separator) --commonBitLength; // if the only common bit is the root, then just return the full path.. if (commonBitLength <= 0 || (commonBitLength == 1 && thisPath [1] == File::separator)) return fullPath; thisPath = thisPath.substring (commonBitLength); dirPath = dirPath.substring (commonBitLength); while (dirPath.isNotEmpty()) { #if JUCE_WINDOWS thisPath = "..\\" + thisPath; #else thisPath = "../" + thisPath; #endif const int sep = dirPath.indexOfChar (separator); if (sep >= 0) dirPath = dirPath.substring (sep + 1); else dirPath = String::empty; } return thisPath; } const File File::createTempFile (const String& fileNameEnding) { const File tempFile (getSpecialLocation (tempDirectory) .getChildFile ("temp_" + String (Random::getSystemRandom().nextInt())) .withFileExtension (fileNameEnding)); if (tempFile.exists()) return createTempFile (fileNameEnding); else return tempFile; } END_JUCE_NAMESPACE /*** End of inlined file: juce_File.cpp ***/ /*** Start of inlined file: juce_FileInputStream.cpp ***/ BEGIN_JUCE_NAMESPACE int64 juce_fileSetPosition (void* handle, int64 pos); FileInputStream::FileInputStream (const File& f) : file (f), fileHandle (0), currentPosition (0), totalSize (0), needToSeek (true) { openHandle(); } FileInputStream::~FileInputStream() { closeHandle(); } int64 FileInputStream::getTotalLength() { return totalSize; } int FileInputStream::read (void* buffer, int bytesToRead) { int num = 0; if (needToSeek) { if (juce_fileSetPosition (fileHandle, currentPosition) < 0) return 0; needToSeek = false; } num = readInternal (buffer, bytesToRead); currentPosition += num; return num; } bool FileInputStream::isExhausted() { return currentPosition >= totalSize; } int64 FileInputStream::getPosition() { return currentPosition; } bool FileInputStream::setPosition (int64 pos) { pos = jlimit ((int64) 0, totalSize, pos); needToSeek |= (currentPosition != pos); currentPosition = pos; return true; } END_JUCE_NAMESPACE /*** End of inlined file: juce_FileInputStream.cpp ***/ /*** Start of inlined file: juce_FileOutputStream.cpp ***/ BEGIN_JUCE_NAMESPACE int64 juce_fileSetPosition (void* handle, int64 pos); FileOutputStream::FileOutputStream (const File& f, const int bufferSize_) : file (f), fileHandle (0), currentPosition (0), bufferSize (bufferSize_), bytesInBuffer (0), buffer (jmax (bufferSize_, 16)) { openHandle(); } FileOutputStream::~FileOutputStream() { flush(); closeHandle(); } int64 FileOutputStream::getPosition() { return currentPosition; } bool FileOutputStream::setPosition (int64 newPosition) { if (newPosition != currentPosition) { flush(); currentPosition = juce_fileSetPosition (fileHandle, newPosition); } return newPosition == currentPosition; } void FileOutputStream::flush() { if (bytesInBuffer > 0) { writeInternal (buffer, bytesInBuffer); bytesInBuffer = 0; } flushInternal(); } bool FileOutputStream::write (const void* const src, const int numBytes) { if (bytesInBuffer + numBytes < bufferSize) { memcpy (buffer + bytesInBuffer, src, numBytes); bytesInBuffer += numBytes; currentPosition += numBytes; } else { if (bytesInBuffer > 0) { // flush the reservoir const bool wroteOk = (writeInternal (buffer, bytesInBuffer) == bytesInBuffer); bytesInBuffer = 0; if (! wroteOk) return false; } if (numBytes < bufferSize) { memcpy (buffer + bytesInBuffer, src, numBytes); bytesInBuffer += numBytes; currentPosition += numBytes; } else { const int bytesWritten = writeInternal (src, numBytes); if (bytesWritten < 0) return false; currentPosition += bytesWritten; return bytesWritten == numBytes; } } return true; } END_JUCE_NAMESPACE /*** End of inlined file: juce_FileOutputStream.cpp ***/ /*** Start of inlined file: juce_FileSearchPath.cpp ***/ BEGIN_JUCE_NAMESPACE FileSearchPath::FileSearchPath() { } FileSearchPath::FileSearchPath (const String& path) { init (path); } FileSearchPath::FileSearchPath (const FileSearchPath& other) : directories (other.directories) { } FileSearchPath::~FileSearchPath() { } FileSearchPath& FileSearchPath::operator= (const String& path) { init (path); return *this; } void FileSearchPath::init (const String& path) { directories.clear(); directories.addTokens (path, ";", "\""); directories.trim(); directories.removeEmptyStrings(); for (int i = directories.size(); --i >= 0;) directories.set (i, directories[i].unquoted()); } int FileSearchPath::getNumPaths() const { return directories.size(); } const File FileSearchPath::operator[] (const int index) const { return File (directories [index]); } const String FileSearchPath::toString() const { StringArray directories2 (directories); for (int i = directories2.size(); --i >= 0;) if (directories2[i].containsChar (';')) directories2.set (i, directories2[i].quoted()); return directories2.joinIntoString (";"); } void FileSearchPath::add (const File& dir, const int insertIndex) { directories.insert (insertIndex, dir.getFullPathName()); } void FileSearchPath::addIfNotAlreadyThere (const File& dir) { for (int i = 0; i < directories.size(); ++i) if (File (directories[i]) == dir) return; add (dir); } void FileSearchPath::remove (const int index) { directories.remove (index); } void FileSearchPath::addPath (const FileSearchPath& other) { for (int i = 0; i < other.getNumPaths(); ++i) addIfNotAlreadyThere (other[i]); } void FileSearchPath::removeRedundantPaths() { for (int i = directories.size(); --i >= 0;) { const File d1 (directories[i]); for (int j = directories.size(); --j >= 0;) { const File d2 (directories[j]); if ((i != j) && (d1.isAChildOf (d2) || d1 == d2)) { directories.remove (i); break; } } } } void FileSearchPath::removeNonExistentPaths() { for (int i = directories.size(); --i >= 0;) if (! File (directories[i]).isDirectory()) directories.remove (i); } int FileSearchPath::findChildFiles (Array& results, const int whatToLookFor, const bool searchRecursively, const String& wildCardPattern) const { int total = 0; for (int i = 0; i < directories.size(); ++i) total += operator[] (i).findChildFiles (results, whatToLookFor, searchRecursively, wildCardPattern); return total; } bool FileSearchPath::isFileInPath (const File& fileToCheck, const bool checkRecursively) const { for (int i = directories.size(); --i >= 0;) { const File d (directories[i]); if (checkRecursively) { if (fileToCheck.isAChildOf (d)) return true; } else { if (fileToCheck.getParentDirectory() == d) return true; } } return false; } END_JUCE_NAMESPACE /*** End of inlined file: juce_FileSearchPath.cpp ***/ /*** Start of inlined file: juce_NamedPipe.cpp ***/ BEGIN_JUCE_NAMESPACE NamedPipe::NamedPipe() : internal (0) { } NamedPipe::~NamedPipe() { close(); } bool NamedPipe::openExisting (const String& pipeName) { currentPipeName = pipeName; return openInternal (pipeName, false); } bool NamedPipe::createNewPipe (const String& pipeName) { currentPipeName = pipeName; return openInternal (pipeName, true); } bool NamedPipe::isOpen() const { return internal != 0; } const String NamedPipe::getName() const { return currentPipeName; } // other methods for this class are implemented in the platform-specific files END_JUCE_NAMESPACE /*** End of inlined file: juce_NamedPipe.cpp ***/ /*** Start of inlined file: juce_TemporaryFile.cpp ***/ BEGIN_JUCE_NAMESPACE TemporaryFile::TemporaryFile (const String& suffix, const int optionFlags) { createTempFile (File::getSpecialLocation (File::tempDirectory), "temp_" + String (Random::getSystemRandom().nextInt()), suffix, optionFlags); } TemporaryFile::TemporaryFile (const File& targetFile_, const int optionFlags) : targetFile (targetFile_) { // If you use this constructor, you need to give it a valid target file! jassert (targetFile != File::nonexistent); createTempFile (targetFile.getParentDirectory(), targetFile.getFileNameWithoutExtension() + "_temp" + String (Random::getSystemRandom().nextInt()), targetFile.getFileExtension(), optionFlags); } void TemporaryFile::createTempFile (const File& parentDirectory, String name, const String& suffix, const int optionFlags) { if ((optionFlags & useHiddenFile) != 0) name = "." + name; temporaryFile = parentDirectory.getNonexistentChildFile (name, suffix, (optionFlags & putNumbersInBrackets) != 0); } TemporaryFile::~TemporaryFile() { if (! deleteTemporaryFile()) { /* Failed to delete our temporary file! The most likely reason for this would be that you've not closed an output stream that was being used to write to file. If you find that something beyond your control is changing permissions on your temporary files and preventing them from being deleted, you may want to call TemporaryFile::deleteTemporaryFile() to detect those error cases and handle them appropriately. */ jassertfalse; } } bool TemporaryFile::overwriteTargetFileWithTemporary() const { // This method only works if you created this object with the constructor // that takes a target file! jassert (targetFile != File::nonexistent); if (temporaryFile.exists()) { // Have a few attempts at overwriting the file before giving up.. for (int i = 5; --i >= 0;) { if (temporaryFile.moveFileTo (targetFile)) return true; Thread::sleep (100); } } else { // There's no temporary file to use. If your write failed, you should // probably check, and not bother calling this method. jassertfalse; } return false; } bool TemporaryFile::deleteTemporaryFile() const { // Have a few attempts at deleting the file before giving up.. for (int i = 5; --i >= 0;) { if (temporaryFile.deleteFile()) return true; Thread::sleep (50); } return false; } END_JUCE_NAMESPACE /*** End of inlined file: juce_TemporaryFile.cpp ***/ /*** Start of inlined file: juce_Socket.cpp ***/ #if JUCE_WINDOWS #include #if JUCE_MSVC #pragma warning (push) #pragma warning (disable : 4127 4389 4018) #endif #else #if JUCE_LINUX #include #include #include #include #include #elif (MACOSX_DEPLOYMENT_TARGET <= MAC_OS_X_VERSION_10_4) && ! JUCE_IOS #include #endif #include #include #include #include #endif BEGIN_JUCE_NAMESPACE #if defined (JUCE_LINUX) || defined (JUCE_MAC) || defined (JUCE_IOS) typedef socklen_t juce_socklen_t; #else typedef int juce_socklen_t; #endif #if JUCE_WINDOWS namespace SocketHelpers { typedef int (__stdcall juce_CloseWin32SocketLibCall) (void); static juce_CloseWin32SocketLibCall* juce_CloseWin32SocketLib = 0; } static void initWin32Sockets() { static CriticalSection lock; const ScopedLock sl (lock); if (SocketHelpers::juce_CloseWin32SocketLib == 0) { WSADATA wsaData; const WORD wVersionRequested = MAKEWORD (1, 1); WSAStartup (wVersionRequested, &wsaData); SocketHelpers::juce_CloseWin32SocketLib = &WSACleanup; } } void juce_shutdownWin32Sockets() { if (SocketHelpers::juce_CloseWin32SocketLib != 0) (*SocketHelpers::juce_CloseWin32SocketLib)(); } #endif namespace SocketHelpers { static bool resetSocketOptions (const int handle, const bool isDatagram, const bool allowBroadcast) throw() { const int sndBufSize = 65536; const int rcvBufSize = 65536; const int one = 1; return handle > 0 && setsockopt (handle, SOL_SOCKET, SO_RCVBUF, (const char*) &rcvBufSize, sizeof (rcvBufSize)) == 0 && setsockopt (handle, SOL_SOCKET, SO_SNDBUF, (const char*) &sndBufSize, sizeof (sndBufSize)) == 0 && (isDatagram ? ((! allowBroadcast) || setsockopt (handle, SOL_SOCKET, SO_BROADCAST, (const char*) &one, sizeof (one)) == 0) : (setsockopt (handle, IPPROTO_TCP, TCP_NODELAY, (const char*) &one, sizeof (one)) == 0)); } static bool bindSocketToPort (const int handle, const int port) throw() { if (handle <= 0 || port <= 0) return false; struct sockaddr_in servTmpAddr; zerostruct (servTmpAddr); servTmpAddr.sin_family = PF_INET; servTmpAddr.sin_addr.s_addr = htonl (INADDR_ANY); servTmpAddr.sin_port = htons ((uint16) port); return bind (handle, (struct sockaddr*) &servTmpAddr, sizeof (struct sockaddr_in)) >= 0; } static int readSocket (const int handle, void* const destBuffer, const int maxBytesToRead, bool volatile& connected, const bool blockUntilSpecifiedAmountHasArrived) throw() { int bytesRead = 0; while (bytesRead < maxBytesToRead) { int bytesThisTime; #if JUCE_WINDOWS bytesThisTime = recv (handle, static_cast (destBuffer) + bytesRead, maxBytesToRead - bytesRead, 0); #else while ((bytesThisTime = (int) ::read (handle, addBytesToPointer (destBuffer, bytesRead), maxBytesToRead - bytesRead)) < 0 && errno == EINTR && connected) { } #endif if (bytesThisTime <= 0 || ! connected) { if (bytesRead == 0) bytesRead = -1; break; } bytesRead += bytesThisTime; if (! blockUntilSpecifiedAmountHasArrived) break; } return bytesRead; } static int waitForReadiness (const int handle, const bool forReading, const int timeoutMsecs) throw() { struct timeval timeout; struct timeval* timeoutp; if (timeoutMsecs >= 0) { timeout.tv_sec = timeoutMsecs / 1000; timeout.tv_usec = (timeoutMsecs % 1000) * 1000; timeoutp = &timeout; } else { timeoutp = 0; } fd_set rset, wset; FD_ZERO (&rset); FD_SET (handle, &rset); FD_ZERO (&wset); FD_SET (handle, &wset); fd_set* const prset = forReading ? &rset : 0; fd_set* const pwset = forReading ? 0 : &wset; #if JUCE_WINDOWS if (select (handle + 1, prset, pwset, 0, timeoutp) < 0) return -1; #else { int result; while ((result = select (handle + 1, prset, pwset, 0, timeoutp)) < 0 && errno == EINTR) { } if (result < 0) return -1; } #endif { int opt; juce_socklen_t len = sizeof (opt); if (getsockopt (handle, SOL_SOCKET, SO_ERROR, (char*) &opt, &len) < 0 || opt != 0) return -1; } if ((forReading && FD_ISSET (handle, &rset)) || ((! forReading) && FD_ISSET (handle, &wset))) return 1; return 0; } static bool setSocketBlockingState (const int handle, const bool shouldBlock) throw() { #if JUCE_WINDOWS u_long nonBlocking = shouldBlock ? 0 : 1; if (ioctlsocket (handle, FIONBIO, &nonBlocking) != 0) return false; #else int socketFlags = fcntl (handle, F_GETFL, 0); if (socketFlags == -1) return false; if (shouldBlock) socketFlags &= ~O_NONBLOCK; else socketFlags |= O_NONBLOCK; if (fcntl (handle, F_SETFL, socketFlags) != 0) return false; #endif return true; } static bool connectSocket (int volatile& handle, const bool isDatagram, void** serverAddress, const String& hostName, const int portNumber, const int timeOutMillisecs) throw() { struct hostent* const hostEnt = gethostbyname (hostName.toUTF8()); if (hostEnt == 0) return false; struct in_addr targetAddress; memcpy (&targetAddress.s_addr, *(hostEnt->h_addr_list), sizeof (targetAddress.s_addr)); struct sockaddr_in servTmpAddr; zerostruct (servTmpAddr); servTmpAddr.sin_family = PF_INET; servTmpAddr.sin_addr = targetAddress; servTmpAddr.sin_port = htons ((uint16) portNumber); if (handle < 0) handle = (int) socket (AF_INET, isDatagram ? SOCK_DGRAM : SOCK_STREAM, 0); if (handle < 0) return false; if (isDatagram) { *serverAddress = new struct sockaddr_in(); *((struct sockaddr_in*) *serverAddress) = servTmpAddr; return true; } setSocketBlockingState (handle, false); const int result = ::connect (handle, (struct sockaddr*) &servTmpAddr, sizeof (struct sockaddr_in)); if (result < 0) { #if JUCE_WINDOWS if (result == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK) #else if (errno == EINPROGRESS) #endif { if (waitForReadiness (handle, false, timeOutMillisecs) != 1) { setSocketBlockingState (handle, true); return false; } } } setSocketBlockingState (handle, true); resetSocketOptions (handle, false, false); return true; } } StreamingSocket::StreamingSocket() : portNumber (0), handle (-1), connected (false), isListener (false) { #if JUCE_WINDOWS initWin32Sockets(); #endif } StreamingSocket::StreamingSocket (const String& hostName_, const int portNumber_, const int handle_) : hostName (hostName_), portNumber (portNumber_), handle (handle_), connected (true), isListener (false) { #if JUCE_WINDOWS initWin32Sockets(); #endif SocketHelpers::resetSocketOptions (handle_, false, false); } StreamingSocket::~StreamingSocket() { close(); } int StreamingSocket::read (void* destBuffer, const int maxBytesToRead, const bool blockUntilSpecifiedAmountHasArrived) { return (connected && ! isListener) ? SocketHelpers::readSocket (handle, destBuffer, maxBytesToRead, connected, blockUntilSpecifiedAmountHasArrived) : -1; } int StreamingSocket::write (const void* sourceBuffer, const int numBytesToWrite) { if (isListener || ! connected) return -1; #if JUCE_WINDOWS return send (handle, (const char*) sourceBuffer, numBytesToWrite, 0); #else int result; while ((result = (int) ::write (handle, sourceBuffer, numBytesToWrite)) < 0 && errno == EINTR) { } return result; #endif } int StreamingSocket::waitUntilReady (const bool readyForReading, const int timeoutMsecs) const { return connected ? SocketHelpers::waitForReadiness (handle, readyForReading, timeoutMsecs) : -1; } bool StreamingSocket::bindToPort (const int port) { return SocketHelpers::bindSocketToPort (handle, port); } bool StreamingSocket::connect (const String& remoteHostName, const int remotePortNumber, const int timeOutMillisecs) { if (isListener) { jassertfalse; // a listener socket can't connect to another one! return false; } if (connected) close(); hostName = remoteHostName; portNumber = remotePortNumber; isListener = false; connected = SocketHelpers::connectSocket (handle, false, 0, remoteHostName, remotePortNumber, timeOutMillisecs); if (! (connected && SocketHelpers::resetSocketOptions (handle, false, false))) { close(); return false; } return true; } void StreamingSocket::close() { #if JUCE_WINDOWS if (handle != SOCKET_ERROR || connected) closesocket (handle); connected = false; #else if (connected) { connected = false; if (isListener) { // need to do this to interrupt the accept() function.. StreamingSocket temp; temp.connect ("localhost", portNumber, 1000); } } if (handle != -1) ::close (handle); #endif hostName = String::empty; portNumber = 0; handle = -1; isListener = false; } bool StreamingSocket::createListener (const int newPortNumber, const String& localHostName) { if (connected) close(); hostName = "listener"; portNumber = newPortNumber; isListener = true; struct sockaddr_in servTmpAddr; zerostruct (servTmpAddr); servTmpAddr.sin_family = PF_INET; servTmpAddr.sin_addr.s_addr = htonl (INADDR_ANY); if (localHostName.isNotEmpty()) servTmpAddr.sin_addr.s_addr = ::inet_addr (localHostName.toUTF8()); servTmpAddr.sin_port = htons ((uint16) portNumber); handle = (int) socket (AF_INET, SOCK_STREAM, 0); if (handle < 0) return false; const int reuse = 1; setsockopt (handle, SOL_SOCKET, SO_REUSEADDR, (const char*) &reuse, sizeof (reuse)); if (bind (handle, (struct sockaddr*) &servTmpAddr, sizeof (struct sockaddr_in)) < 0 || listen (handle, SOMAXCONN) < 0) { close(); return false; } connected = true; return true; } StreamingSocket* StreamingSocket::waitForNextConnection() const { jassert (isListener || ! connected); // to call this method, you first have to use createListener() to // prepare this socket as a listener. if (connected && isListener) { struct sockaddr address; juce_socklen_t len = sizeof (sockaddr); const int newSocket = (int) accept (handle, &address, &len); if (newSocket >= 0 && connected) return new StreamingSocket (inet_ntoa (((struct sockaddr_in*) &address)->sin_addr), portNumber, newSocket); } return 0; } bool StreamingSocket::isLocal() const throw() { return hostName == "127.0.0.1"; } DatagramSocket::DatagramSocket (const int localPortNumber, const bool allowBroadcast_) : portNumber (0), handle (-1), connected (true), allowBroadcast (allowBroadcast_), serverAddress (0) { #if JUCE_WINDOWS initWin32Sockets(); #endif handle = (int) socket (AF_INET, SOCK_DGRAM, 0); bindToPort (localPortNumber); } DatagramSocket::DatagramSocket (const String& hostName_, const int portNumber_, const int handle_, const int localPortNumber) : hostName (hostName_), portNumber (portNumber_), handle (handle_), connected (true), allowBroadcast (false), serverAddress (0) { #if JUCE_WINDOWS initWin32Sockets(); #endif SocketHelpers::resetSocketOptions (handle_, true, allowBroadcast); bindToPort (localPortNumber); } DatagramSocket::~DatagramSocket() { close(); delete static_cast (serverAddress); serverAddress = 0; } void DatagramSocket::close() { #if JUCE_WINDOWS closesocket (handle); connected = false; #else connected = false; ::close (handle); #endif hostName = String::empty; portNumber = 0; handle = -1; } bool DatagramSocket::bindToPort (const int port) { return SocketHelpers::bindSocketToPort (handle, port); } bool DatagramSocket::connect (const String& remoteHostName, const int remotePortNumber, const int timeOutMillisecs) { if (connected) close(); hostName = remoteHostName; portNumber = remotePortNumber; connected = SocketHelpers::connectSocket (handle, true, &serverAddress, remoteHostName, remotePortNumber, timeOutMillisecs); if (! (connected && SocketHelpers::resetSocketOptions (handle, true, allowBroadcast))) { close(); return false; } return true; } DatagramSocket* DatagramSocket::waitForNextConnection() const { struct sockaddr address; juce_socklen_t len = sizeof (sockaddr); while (waitUntilReady (true, -1) == 1) { char buf[1]; if (recvfrom (handle, buf, 0, 0, &address, &len) > 0) { return new DatagramSocket (inet_ntoa (((struct sockaddr_in*) &address)->sin_addr), ntohs (((struct sockaddr_in*) &address)->sin_port), -1, -1); } } return 0; } int DatagramSocket::waitUntilReady (const bool readyForReading, const int timeoutMsecs) const { return connected ? SocketHelpers::waitForReadiness (handle, readyForReading, timeoutMsecs) : -1; } int DatagramSocket::read (void* destBuffer, const int maxBytesToRead, const bool blockUntilSpecifiedAmountHasArrived) { return connected ? SocketHelpers::readSocket (handle, destBuffer, maxBytesToRead, connected, blockUntilSpecifiedAmountHasArrived) : -1; } int DatagramSocket::write (const void* sourceBuffer, const int numBytesToWrite) { // You need to call connect() first to set the server address.. jassert (serverAddress != 0 && connected); return connected ? (int) sendto (handle, (const char*) sourceBuffer, numBytesToWrite, 0, (const struct sockaddr*) serverAddress, sizeof (struct sockaddr_in)) : -1; } bool DatagramSocket::isLocal() const throw() { return hostName == "127.0.0.1"; } #if JUCE_MSVC #pragma warning (pop) #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_Socket.cpp ***/ /*** Start of inlined file: juce_URL.cpp ***/ BEGIN_JUCE_NAMESPACE URL::URL() { } URL::URL (const String& url_) : url (url_) { int i = url.indexOfChar ('?'); if (i >= 0) { do { const int nextAmp = url.indexOfChar (i + 1, '&'); const int equalsPos = url.indexOfChar (i + 1, '='); if (equalsPos > i + 1) { if (nextAmp < 0) { parameters.set (removeEscapeChars (url.substring (i + 1, equalsPos)), removeEscapeChars (url.substring (equalsPos + 1))); } else if (nextAmp > 0 && equalsPos < nextAmp) { parameters.set (removeEscapeChars (url.substring (i + 1, equalsPos)), removeEscapeChars (url.substring (equalsPos + 1, nextAmp))); } } i = nextAmp; } while (i >= 0); url = url.upToFirstOccurrenceOf ("?", false, false); } } URL::URL (const URL& other) : url (other.url), postData (other.postData), parameters (other.parameters), filesToUpload (other.filesToUpload), mimeTypes (other.mimeTypes) { } URL& URL::operator= (const URL& other) { url = other.url; postData = other.postData; parameters = other.parameters; filesToUpload = other.filesToUpload; mimeTypes = other.mimeTypes; return *this; } URL::~URL() { } static const String getMangledParameters (const StringPairArray& parameters) { String p; for (int i = 0; i < parameters.size(); ++i) { if (i > 0) p << '&'; p << URL::addEscapeChars (parameters.getAllKeys() [i], true) << '=' << URL::addEscapeChars (parameters.getAllValues() [i], true); } return p; } const String URL::toString (const bool includeGetParameters) const { if (includeGetParameters && parameters.size() > 0) return url + "?" + getMangledParameters (parameters); else return url; } bool URL::isWellFormed() const { //xxx TODO return url.isNotEmpty(); } static int findStartOfDomain (const String& url) { int i = 0; while (CharacterFunctions::isLetterOrDigit (url[i]) || CharacterFunctions::indexOfChar (L"+-.", url[i], false) >= 0) ++i; return url[i] == ':' ? i + 1 : 0; } const String URL::getDomain() const { int start = findStartOfDomain (url); while (url[start] == '/') ++start; const int end1 = url.indexOfChar (start, '/'); const int end2 = url.indexOfChar (start, ':'); const int end = (end1 < 0 || end2 < 0) ? jmax (end1, end2) : jmin (end1, end2); return url.substring (start, end); } const String URL::getSubPath() const { int start = findStartOfDomain (url); while (url[start] == '/') ++start; const int startOfPath = url.indexOfChar (start, '/') + 1; return startOfPath <= 0 ? String::empty : url.substring (startOfPath); } const String URL::getScheme() const { return url.substring (0, findStartOfDomain (url) - 1); } const URL URL::withNewSubPath (const String& newPath) const { int start = findStartOfDomain (url); while (url[start] == '/') ++start; const int startOfPath = url.indexOfChar (start, '/') + 1; URL u (*this); if (startOfPath > 0) u.url = url.substring (0, startOfPath); if (! u.url.endsWithChar ('/')) u.url << '/'; if (newPath.startsWithChar ('/')) u.url << newPath.substring (1); else u.url << newPath; return u; } bool URL::isProbablyAWebsiteURL (const String& possibleURL) { if (possibleURL.startsWithIgnoreCase ("http:") || possibleURL.startsWithIgnoreCase ("ftp:")) return true; if (possibleURL.startsWithIgnoreCase ("file:") || possibleURL.containsChar ('@') || possibleURL.endsWithChar ('.') || (! possibleURL.containsChar ('.'))) return false; if (possibleURL.startsWithIgnoreCase ("www.") && possibleURL.substring (5).containsChar ('.')) return true; const char* commonTLDs[] = { "com", "net", "org", "uk", "de", "fr", "jp" }; for (int i = 0; i < numElementsInArray (commonTLDs); ++i) if ((possibleURL + "/").containsIgnoreCase ("." + String (commonTLDs[i]) + "/")) return true; return false; } bool URL::isProbablyAnEmailAddress (const String& possibleEmailAddress) { const int atSign = possibleEmailAddress.indexOfChar ('@'); return atSign > 0 && possibleEmailAddress.lastIndexOfChar ('.') > (atSign + 1) && (! possibleEmailAddress.endsWithChar ('.')); } void* juce_openInternetFile (const String& url, const String& headers, const MemoryBlock& optionalPostData, const bool isPost, URL::OpenStreamProgressCallback* callback, void* callbackContext, int timeOutMs); void juce_closeInternetFile (void* handle); int juce_readFromInternetFile (void* handle, void* dest, int bytesToRead); int juce_seekInInternetFile (void* handle, int newPosition); int64 juce_getInternetFileContentLength (void* handle); void juce_getInternetFileHeaders (void* handle, StringPairArray& headers); class WebInputStream : public InputStream { public: WebInputStream (const URL& url, const bool isPost_, URL::OpenStreamProgressCallback* const progressCallback_, void* const progressCallbackContext_, const String& extraHeaders, const int timeOutMs_, StringPairArray* const responseHeaders) : position (0), finished (false), isPost (isPost_), progressCallback (progressCallback_), progressCallbackContext (progressCallbackContext_), timeOutMs (timeOutMs_) { server = url.toString (! isPost); if (isPost_) createHeadersAndPostData (url); headers += extraHeaders; if (! headers.endsWithChar ('\n')) headers << "\r\n"; handle = juce_openInternetFile (server, headers, postData, isPost, progressCallback_, progressCallbackContext_, timeOutMs); if (responseHeaders != 0) juce_getInternetFileHeaders (handle, *responseHeaders); } ~WebInputStream() { juce_closeInternetFile (handle); } bool isError() const { return handle == 0; } int64 getTotalLength() { return juce_getInternetFileContentLength (handle); } bool isExhausted() { return finished; } int64 getPosition() { return position; } int read (void* dest, int bytes) { if (finished || isError()) { return 0; } else { const int bytesRead = juce_readFromInternetFile (handle, dest, bytes); position += bytesRead; if (bytesRead == 0) finished = true; return bytesRead; } } bool setPosition (int64 wantedPos) { if (wantedPos != position) { finished = false; const int actualPos = juce_seekInInternetFile (handle, (int) wantedPos); if (actualPos == wantedPos) { position = wantedPos; } else { if (wantedPos < position) { juce_closeInternetFile (handle); position = 0; finished = false; handle = juce_openInternetFile (server, headers, postData, isPost, progressCallback, progressCallbackContext, timeOutMs); } skipNextBytes (wantedPos - position); } } return true; } juce_UseDebuggingNewOperator private: String server, headers; MemoryBlock postData; int64 position; bool finished; const bool isPost; void* handle; URL::OpenStreamProgressCallback* const progressCallback; void* const progressCallbackContext; const int timeOutMs; void createHeadersAndPostData (const URL& url) { MemoryOutputStream data (postData, false); if (url.getFilesToUpload().size() > 0) { // need to upload some files, so do it as multi-part... const String boundary (String::toHexString (Random::getSystemRandom().nextInt64())); headers << "Content-Type: multipart/form-data; boundary=" << boundary << "\r\n"; data << "--" << boundary; int i; for (i = 0; i < url.getParameters().size(); ++i) { data << "\r\nContent-Disposition: form-data; name=\"" << url.getParameters().getAllKeys() [i] << "\"\r\n\r\n" << url.getParameters().getAllValues() [i] << "\r\n--" << boundary; } for (i = 0; i < url.getFilesToUpload().size(); ++i) { const File file (url.getFilesToUpload().getAllValues() [i]); const String paramName (url.getFilesToUpload().getAllKeys() [i]); data << "\r\nContent-Disposition: form-data; name=\"" << paramName << "\"; filename=\"" << file.getFileName() << "\"\r\n"; const String mimeType (url.getMimeTypesOfUploadFiles() .getValue (paramName, String::empty)); if (mimeType.isNotEmpty()) data << "Content-Type: " << mimeType << "\r\n"; data << "Content-Transfer-Encoding: binary\r\n\r\n" << file << "\r\n--" << boundary; } data << "--\r\n"; data.flush(); } else { data << getMangledParameters (url.getParameters()) << url.getPostData(); data.flush(); // just a short text attachment, so use simple url encoding.. headers = "Content-Type: application/x-www-form-urlencoded\r\nContent-length: " + String ((unsigned int) postData.getSize()) + "\r\n"; } } WebInputStream (const WebInputStream&); WebInputStream& operator= (const WebInputStream&); }; InputStream* URL::createInputStream (const bool usePostCommand, OpenStreamProgressCallback* const progressCallback, void* const progressCallbackContext, const String& extraHeaders, const int timeOutMs, StringPairArray* const responseHeaders) const { ScopedPointer wi (new WebInputStream (*this, usePostCommand, progressCallback, progressCallbackContext, extraHeaders, timeOutMs, responseHeaders)); return wi->isError() ? 0 : wi.release(); } bool URL::readEntireBinaryStream (MemoryBlock& destData, const bool usePostCommand) const { const ScopedPointer in (createInputStream (usePostCommand)); if (in != 0) { in->readIntoMemoryBlock (destData); return true; } return false; } const String URL::readEntireTextStream (const bool usePostCommand) const { const ScopedPointer in (createInputStream (usePostCommand)); if (in != 0) return in->readEntireStreamAsString(); return String::empty; } XmlElement* URL::readEntireXmlStream (const bool usePostCommand) const { XmlDocument doc (readEntireTextStream (usePostCommand)); return doc.getDocumentElement(); } const URL URL::withParameter (const String& parameterName, const String& parameterValue) const { URL u (*this); u.parameters.set (parameterName, parameterValue); return u; } const URL URL::withFileToUpload (const String& parameterName, const File& fileToUpload, const String& mimeType) const { jassert (mimeType.isNotEmpty()); // You need to supply a mime type! URL u (*this); u.filesToUpload.set (parameterName, fileToUpload.getFullPathName()); u.mimeTypes.set (parameterName, mimeType); return u; } const URL URL::withPOSTData (const String& postData_) const { URL u (*this); u.postData = postData_; return u; } const StringPairArray& URL::getParameters() const { return parameters; } const StringPairArray& URL::getFilesToUpload() const { return filesToUpload; } const StringPairArray& URL::getMimeTypesOfUploadFiles() const { return mimeTypes; } const String URL::removeEscapeChars (const String& s) { String result (s.replaceCharacter ('+', ' ')); int nextPercent = 0; for (;;) { nextPercent = result.indexOfChar (nextPercent, '%'); if (nextPercent < 0) break; int hexDigit1 = 0, hexDigit2 = 0; if ((hexDigit1 = CharacterFunctions::getHexDigitValue (result [nextPercent + 1])) >= 0 && (hexDigit2 = CharacterFunctions::getHexDigitValue (result [nextPercent + 2])) >= 0) { const juce_wchar replacementChar = (juce_wchar) ((hexDigit1 << 4) + hexDigit2); result = result.replaceSection (nextPercent, 3, String::charToString (replacementChar)); } ++nextPercent; } return result; } const String URL::addEscapeChars (const String& s, const bool isParameter) { String result; result.preallocateStorage (s.length() + 8); const char* utf8 = s.toUTF8(); const char* legalChars = isParameter ? "_-.*!'()" : "_-$.*!'(),"; while (*utf8 != 0) { const char c = *utf8++; if (CharacterFunctions::isLetterOrDigit (c) || CharacterFunctions::indexOfChar (legalChars, c, false) >= 0) { result << c; } else { const int v = (int) (uint8) c; result << (v < 0x10 ? "%0" : "%") << String::toHexString (v); } } return result; } bool URL::launchInDefaultBrowser() const { String u (toString (true)); if (u.containsChar ('@') && ! u.containsChar (':')) u = "mailto:" + u; return PlatformUtilities::openDocument (u, String::empty); } END_JUCE_NAMESPACE /*** End of inlined file: juce_URL.cpp ***/ /*** Start of inlined file: juce_BufferedInputStream.cpp ***/ BEGIN_JUCE_NAMESPACE BufferedInputStream::BufferedInputStream (InputStream* const source_, const int bufferSize_, const bool deleteSourceWhenDestroyed) : source (source_), sourceToDelete (deleteSourceWhenDestroyed ? source_ : 0), bufferSize (jmax (256, bufferSize_)), position (source_->getPosition()), lastReadPos (0), bufferOverlap (128) { const int sourceSize = (int) source_->getTotalLength(); if (sourceSize >= 0) bufferSize = jmin (jmax (32, sourceSize), bufferSize); bufferStart = position; buffer.malloc (bufferSize); } BufferedInputStream::~BufferedInputStream() { } int64 BufferedInputStream::getTotalLength() { return source->getTotalLength(); } int64 BufferedInputStream::getPosition() { return position; } bool BufferedInputStream::setPosition (int64 newPosition) { position = jmax ((int64) 0, newPosition); return true; } bool BufferedInputStream::isExhausted() { return (position >= lastReadPos) && source->isExhausted(); } void BufferedInputStream::ensureBuffered() { const int64 bufferEndOverlap = lastReadPos - bufferOverlap; if (position < bufferStart || position >= bufferEndOverlap) { int bytesRead; if (position < lastReadPos && position >= bufferEndOverlap && position >= bufferStart) { const int bytesToKeep = (int) (lastReadPos - position); memmove (buffer, buffer + (int) (position - bufferStart), bytesToKeep); bufferStart = position; bytesRead = source->read (buffer + bytesToKeep, bufferSize - bytesToKeep); lastReadPos += bytesRead; bytesRead += bytesToKeep; } else { bufferStart = position; source->setPosition (bufferStart); bytesRead = source->read (buffer, bufferSize); lastReadPos = bufferStart + bytesRead; } while (bytesRead < bufferSize) buffer [bytesRead++] = 0; } } int BufferedInputStream::read (void* destBuffer, int maxBytesToRead) { if (position >= bufferStart && position + maxBytesToRead <= lastReadPos) { memcpy (destBuffer, buffer + (int) (position - bufferStart), maxBytesToRead); position += maxBytesToRead; return maxBytesToRead; } else { if (position < bufferStart || position >= lastReadPos) ensureBuffered(); int bytesRead = 0; while (maxBytesToRead > 0) { const int bytesAvailable = jmin (maxBytesToRead, (int) (lastReadPos - position)); if (bytesAvailable > 0) { memcpy (destBuffer, buffer + (int) (position - bufferStart), bytesAvailable); maxBytesToRead -= bytesAvailable; bytesRead += bytesAvailable; position += bytesAvailable; destBuffer = static_cast (destBuffer) + bytesAvailable; } const int64 oldLastReadPos = lastReadPos; ensureBuffered(); if (oldLastReadPos == lastReadPos) break; // if ensureBuffered() failed to read any more data, bail out if (isExhausted()) break; } return bytesRead; } } const String BufferedInputStream::readString() { if (position >= bufferStart && position < lastReadPos) { const int maxChars = (int) (lastReadPos - position); const char* const src = buffer + (int) (position - bufferStart); for (int i = 0; i < maxChars; ++i) { if (src[i] == 0) { position += i + 1; return String::fromUTF8 (src, i); } } } return InputStream::readString(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_BufferedInputStream.cpp ***/ /*** Start of inlined file: juce_FileInputSource.cpp ***/ BEGIN_JUCE_NAMESPACE FileInputSource::FileInputSource (const File& file_) : file (file_) { } FileInputSource::~FileInputSource() { } InputStream* FileInputSource::createInputStream() { return file.createInputStream(); } InputStream* FileInputSource::createInputStreamFor (const String& relatedItemPath) { return file.getSiblingFile (relatedItemPath).createInputStream(); } int64 FileInputSource::hashCode() const { return file.hashCode(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_FileInputSource.cpp ***/ /*** Start of inlined file: juce_MemoryInputStream.cpp ***/ BEGIN_JUCE_NAMESPACE MemoryInputStream::MemoryInputStream (const void* const sourceData, const size_t sourceDataSize, const bool keepInternalCopy) : data (static_cast (sourceData)), dataSize (sourceDataSize), position (0) { if (keepInternalCopy) { internalCopy.append (data, sourceDataSize); data = static_cast (internalCopy.getData()); } } MemoryInputStream::MemoryInputStream (const MemoryBlock& sourceData, const bool keepInternalCopy) : data (static_cast (sourceData.getData())), dataSize (sourceData.getSize()), position (0) { if (keepInternalCopy) { internalCopy = sourceData; data = static_cast (internalCopy.getData()); } } MemoryInputStream::~MemoryInputStream() { } int64 MemoryInputStream::getTotalLength() { return dataSize; } int MemoryInputStream::read (void* const buffer, const int howMany) { jassert (howMany >= 0); const int num = jmin (howMany, (int) (dataSize - position)); memcpy (buffer, data + position, num); position += num; return (int) num; } bool MemoryInputStream::isExhausted() { return (position >= dataSize); } bool MemoryInputStream::setPosition (const int64 pos) { position = (int) jlimit ((int64) 0, (int64) dataSize, pos); return true; } int64 MemoryInputStream::getPosition() { return position; } #if JUCE_UNIT_TESTS class MemoryStreamTests : public UnitTest { public: MemoryStreamTests() : UnitTest ("MemoryInputStream & MemoryOutputStream") {} void runTest() { beginTest ("Basics"); int randomInt = Random::getSystemRandom().nextInt(); int64 randomInt64 = Random::getSystemRandom().nextInt64(); double randomDouble = Random::getSystemRandom().nextDouble(); String randomString; for (int i = 50; --i >= 0;) randomString << (juce_wchar) (Random::getSystemRandom().nextInt() & 0xffff); MemoryOutputStream mo; mo.writeInt (randomInt); mo.writeIntBigEndian (randomInt); mo.writeCompressedInt (randomInt); mo.writeString (randomString); mo.writeInt64 (randomInt64); mo.writeInt64BigEndian (randomInt64); mo.writeDouble (randomDouble); mo.writeDoubleBigEndian (randomDouble); MemoryInputStream mi (mo.getData(), mo.getDataSize(), false); expect (mi.readInt() == randomInt); expect (mi.readIntBigEndian() == randomInt); expect (mi.readCompressedInt() == randomInt); expect (mi.readString() == randomString); expect (mi.readInt64() == randomInt64); expect (mi.readInt64BigEndian() == randomInt64); expect (mi.readDouble() == randomDouble); expect (mi.readDoubleBigEndian() == randomDouble); } }; static MemoryStreamTests memoryInputStreamUnitTests; #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_MemoryInputStream.cpp ***/ /*** Start of inlined file: juce_MemoryOutputStream.cpp ***/ BEGIN_JUCE_NAMESPACE MemoryOutputStream::MemoryOutputStream (const size_t initialSize) : data (internalBlock), position (0), size (0) { internalBlock.setSize (initialSize, false); } MemoryOutputStream::MemoryOutputStream (MemoryBlock& memoryBlockToWriteTo, const bool appendToExistingBlockContent) : data (memoryBlockToWriteTo), position (0), size (0) { if (appendToExistingBlockContent) position = size = memoryBlockToWriteTo.getSize(); } MemoryOutputStream::~MemoryOutputStream() { flush(); } void MemoryOutputStream::flush() { if (&data != &internalBlock) data.setSize (size, false); } void MemoryOutputStream::preallocate (const size_t bytesToPreallocate) { data.ensureSize (bytesToPreallocate + 1); } void MemoryOutputStream::reset() throw() { position = 0; size = 0; } bool MemoryOutputStream::write (const void* const buffer, int howMany) { if (howMany > 0) { const size_t storageNeeded = position + howMany; if (storageNeeded >= data.getSize()) data.ensureSize ((storageNeeded + jmin (storageNeeded / 2, (size_t) (1024 * 1024)) + 32) & ~31); memcpy (static_cast (data.getData()) + position, buffer, howMany); position += howMany; size = jmax (size, position); } return true; } const void* MemoryOutputStream::getData() const throw() { void* const d = data.getData(); if (data.getSize() > size) static_cast (d) [size] = 0; return d; } bool MemoryOutputStream::setPosition (int64 newPosition) { if (newPosition <= (int64) size) { // ok to seek backwards position = jlimit ((size_t) 0, size, (size_t) newPosition); return true; } else { // trying to make it bigger isn't a good thing to do.. return false; } } int MemoryOutputStream::writeFromInputStream (InputStream& source, int64 maxNumBytesToWrite) { // before writing from an input, see if we can preallocate to make it more efficient.. int64 availableData = source.getTotalLength() - source.getPosition(); if (availableData > 0) { if (maxNumBytesToWrite > 0 && maxNumBytesToWrite < availableData) availableData = maxNumBytesToWrite; preallocate (data.getSize() + (size_t) maxNumBytesToWrite); } return OutputStream::writeFromInputStream (source, maxNumBytesToWrite); } const String MemoryOutputStream::toUTF8() const { return String (static_cast (getData()), getDataSize()); } const String MemoryOutputStream::toString() const { return String::createStringFromData (getData(), getDataSize()); } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const MemoryOutputStream& streamToRead) { stream.write (streamToRead.getData(), streamToRead.getDataSize()); return stream; } END_JUCE_NAMESPACE /*** End of inlined file: juce_MemoryOutputStream.cpp ***/ /*** Start of inlined file: juce_SubregionStream.cpp ***/ BEGIN_JUCE_NAMESPACE SubregionStream::SubregionStream (InputStream* const sourceStream, const int64 startPositionInSourceStream_, const int64 lengthOfSourceStream_, const bool deleteSourceWhenDestroyed) : source (sourceStream), startPositionInSourceStream (startPositionInSourceStream_), lengthOfSourceStream (lengthOfSourceStream_) { if (deleteSourceWhenDestroyed) sourceToDelete = source; setPosition (0); } SubregionStream::~SubregionStream() { } int64 SubregionStream::getTotalLength() { const int64 srcLen = source->getTotalLength() - startPositionInSourceStream; return (lengthOfSourceStream >= 0) ? jmin (lengthOfSourceStream, srcLen) : srcLen; } int64 SubregionStream::getPosition() { return source->getPosition() - startPositionInSourceStream; } bool SubregionStream::setPosition (int64 newPosition) { return source->setPosition (jmax ((int64) 0, newPosition + startPositionInSourceStream)); } int SubregionStream::read (void* destBuffer, int maxBytesToRead) { if (lengthOfSourceStream < 0) { return source->read (destBuffer, maxBytesToRead); } else { maxBytesToRead = (int) jmin ((int64) maxBytesToRead, lengthOfSourceStream - getPosition()); if (maxBytesToRead <= 0) return 0; return source->read (destBuffer, maxBytesToRead); } } bool SubregionStream::isExhausted() { if (lengthOfSourceStream >= 0) return (getPosition() >= lengthOfSourceStream) || source->isExhausted(); else return source->isExhausted(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_SubregionStream.cpp ***/ /*** Start of inlined file: juce_PerformanceCounter.cpp ***/ BEGIN_JUCE_NAMESPACE PerformanceCounter::PerformanceCounter (const String& name_, int runsPerPrintout, const File& loggingFile) : name (name_), numRuns (0), runsPerPrint (runsPerPrintout), totalTime (0), outputFile (loggingFile) { if (outputFile != File::nonexistent) { String s ("**** Counter for \""); s << name_ << "\" started at: " << Time::getCurrentTime().toString (true, true) << "\r\n"; outputFile.appendText (s, false, false); } } PerformanceCounter::~PerformanceCounter() { printStatistics(); } void PerformanceCounter::start() { started = Time::getHighResolutionTicks(); } void PerformanceCounter::stop() { const int64 now = Time::getHighResolutionTicks(); totalTime += 1000.0 * Time::highResolutionTicksToSeconds (now - started); if (++numRuns == runsPerPrint) printStatistics(); } void PerformanceCounter::printStatistics() { if (numRuns > 0) { String s ("Performance count for \""); s << name << "\" - average over " << numRuns << " run(s) = "; const int micros = (int) (totalTime * (1000.0 / numRuns)); if (micros > 10000) s << (micros/1000) << " millisecs"; else s << micros << " microsecs"; s << ", total = " << String (totalTime / 1000, 5) << " seconds"; Logger::outputDebugString (s); s << "\r\n"; if (outputFile != File::nonexistent) outputFile.appendText (s, false, false); numRuns = 0; totalTime = 0; } } END_JUCE_NAMESPACE /*** End of inlined file: juce_PerformanceCounter.cpp ***/ /*** Start of inlined file: juce_Uuid.cpp ***/ BEGIN_JUCE_NAMESPACE Uuid::Uuid() { // Mix up any available MAC addresses with some time-based pseudo-random numbers // to make it very very unlikely that two UUIDs will ever be the same.. static int64 macAddresses[2]; static bool hasCheckedMacAddresses = false; if (! hasCheckedMacAddresses) { hasCheckedMacAddresses = true; SystemStats::getMACAddresses (macAddresses, 2); } value.asInt64[0] = macAddresses[0]; value.asInt64[1] = macAddresses[1]; // We'll use both a local RNG that is re-seeded, plus the shared RNG, // whose seed will carry over between calls to this method. Random r (macAddresses[0] ^ macAddresses[1] ^ Random::getSystemRandom().nextInt64()); for (int i = 4; --i >= 0;) { r.setSeedRandomly(); // calling this repeatedly improves randomness value.asInt[i] ^= r.nextInt(); value.asInt[i] ^= Random::getSystemRandom().nextInt(); } } Uuid::~Uuid() throw() { } Uuid::Uuid (const Uuid& other) : value (other.value) { } Uuid& Uuid::operator= (const Uuid& other) { value = other.value; return *this; } bool Uuid::operator== (const Uuid& other) const { return value.asInt64[0] == other.value.asInt64[0] && value.asInt64[1] == other.value.asInt64[1]; } bool Uuid::operator!= (const Uuid& other) const { return ! operator== (other); } bool Uuid::isNull() const throw() { return (value.asInt64 [0] == 0) && (value.asInt64 [1] == 0); } const String Uuid::toString() const { return String::toHexString (value.asBytes, sizeof (value.asBytes), 0); } Uuid::Uuid (const String& uuidString) { operator= (uuidString); } Uuid& Uuid::operator= (const String& uuidString) { MemoryBlock mb; mb.loadFromHexString (uuidString); mb.ensureSize (sizeof (value.asBytes), true); mb.copyTo (value.asBytes, 0, sizeof (value.asBytes)); return *this; } Uuid::Uuid (const uint8* const rawData) { operator= (rawData); } Uuid& Uuid::operator= (const uint8* const rawData) { if (rawData != 0) memcpy (value.asBytes, rawData, sizeof (value.asBytes)); else zeromem (value.asBytes, sizeof (value.asBytes)); return *this; } END_JUCE_NAMESPACE /*** End of inlined file: juce_Uuid.cpp ***/ /*** Start of inlined file: juce_ZipFile.cpp ***/ BEGIN_JUCE_NAMESPACE class ZipFile::ZipEntryInfo { public: ZipFile::ZipEntry entry; int streamOffset; int compressedSize; bool compressed; }; class ZipFile::ZipInputStream : public InputStream { public: ZipInputStream (ZipFile& file_, ZipFile::ZipEntryInfo& zei) : file (file_), zipEntryInfo (zei), pos (0), headerSize (0), inputStream (0) { inputStream = file_.inputStream; if (file_.inputSource != 0) { inputStream = file.inputSource->createInputStream(); } else { #if JUCE_DEBUG file_.numOpenStreams++; #endif } char buffer [30]; if (inputStream != 0 && inputStream->setPosition (zei.streamOffset) && inputStream->read (buffer, 30) == 30 && ByteOrder::littleEndianInt (buffer) == 0x04034b50) { headerSize = 30 + ByteOrder::littleEndianShort (buffer + 26) + ByteOrder::littleEndianShort (buffer + 28); } } ~ZipInputStream() { #if JUCE_DEBUG if (inputStream != 0 && inputStream == file.inputStream) file.numOpenStreams--; #endif if (inputStream != file.inputStream) delete inputStream; } int64 getTotalLength() { return zipEntryInfo.compressedSize; } int read (void* buffer, int howMany) { if (headerSize <= 0) return 0; howMany = (int) jmin ((int64) howMany, zipEntryInfo.compressedSize - pos); if (inputStream == 0) return 0; int num; if (inputStream == file.inputStream) { const ScopedLock sl (file.lock); inputStream->setPosition (pos + zipEntryInfo.streamOffset + headerSize); num = inputStream->read (buffer, howMany); } else { inputStream->setPosition (pos + zipEntryInfo.streamOffset + headerSize); num = inputStream->read (buffer, howMany); } pos += num; return num; } bool isExhausted() { return headerSize <= 0 || pos >= zipEntryInfo.compressedSize; } int64 getPosition() { return pos; } bool setPosition (int64 newPos) { pos = jlimit ((int64) 0, (int64) zipEntryInfo.compressedSize, newPos); return true; } private: ZipFile& file; ZipEntryInfo zipEntryInfo; int64 pos; int headerSize; InputStream* inputStream; ZipInputStream (const ZipInputStream&); ZipInputStream& operator= (const ZipInputStream&); }; ZipFile::ZipFile (InputStream* const source_, const bool deleteStreamWhenDestroyed) : inputStream (source_) #if JUCE_DEBUG , numOpenStreams (0) #endif { if (deleteStreamWhenDestroyed) streamToDelete = inputStream; init(); } ZipFile::ZipFile (const File& file) : inputStream (0) #if JUCE_DEBUG , numOpenStreams (0) #endif { inputSource = new FileInputSource (file); init(); } ZipFile::ZipFile (InputSource* const inputSource_) : inputStream (0), inputSource (inputSource_) #if JUCE_DEBUG , numOpenStreams (0) #endif { init(); } ZipFile::~ZipFile() { #if JUCE_DEBUG entries.clear(); // If you hit this assertion, it means you've created a stream to read // one of the items in the zipfile, but you've forgotten to delete that // stream object before deleting the file.. Streams can't be kept open // after the file is deleted because they need to share the input // stream that the file uses to read itself. jassert (numOpenStreams == 0); #endif } int ZipFile::getNumEntries() const throw() { return entries.size(); } const ZipFile::ZipEntry* ZipFile::getEntry (const int index) const throw() { ZipEntryInfo* const zei = entries [index]; return zei != 0 ? &(zei->entry) : 0; } int ZipFile::getIndexOfFileName (const String& fileName) const throw() { for (int i = 0; i < entries.size(); ++i) if (entries.getUnchecked (i)->entry.filename == fileName) return i; return -1; } const ZipFile::ZipEntry* ZipFile::getEntry (const String& fileName) const throw() { return getEntry (getIndexOfFileName (fileName)); } InputStream* ZipFile::createStreamForEntry (const int index) { ZipEntryInfo* const zei = entries[index]; InputStream* stream = 0; if (zei != 0) { stream = new ZipInputStream (*this, *zei); if (zei->compressed) { stream = new GZIPDecompressorInputStream (stream, true, true, zei->entry.uncompressedSize); // (much faster to unzip in big blocks using a buffer..) stream = new BufferedInputStream (stream, 32768, true); } } return stream; } class ZipFile::ZipFilenameComparator { public: int compareElements (const ZipFile::ZipEntryInfo* first, const ZipFile::ZipEntryInfo* second) { return first->entry.filename.compare (second->entry.filename); } }; void ZipFile::sortEntriesByFilename() { ZipFilenameComparator sorter; entries.sort (sorter); } void ZipFile::init() { ScopedPointer toDelete; InputStream* in = inputStream; if (inputSource != 0) { in = inputSource->createInputStream(); toDelete = in; } if (in != 0) { int numEntries = 0; int pos = findEndOfZipEntryTable (in, numEntries); if (pos >= 0 && pos < in->getTotalLength()) { const int size = (int) (in->getTotalLength() - pos); in->setPosition (pos); MemoryBlock headerData; if (in->readIntoMemoryBlock (headerData, size) == size) { pos = 0; for (int i = 0; i < numEntries; ++i) { if (pos + 46 > size) break; const char* const buffer = static_cast (headerData.getData()) + pos; const int fileNameLen = ByteOrder::littleEndianShort (buffer + 28); if (pos + 46 + fileNameLen > size) break; ZipEntryInfo* const zei = new ZipEntryInfo(); zei->entry.filename = String::fromUTF8 (buffer + 46, fileNameLen); const int time = ByteOrder::littleEndianShort (buffer + 12); const int date = ByteOrder::littleEndianShort (buffer + 14); const int year = 1980 + (date >> 9); const int month = ((date >> 5) & 15) - 1; const int day = date & 31; const int hours = time >> 11; const int minutes = (time >> 5) & 63; const int seconds = (time & 31) << 1; zei->entry.fileTime = Time (year, month, day, hours, minutes, seconds); zei->compressed = ByteOrder::littleEndianShort (buffer + 10) != 0; zei->compressedSize = ByteOrder::littleEndianInt (buffer + 20); zei->entry.uncompressedSize = ByteOrder::littleEndianInt (buffer + 24); zei->streamOffset = ByteOrder::littleEndianInt (buffer + 42); entries.add (zei); pos += 46 + fileNameLen + ByteOrder::littleEndianShort (buffer + 30) + ByteOrder::littleEndianShort (buffer + 32); } } } } } int ZipFile::findEndOfZipEntryTable (InputStream* input, int& numEntries) { BufferedInputStream in (input, 8192, false); in.setPosition (in.getTotalLength()); int64 pos = in.getPosition(); const int64 lowestPos = jmax ((int64) 0, pos - 1024); char buffer [32]; zeromem (buffer, sizeof (buffer)); while (pos > lowestPos) { in.setPosition (pos - 22); pos = in.getPosition(); memcpy (buffer + 22, buffer, 4); if (in.read (buffer, 22) != 22) return 0; for (int i = 0; i < 22; ++i) { if (ByteOrder::littleEndianInt (buffer + i) == 0x06054b50) { in.setPosition (pos + i); in.read (buffer, 22); numEntries = ByteOrder::littleEndianShort (buffer + 10); return ByteOrder::littleEndianInt (buffer + 16); } } } return 0; } bool ZipFile::uncompressTo (const File& targetDirectory, const bool shouldOverwriteFiles) { for (int i = 0; i < entries.size(); ++i) if (! uncompressEntry (i, targetDirectory, shouldOverwriteFiles)) return false; return true; } bool ZipFile::uncompressEntry (const int index, const File& targetDirectory, bool shouldOverwriteFiles) { const ZipEntryInfo* zei = entries [index]; if (zei != 0) { const File targetFile (targetDirectory.getChildFile (zei->entry.filename)); if (zei->entry.filename.endsWithChar ('/')) { targetFile.createDirectory(); // (entry is a directory, not a file) } else { ScopedPointer in (createStreamForEntry (index)); if (in != 0) { if (shouldOverwriteFiles && ! targetFile.deleteFile()) return false; if ((! targetFile.exists()) && targetFile.getParentDirectory().createDirectory()) { ScopedPointer out (targetFile.createOutputStream()); if (out != 0) { out->writeFromInputStream (*in, -1); out = 0; targetFile.setCreationTime (zei->entry.fileTime); targetFile.setLastModificationTime (zei->entry.fileTime); targetFile.setLastAccessTime (zei->entry.fileTime); return true; } } } } } return false; } END_JUCE_NAMESPACE /*** End of inlined file: juce_ZipFile.cpp ***/ /*** Start of inlined file: juce_CharacterFunctions.cpp ***/ #if JUCE_MSVC #pragma warning (push) #pragma warning (disable: 4514 4996) #endif #include #include #include BEGIN_JUCE_NAMESPACE int CharacterFunctions::length (const char* const s) throw() { return (int) strlen (s); } int CharacterFunctions::length (const juce_wchar* const s) throw() { return (int) wcslen (s); } void CharacterFunctions::copy (char* dest, const char* src, const int maxChars) throw() { strncpy (dest, src, maxChars); } void CharacterFunctions::copy (juce_wchar* dest, const juce_wchar* src, int maxChars) throw() { wcsncpy (dest, src, maxChars); } void CharacterFunctions::copy (juce_wchar* dest, const char* src, const int maxChars) throw() { mbstowcs (dest, src, maxChars); } void CharacterFunctions::copy (char* dest, const juce_wchar* src, const int maxChars) throw() { wcstombs (dest, src, maxChars); } int CharacterFunctions::bytesRequiredForCopy (const juce_wchar* src) throw() { return (int) wcstombs (0, src, 0); } void CharacterFunctions::append (char* dest, const char* src) throw() { strcat (dest, src); } void CharacterFunctions::append (juce_wchar* dest, const juce_wchar* src) throw() { wcscat (dest, src); } int CharacterFunctions::compare (const char* const s1, const char* const s2) throw() { return strcmp (s1, s2); } int CharacterFunctions::compare (const juce_wchar* s1, const juce_wchar* s2) throw() { jassert (s1 != 0 && s2 != 0); return wcscmp (s1, s2); } int CharacterFunctions::compare (const char* const s1, const char* const s2, const int maxChars) throw() { jassert (s1 != 0 && s2 != 0); return strncmp (s1, s2, maxChars); } int CharacterFunctions::compare (const juce_wchar* s1, const juce_wchar* s2, int maxChars) throw() { jassert (s1 != 0 && s2 != 0); return wcsncmp (s1, s2, maxChars); } int CharacterFunctions::compare (const juce_wchar* s1, const char* s2) throw() { jassert (s1 != 0 && s2 != 0); for (;;) { const int diff = (int) (*s1 - (juce_wchar) (unsigned char) *s2); if (diff != 0) return diff; else if (*s1 == 0) break; ++s1; ++s2; } return 0; } int CharacterFunctions::compare (const char* s1, const juce_wchar* s2) throw() { return -compare (s2, s1); } int CharacterFunctions::compareIgnoreCase (const char* const s1, const char* const s2) throw() { jassert (s1 != 0 && s2 != 0); #if JUCE_WINDOWS return stricmp (s1, s2); #else return strcasecmp (s1, s2); #endif } int CharacterFunctions::compareIgnoreCase (const juce_wchar* s1, const juce_wchar* s2) throw() { jassert (s1 != 0 && s2 != 0); #if JUCE_WINDOWS return _wcsicmp (s1, s2); #else for (;;) { if (*s1 != *s2) { const int diff = toUpperCase (*s1) - toUpperCase (*s2); if (diff != 0) return diff < 0 ? -1 : 1; } else if (*s1 == 0) break; ++s1; ++s2; } return 0; #endif } int CharacterFunctions::compareIgnoreCase (const juce_wchar* s1, const char* s2) throw() { jassert (s1 != 0 && s2 != 0); for (;;) { if (*s1 != *s2) { const int diff = toUpperCase (*s1) - toUpperCase (*s2); if (diff != 0) return diff < 0 ? -1 : 1; } else if (*s1 == 0) break; ++s1; ++s2; } return 0; } int CharacterFunctions::compareIgnoreCase (const char* const s1, const char* const s2, const int maxChars) throw() { jassert (s1 != 0 && s2 != 0); #if JUCE_WINDOWS return strnicmp (s1, s2, maxChars); #else return strncasecmp (s1, s2, maxChars); #endif } int CharacterFunctions::compareIgnoreCase (const juce_wchar* s1, const juce_wchar* s2, int maxChars) throw() { jassert (s1 != 0 && s2 != 0); #if JUCE_WINDOWS return _wcsnicmp (s1, s2, maxChars); #else while (--maxChars >= 0) { if (*s1 != *s2) { const int diff = toUpperCase (*s1) - toUpperCase (*s2); if (diff != 0) return diff < 0 ? -1 : 1; } else if (*s1 == 0) break; ++s1; ++s2; } return 0; #endif } const char* CharacterFunctions::find (const char* const haystack, const char* const needle) throw() { return strstr (haystack, needle); } const juce_wchar* CharacterFunctions::find (const juce_wchar* haystack, const juce_wchar* const needle) throw() { return wcsstr (haystack, needle); } int CharacterFunctions::indexOfChar (const char* const haystack, const char needle, const bool ignoreCase) throw() { if (haystack != 0) { int i = 0; if (ignoreCase) { const char n1 = toLowerCase (needle); const char n2 = toUpperCase (needle); if (n1 != n2) // if the char is the same in upper/lower case, fall through to the normal search { while (haystack[i] != 0) { if (haystack[i] == n1 || haystack[i] == n2) return i; ++i; } return -1; } jassert (n1 == needle); } while (haystack[i] != 0) { if (haystack[i] == needle) return i; ++i; } } return -1; } int CharacterFunctions::indexOfChar (const juce_wchar* const haystack, const juce_wchar needle, const bool ignoreCase) throw() { if (haystack != 0) { int i = 0; if (ignoreCase) { const juce_wchar n1 = toLowerCase (needle); const juce_wchar n2 = toUpperCase (needle); if (n1 != n2) // if the char is the same in upper/lower case, fall through to the normal search { while (haystack[i] != 0) { if (haystack[i] == n1 || haystack[i] == n2) return i; ++i; } return -1; } jassert (n1 == needle); } while (haystack[i] != 0) { if (haystack[i] == needle) return i; ++i; } } return -1; } int CharacterFunctions::indexOfCharFast (const char* const haystack, const char needle) throw() { jassert (haystack != 0); int i = 0; while (haystack[i] != 0) { if (haystack[i] == needle) return i; ++i; } return -1; } int CharacterFunctions::indexOfCharFast (const juce_wchar* const haystack, const juce_wchar needle) throw() { jassert (haystack != 0); int i = 0; while (haystack[i] != 0) { if (haystack[i] == needle) return i; ++i; } return -1; } int CharacterFunctions::getIntialSectionContainingOnly (const char* const text, const char* const allowedChars) throw() { return allowedChars == 0 ? 0 : (int) strspn (text, allowedChars); } int CharacterFunctions::getIntialSectionContainingOnly (const juce_wchar* const text, const juce_wchar* const allowedChars) throw() { if (allowedChars == 0) return 0; int i = 0; for (;;) { if (indexOfCharFast (allowedChars, text[i]) < 0) break; ++i; } return i; } int CharacterFunctions::ftime (char* const dest, const int maxChars, const char* const format, const struct tm* const tm) throw() { return (int) strftime (dest, maxChars, format, tm); } int CharacterFunctions::ftime (juce_wchar* const dest, const int maxChars, const juce_wchar* const format, const struct tm* const tm) throw() { return (int) wcsftime (dest, maxChars, format, tm); } int CharacterFunctions::getIntValue (const char* const s) throw() { return atoi (s); } int CharacterFunctions::getIntValue (const juce_wchar* s) throw() { #if JUCE_WINDOWS return _wtoi (s); #else int v = 0; while (isWhitespace (*s)) ++s; const bool isNeg = *s == '-'; if (isNeg) ++s; for (;;) { const wchar_t c = *s++; if (c >= '0' && c <= '9') v = v * 10 + (int) (c - '0'); else break; } return isNeg ? -v : v; #endif } int64 CharacterFunctions::getInt64Value (const char* s) throw() { #if JUCE_LINUX return atoll (s); #elif JUCE_WINDOWS return _atoi64 (s); #else int64 v = 0; while (isWhitespace (*s)) ++s; const bool isNeg = *s == '-'; if (isNeg) ++s; for (;;) { const char c = *s++; if (c >= '0' && c <= '9') v = v * 10 + (int64) (c - '0'); else break; } return isNeg ? -v : v; #endif } int64 CharacterFunctions::getInt64Value (const juce_wchar* s) throw() { #if JUCE_WINDOWS return _wtoi64 (s); #else int64 v = 0; while (isWhitespace (*s)) ++s; const bool isNeg = *s == '-'; if (isNeg) ++s; for (;;) { const juce_wchar c = *s++; if (c >= '0' && c <= '9') v = v * 10 + (int64) (c - '0'); else break; } return isNeg ? -v : v; #endif } static double juce_mulexp10 (const double value, int exponent) throw() { if (exponent == 0) return value; if (value == 0) return 0; const bool negative = (exponent < 0); if (negative) exponent = -exponent; double result = 1.0, power = 10.0; for (int bit = 1; exponent != 0; bit <<= 1) { if ((exponent & bit) != 0) { exponent ^= bit; result *= power; if (exponent == 0) break; } power *= power; } return negative ? (value / result) : (value * result); } template double juce_atof (const CharType* const original) throw() { double result[3] = { 0, 0, 0 }, accumulator[2] = { 0, 0 }; int exponentAdjustment[2] = { 0, 0 }, exponentAccumulator[2] = { -1, -1 }; int exponent = 0, decPointIndex = 0, digit = 0; int lastDigit = 0, numSignificantDigits = 0; bool isNegative = false, digitsFound = false; const int maxSignificantDigits = 15 + 2; const CharType* s = original; while (CharacterFunctions::isWhitespace (*s)) ++s; switch (*s) { case '-': isNegative = true; // fall-through.. case '+': ++s; } if (*s == 'n' || *s == 'N' || *s == 'i' || *s == 'I') return atof (String (original).toUTF8()); // Let the c library deal with NAN and INF for (;;) { if (CharacterFunctions::isDigit (*s)) { lastDigit = digit; digit = *s++ - '0'; digitsFound = true; if (decPointIndex != 0) exponentAdjustment[1]++; if (numSignificantDigits == 0 && digit == 0) continue; if (++numSignificantDigits > maxSignificantDigits) { if (digit > 5) ++accumulator [decPointIndex]; else if (digit == 5 && (lastDigit & 1) != 0) ++accumulator [decPointIndex]; if (decPointIndex > 0) exponentAdjustment[1]--; else exponentAdjustment[0]++; while (CharacterFunctions::isDigit (*s)) { ++s; if (decPointIndex == 0) exponentAdjustment[0]++; } } else { const double maxAccumulatorValue = (double) ((std::numeric_limits::max() - 9) / 10); if (accumulator [decPointIndex] > maxAccumulatorValue) { result [decPointIndex] = juce_mulexp10 (result [decPointIndex], exponentAccumulator [decPointIndex]) + accumulator [decPointIndex]; accumulator [decPointIndex] = 0; exponentAccumulator [decPointIndex] = 0; } accumulator [decPointIndex] = accumulator[decPointIndex] * 10 + digit; exponentAccumulator [decPointIndex]++; } } else if (decPointIndex == 0 && *s == '.') { ++s; decPointIndex = 1; if (numSignificantDigits > maxSignificantDigits) { while (CharacterFunctions::isDigit (*s)) ++s; break; } } else { break; } } result[0] = juce_mulexp10 (result[0], exponentAccumulator[0]) + accumulator[0]; if (decPointIndex != 0) result[1] = juce_mulexp10 (result[1], exponentAccumulator[1]) + accumulator[1]; if ((*s == 'e' || *s == 'E') && digitsFound) { bool negativeExponent = false; switch (*++s) { case '-': negativeExponent = true; // fall-through.. case '+': ++s; } while (CharacterFunctions::isDigit (*s)) exponent = (exponent * 10) + (*s++ - '0'); if (negativeExponent) exponent = -exponent; } double r = juce_mulexp10 (result[0], exponent + exponentAdjustment[0]); if (decPointIndex != 0) r += juce_mulexp10 (result[1], exponent - exponentAdjustment[1]); return isNegative ? -r : r; } double CharacterFunctions::getDoubleValue (const char* const s) throw() { return juce_atof (s); } double CharacterFunctions::getDoubleValue (const juce_wchar* const s) throw() { return juce_atof (s); } char CharacterFunctions::toUpperCase (const char character) throw() { return (char) toupper (character); } juce_wchar CharacterFunctions::toUpperCase (const juce_wchar character) throw() { return towupper (character); } void CharacterFunctions::toUpperCase (char* s) throw() { #if JUCE_WINDOWS strupr (s); #else while (*s != 0) { *s = toUpperCase (*s); ++s; } #endif } void CharacterFunctions::toUpperCase (juce_wchar* s) throw() { #if JUCE_WINDOWS _wcsupr (s); #else while (*s != 0) { *s = toUpperCase (*s); ++s; } #endif } bool CharacterFunctions::isUpperCase (const char character) throw() { return isupper (character) != 0; } bool CharacterFunctions::isUpperCase (const juce_wchar character) throw() { #if JUCE_WINDOWS return iswupper (character) != 0; #else return toLowerCase (character) != character; #endif } char CharacterFunctions::toLowerCase (const char character) throw() { return (char) tolower (character); } juce_wchar CharacterFunctions::toLowerCase (const juce_wchar character) throw() { return towlower (character); } void CharacterFunctions::toLowerCase (char* s) throw() { #if JUCE_WINDOWS strlwr (s); #else while (*s != 0) { *s = toLowerCase (*s); ++s; } #endif } void CharacterFunctions::toLowerCase (juce_wchar* s) throw() { #if JUCE_WINDOWS _wcslwr (s); #else while (*s != 0) { *s = toLowerCase (*s); ++s; } #endif } bool CharacterFunctions::isLowerCase (const char character) throw() { return islower (character) != 0; } bool CharacterFunctions::isLowerCase (const juce_wchar character) throw() { #if JUCE_WINDOWS return iswlower (character) != 0; #else return toUpperCase (character) != character; #endif } bool CharacterFunctions::isWhitespace (const char character) throw() { return character == ' ' || (character <= 13 && character >= 9); } bool CharacterFunctions::isWhitespace (const juce_wchar character) throw() { return iswspace (character) != 0; } bool CharacterFunctions::isDigit (const char character) throw() { return (character >= '0' && character <= '9'); } bool CharacterFunctions::isDigit (const juce_wchar character) throw() { return iswdigit (character) != 0; } bool CharacterFunctions::isLetter (const char character) throw() { return (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z'); } bool CharacterFunctions::isLetter (const juce_wchar character) throw() { return iswalpha (character) != 0; } bool CharacterFunctions::isLetterOrDigit (const char character) throw() { return (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || (character >= '0' && character <= '9'); } bool CharacterFunctions::isLetterOrDigit (const juce_wchar character) throw() { return iswalnum (character) != 0; } int CharacterFunctions::getHexDigitValue (const juce_wchar digit) throw() { unsigned int d = digit - '0'; if (d < (unsigned int) 10) return (int) d; d += (unsigned int) ('0' - 'a'); if (d < (unsigned int) 6) return (int) d + 10; d += (unsigned int) ('a' - 'A'); if (d < (unsigned int) 6) return (int) d + 10; return -1; } #if JUCE_MSVC #pragma warning (pop) #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_CharacterFunctions.cpp ***/ /*** Start of inlined file: juce_LocalisedStrings.cpp ***/ BEGIN_JUCE_NAMESPACE LocalisedStrings::LocalisedStrings (const String& fileContents) { loadFromText (fileContents); } LocalisedStrings::LocalisedStrings (const File& fileToLoad) { loadFromText (fileToLoad.loadFileAsString()); } LocalisedStrings::~LocalisedStrings() { } const String LocalisedStrings::translate (const String& text) const { return translations.getValue (text, text); } static int findCloseQuote (const String& text, int startPos) { juce_wchar lastChar = 0; for (;;) { const juce_wchar c = text [startPos]; if (c == 0 || (c == '"' && lastChar != '\\')) break; lastChar = c; ++startPos; } return startPos; } static const String unescapeString (const String& s) { return s.replace ("\\\"", "\"") .replace ("\\\'", "\'") .replace ("\\t", "\t") .replace ("\\r", "\r") .replace ("\\n", "\n"); } void LocalisedStrings::loadFromText (const String& fileContents) { StringArray lines; lines.addLines (fileContents); for (int i = 0; i < lines.size(); ++i) { String line (lines[i].trim()); if (line.startsWithChar ('"')) { int closeQuote = findCloseQuote (line, 1); const String originalText (unescapeString (line.substring (1, closeQuote))); if (originalText.isNotEmpty()) { const int openingQuote = findCloseQuote (line, closeQuote + 1); closeQuote = findCloseQuote (line, openingQuote + 1); const String newText (unescapeString (line.substring (openingQuote + 1, closeQuote))); if (newText.isNotEmpty()) translations.set (originalText, newText); } } else if (line.startsWithIgnoreCase ("language:")) { languageName = line.substring (9).trim(); } else if (line.startsWithIgnoreCase ("countries:")) { countryCodes.addTokens (line.substring (10).trim(), true); countryCodes.trim(); countryCodes.removeEmptyStrings(); } } } void LocalisedStrings::setIgnoresCase (const bool shouldIgnoreCase) { translations.setIgnoresCase (shouldIgnoreCase); } static CriticalSection currentMappingsLock; static LocalisedStrings* currentMappings = 0; void LocalisedStrings::setCurrentMappings (LocalisedStrings* newTranslations) { const ScopedLock sl (currentMappingsLock); delete currentMappings; currentMappings = newTranslations; } LocalisedStrings* LocalisedStrings::getCurrentMappings() { return currentMappings; } const String LocalisedStrings::translateWithCurrentMappings (const String& text) { const ScopedLock sl (currentMappingsLock); if (currentMappings != 0) return currentMappings->translate (text); return text; } const String LocalisedStrings::translateWithCurrentMappings (const char* text) { return translateWithCurrentMappings (String (text)); } END_JUCE_NAMESPACE /*** End of inlined file: juce_LocalisedStrings.cpp ***/ /*** Start of inlined file: juce_String.cpp ***/ #if JUCE_MSVC #pragma warning (push) #pragma warning (disable: 4514) #endif #include BEGIN_JUCE_NAMESPACE #if JUCE_MSVC #pragma warning (pop) #endif #if defined (JUCE_STRINGS_ARE_UNICODE) && ! JUCE_STRINGS_ARE_UNICODE #error "JUCE_STRINGS_ARE_UNICODE is deprecated! All strings are now unicode by default." #endif class StringHolder { public: StringHolder() : refCount (0x3fffffff), allocatedNumChars (0) { text[0] = 0; } static juce_wchar* createUninitialised (const size_t numChars) { StringHolder* const s = reinterpret_cast (new char [sizeof (StringHolder) + numChars * sizeof (juce_wchar)]); s->refCount.value = 0; s->allocatedNumChars = numChars; return &(s->text[0]); } static juce_wchar* createCopy (const juce_wchar* const src, const size_t numChars) { juce_wchar* const dest = createUninitialised (numChars); copyChars (dest, src, numChars); return dest; } static juce_wchar* createCopy (const char* const src, const size_t numChars) { juce_wchar* const dest = createUninitialised (numChars); CharacterFunctions::copy (dest, src, (int) numChars); dest [numChars] = 0; return dest; } static inline juce_wchar* getEmpty() throw() { return &(empty.text[0]); } static void retain (juce_wchar* const text) throw() { ++(bufferFromText (text)->refCount); } static inline void release (StringHolder* const b) throw() { if (--(b->refCount) == -1 && b != &empty) delete[] reinterpret_cast (b); } static void release (juce_wchar* const text) throw() { release (bufferFromText (text)); } static juce_wchar* makeUnique (juce_wchar* const text) { StringHolder* const b = bufferFromText (text); if (b->refCount.get() <= 0) return text; juce_wchar* const newText = createCopy (text, b->allocatedNumChars); release (b); return newText; } static juce_wchar* makeUniqueWithSize (juce_wchar* const text, size_t numChars) { StringHolder* const b = bufferFromText (text); if (b->refCount.get() <= 0 && b->allocatedNumChars >= numChars) return text; juce_wchar* const newText = createUninitialised (jmax (b->allocatedNumChars, numChars)); copyChars (newText, text, b->allocatedNumChars); release (b); return newText; } static size_t getAllocatedNumChars (juce_wchar* const text) throw() { return bufferFromText (text)->allocatedNumChars; } static void copyChars (juce_wchar* const dest, const juce_wchar* const src, const size_t numChars) throw() { memcpy (dest, src, numChars * sizeof (juce_wchar)); dest [numChars] = 0; } Atomic refCount; size_t allocatedNumChars; juce_wchar text[1]; static StringHolder empty; private: static inline StringHolder* bufferFromText (void* const text) throw() { // (Can't use offsetof() here because of warnings about this not being a POD) return reinterpret_cast (static_cast (text) - (reinterpret_cast (reinterpret_cast (1)->text) - 1)); } }; StringHolder StringHolder::empty; const String String::empty; void String::createInternal (const juce_wchar* const t, const size_t numChars) { jassert (t[numChars] == 0); // must have a null terminator text = StringHolder::createCopy (t, numChars); } void String::appendInternal (const juce_wchar* const newText, const int numExtraChars) { if (numExtraChars > 0) { const int oldLen = length(); const int newTotalLen = oldLen + numExtraChars; text = StringHolder::makeUniqueWithSize (text, newTotalLen); StringHolder::copyChars (text + oldLen, newText, numExtraChars); } } void String::preallocateStorage (const size_t numChars) { text = StringHolder::makeUniqueWithSize (text, numChars); } String::String() throw() : text (StringHolder::getEmpty()) { } String::~String() throw() { StringHolder::release (text); } String::String (const String& other) throw() : text (other.text) { StringHolder::retain (text); } void String::swapWith (String& other) throw() { swapVariables (text, other.text); } String& String::operator= (const String& other) throw() { juce_wchar* const newText = other.text; StringHolder::retain (newText); StringHolder::release (reinterpret_cast *> (&text)->exchange (newText)); return *this; } String::String (const size_t numChars, const int /*dummyVariable*/) : text (StringHolder::createUninitialised (numChars)) { } String::String (const String& stringToCopy, const size_t charsToAllocate) { const size_t otherSize = StringHolder::getAllocatedNumChars (stringToCopy.text); text = StringHolder::createUninitialised (jmax (charsToAllocate, otherSize)); StringHolder::copyChars (text, stringToCopy.text, otherSize); } String::String (const char* const t) { if (t != 0 && *t != 0) text = StringHolder::createCopy (t, CharacterFunctions::length (t)); else text = StringHolder::getEmpty(); } String::String (const juce_wchar* const t) { if (t != 0 && *t != 0) text = StringHolder::createCopy (t, CharacterFunctions::length (t)); else text = StringHolder::getEmpty(); } String::String (const char* const t, const size_t maxChars) { int i; for (i = 0; (size_t) i < maxChars; ++i) if (t[i] == 0) break; if (i > 0) text = StringHolder::createCopy (t, i); else text = StringHolder::getEmpty(); } String::String (const juce_wchar* const t, const size_t maxChars) { int i; for (i = 0; (size_t) i < maxChars; ++i) if (t[i] == 0) break; if (i > 0) text = StringHolder::createCopy (t, i); else text = StringHolder::getEmpty(); } const String String::charToString (const juce_wchar character) { String result ((size_t) 1, (int) 0); result.text[0] = character; result.text[1] = 0; return result; } namespace NumberToStringConverters { // pass in a pointer to the END of a buffer.. static juce_wchar* int64ToString (juce_wchar* t, const int64 n) throw() { *--t = 0; int64 v = (n >= 0) ? n : -n; do { *--t = (juce_wchar) ('0' + (int) (v % 10)); v /= 10; } while (v > 0); if (n < 0) *--t = '-'; return t; } static juce_wchar* uint64ToString (juce_wchar* t, int64 v) throw() { *--t = 0; do { *--t = (juce_wchar) ('0' + (int) (v % 10)); v /= 10; } while (v > 0); return t; } static juce_wchar* intToString (juce_wchar* t, const int n) throw() { if (n == (int) 0x80000000) // (would cause an overflow) return int64ToString (t, n); *--t = 0; int v = abs (n); do { *--t = (juce_wchar) ('0' + (v % 10)); v /= 10; } while (v > 0); if (n < 0) *--t = '-'; return t; } static juce_wchar* uintToString (juce_wchar* t, unsigned int v) throw() { *--t = 0; do { *--t = (juce_wchar) ('0' + (v % 10)); v /= 10; } while (v > 0); return t; } static juce_wchar getDecimalPoint() { #if JUCE_WINDOWS && _MSC_VER < 1400 static juce_wchar dp = std::_USE (std::locale(), std::numpunct ).decimal_point(); #else static juce_wchar dp = std::use_facet > (std::locale()).decimal_point(); #endif return dp; } static juce_wchar* doubleToString (juce_wchar* buffer, int numChars, double n, int numDecPlaces, size_t& len) throw() { if (numDecPlaces > 0 && n > -1.0e20 && n < 1.0e20) { juce_wchar* const end = buffer + numChars; juce_wchar* t = end; int64 v = (int64) (pow (10.0, numDecPlaces) * std::abs (n) + 0.5); *--t = (juce_wchar) 0; while (numDecPlaces >= 0 || v > 0) { if (numDecPlaces == 0) *--t = getDecimalPoint(); *--t = (juce_wchar) ('0' + (v % 10)); v /= 10; --numDecPlaces; } if (n < 0) *--t = '-'; len = end - t - 1; return t; } else { #if JUCE_WINDOWS #if _MSC_VER <= 1400 len = _snwprintf (buffer, numChars, L"%.9g", n); #else len = _snwprintf_s (buffer, numChars, _TRUNCATE, L"%.9g", n); #endif #else len = swprintf (buffer, numChars, L"%.9g", n); #endif return buffer; } } } String::String (const int number) { juce_wchar buffer [16]; juce_wchar* const end = buffer + numElementsInArray (buffer); juce_wchar* const start = NumberToStringConverters::intToString (end, number); createInternal (start, end - start - 1); } String::String (const unsigned int number) { juce_wchar buffer [16]; juce_wchar* const end = buffer + numElementsInArray (buffer); juce_wchar* const start = NumberToStringConverters::uintToString (end, number); createInternal (start, end - start - 1); } String::String (const short number) { juce_wchar buffer [16]; juce_wchar* const end = buffer + numElementsInArray (buffer); juce_wchar* const start = NumberToStringConverters::intToString (end, (int) number); createInternal (start, end - start - 1); } String::String (const unsigned short number) { juce_wchar buffer [16]; juce_wchar* const end = buffer + numElementsInArray (buffer); juce_wchar* const start = NumberToStringConverters::uintToString (end, (unsigned int) number); createInternal (start, end - start - 1); } String::String (const int64 number) { juce_wchar buffer [32]; juce_wchar* const end = buffer + numElementsInArray (buffer); juce_wchar* const start = NumberToStringConverters::int64ToString (end, number); createInternal (start, end - start - 1); } String::String (const uint64 number) { juce_wchar buffer [32]; juce_wchar* const end = buffer + numElementsInArray (buffer); juce_wchar* const start = NumberToStringConverters::uint64ToString (end, number); createInternal (start, end - start - 1); } String::String (const float number, const int numberOfDecimalPlaces) { juce_wchar buffer [48]; size_t len; juce_wchar* start = NumberToStringConverters::doubleToString (buffer, numElementsInArray (buffer), (double) number, numberOfDecimalPlaces, len); createInternal (start, len); } String::String (const double number, const int numberOfDecimalPlaces) { juce_wchar buffer [48]; size_t len; juce_wchar* start = NumberToStringConverters::doubleToString (buffer, numElementsInArray (buffer), number, numberOfDecimalPlaces, len); createInternal (start, len); } int String::length() const throw() { return CharacterFunctions::length (text); } int String::hashCode() const throw() { const juce_wchar* t = text; int result = 0; while (*t != (juce_wchar) 0) result = 31 * result + *t++; return result; } int64 String::hashCode64() const throw() { const juce_wchar* t = text; int64 result = 0; while (*t != (juce_wchar) 0) result = 101 * result + *t++; return result; } bool JUCE_PUBLIC_FUNCTION operator== (const String& string1, const String& string2) throw() { return string1.compare (string2) == 0; } bool JUCE_PUBLIC_FUNCTION operator== (const String& string1, const char* string2) throw() { return string1.compare (string2) == 0; } bool JUCE_PUBLIC_FUNCTION operator== (const String& string1, const juce_wchar* string2) throw() { return string1.compare (string2) == 0; } bool JUCE_PUBLIC_FUNCTION operator!= (const String& string1, const String& string2) throw() { return string1.compare (string2) != 0; } bool JUCE_PUBLIC_FUNCTION operator!= (const String& string1, const char* string2) throw() { return string1.compare (string2) != 0; } bool JUCE_PUBLIC_FUNCTION operator!= (const String& string1, const juce_wchar* string2) throw() { return string1.compare (string2) != 0; } bool JUCE_PUBLIC_FUNCTION operator> (const String& string1, const String& string2) throw() { return string1.compare (string2) > 0; } bool JUCE_PUBLIC_FUNCTION operator< (const String& string1, const String& string2) throw() { return string1.compare (string2) < 0; } bool JUCE_PUBLIC_FUNCTION operator>= (const String& string1, const String& string2) throw() { return string1.compare (string2) >= 0; } bool JUCE_PUBLIC_FUNCTION operator<= (const String& string1, const String& string2) throw() { return string1.compare (string2) <= 0; } bool String::equalsIgnoreCase (const juce_wchar* t) const throw() { return t != 0 ? CharacterFunctions::compareIgnoreCase (text, t) == 0 : isEmpty(); } bool String::equalsIgnoreCase (const char* t) const throw() { return t != 0 ? CharacterFunctions::compareIgnoreCase (text, t) == 0 : isEmpty(); } bool String::equalsIgnoreCase (const String& other) const throw() { return text == other.text || CharacterFunctions::compareIgnoreCase (text, other.text) == 0; } int String::compare (const String& other) const throw() { return (text == other.text) ? 0 : CharacterFunctions::compare (text, other.text); } int String::compare (const char* other) const throw() { return other == 0 ? isEmpty() : CharacterFunctions::compare (text, other); } int String::compare (const juce_wchar* other) const throw() { return other == 0 ? isEmpty() : CharacterFunctions::compare (text, other); } int String::compareIgnoreCase (const String& other) const throw() { return (text == other.text) ? 0 : CharacterFunctions::compareIgnoreCase (text, other.text); } int String::compareLexicographically (const String& other) const throw() { const juce_wchar* s1 = text; while (*s1 != 0 && ! CharacterFunctions::isLetterOrDigit (*s1)) ++s1; const juce_wchar* s2 = other.text; while (*s2 != 0 && ! CharacterFunctions::isLetterOrDigit (*s2)) ++s2; return CharacterFunctions::compareIgnoreCase (s1, s2); } String& String::operator+= (const juce_wchar* const t) { if (t != 0) appendInternal (t, CharacterFunctions::length (t)); return *this; } String& String::operator+= (const String& other) { if (isEmpty()) operator= (other); else appendInternal (other.text, other.length()); return *this; } String& String::operator+= (const char ch) { const juce_wchar asString[] = { (juce_wchar) ch, 0 }; return operator+= (static_cast (asString)); } String& String::operator+= (const juce_wchar ch) { const juce_wchar asString[] = { (juce_wchar) ch, 0 }; return operator+= (static_cast (asString)); } String& String::operator+= (const int number) { juce_wchar buffer [16]; juce_wchar* const end = buffer + numElementsInArray (buffer); juce_wchar* const start = NumberToStringConverters::intToString (end, number); appendInternal (start, (int) (end - start)); return *this; } String& String::operator+= (const unsigned int number) { juce_wchar buffer [16]; juce_wchar* const end = buffer + numElementsInArray (buffer); juce_wchar* const start = NumberToStringConverters::uintToString (end, number); appendInternal (start, (int) (end - start)); return *this; } void String::append (const juce_wchar* const other, const int howMany) { if (howMany > 0) { int i; for (i = 0; i < howMany; ++i) if (other[i] == 0) break; appendInternal (other, i); } } const String JUCE_PUBLIC_FUNCTION operator+ (const char* const string1, const String& string2) { String s (string1); return s += string2; } const String JUCE_PUBLIC_FUNCTION operator+ (const juce_wchar* const string1, const String& string2) { String s (string1); return s += string2; } const String JUCE_PUBLIC_FUNCTION operator+ (const char string1, const String& string2) { return String::charToString (string1) + string2; } const String JUCE_PUBLIC_FUNCTION operator+ (const juce_wchar string1, const String& string2) { return String::charToString (string1) + string2; } const String JUCE_PUBLIC_FUNCTION operator+ (String string1, const String& string2) { return string1 += string2; } const String JUCE_PUBLIC_FUNCTION operator+ (String string1, const char* const string2) { return string1 += string2; } const String JUCE_PUBLIC_FUNCTION operator+ (String string1, const juce_wchar* const string2) { return string1 += string2; } const String JUCE_PUBLIC_FUNCTION operator+ (String string1, const char string2) { return string1 += string2; } const String JUCE_PUBLIC_FUNCTION operator+ (String string1, const juce_wchar string2) { return string1 += string2; } String& JUCE_PUBLIC_FUNCTION operator<< (String& string1, const char characterToAppend) { return string1 += characterToAppend; } String& JUCE_PUBLIC_FUNCTION operator<< (String& string1, const juce_wchar characterToAppend) { return string1 += characterToAppend; } String& JUCE_PUBLIC_FUNCTION operator<< (String& string1, const char* const string2) { return string1 += string2; } String& JUCE_PUBLIC_FUNCTION operator<< (String& string1, const juce_wchar* const string2) { return string1 += string2; } String& JUCE_PUBLIC_FUNCTION operator<< (String& string1, const String& string2) { return string1 += string2; } String& JUCE_PUBLIC_FUNCTION operator<< (String& string1, const short number) { return string1 += (int) number; } String& JUCE_PUBLIC_FUNCTION operator<< (String& string1, const int number) { return string1 += number; } String& JUCE_PUBLIC_FUNCTION operator<< (String& string1, const unsigned int number) { return string1 += number; } String& JUCE_PUBLIC_FUNCTION operator<< (String& string1, const long number) { return string1 += (int) number; } String& JUCE_PUBLIC_FUNCTION operator<< (String& string1, const unsigned long number) { return string1 += (unsigned int) number; } String& JUCE_PUBLIC_FUNCTION operator<< (String& string1, const float number) { return string1 += String (number); } String& JUCE_PUBLIC_FUNCTION operator<< (String& string1, const double number) { return string1 += String (number); } OutputStream& JUCE_PUBLIC_FUNCTION operator<< (OutputStream& stream, const String& text) { // (This avoids using toUTF8() to prevent the memory bloat that it would leave behind // if lots of large, persistent strings were to be written to streams). const int numBytes = text.getNumBytesAsUTF8(); HeapBlock temp (numBytes + 1); text.copyToUTF8 (temp, numBytes + 1); stream.write (temp, numBytes); return stream; } int String::indexOfChar (const juce_wchar character) const throw() { const juce_wchar* t = text; for (;;) { if (*t == character) return (int) (t - text); if (*t++ == 0) return -1; } } int String::lastIndexOfChar (const juce_wchar character) const throw() { for (int i = length(); --i >= 0;) if (text[i] == character) return i; return -1; } int String::indexOf (const String& t) const throw() { const juce_wchar* const r = CharacterFunctions::find (text, t.text); return r == 0 ? -1 : (int) (r - text); } int String::indexOfChar (const int startIndex, const juce_wchar character) const throw() { if (startIndex > 0 && startIndex >= length()) return -1; const juce_wchar* t = text + jmax (0, startIndex); for (;;) { if (*t == character) return (int) (t - text); if (*t == 0) return -1; ++t; } } int String::indexOfAnyOf (const String& charactersToLookFor, const int startIndex, const bool ignoreCase) const throw() { if (startIndex > 0 && startIndex >= length()) return -1; const juce_wchar* t = text + jmax (0, startIndex); while (*t != 0) { if (CharacterFunctions::indexOfChar (charactersToLookFor.text, *t, ignoreCase) >= 0) return (int) (t - text); ++t; } return -1; } int String::indexOf (const int startIndex, const String& other) const throw() { if (startIndex > 0 && startIndex >= length()) return -1; const juce_wchar* const found = CharacterFunctions::find (text + jmax (0, startIndex), other.text); return found == 0 ? -1 : (int) (found - text); } int String::indexOfIgnoreCase (const String& other) const throw() { if (other.isNotEmpty()) { const int len = other.length(); const int end = length() - len; for (int i = 0; i <= end; ++i) if (CharacterFunctions::compareIgnoreCase (text + i, other.text, len) == 0) return i; } return -1; } int String::indexOfIgnoreCase (const int startIndex, const String& other) const throw() { if (other.isNotEmpty()) { const int len = other.length(); const int end = length() - len; for (int i = jmax (0, startIndex); i <= end; ++i) if (CharacterFunctions::compareIgnoreCase (text + i, other.text, len) == 0) return i; } return -1; } int String::lastIndexOf (const String& other) const throw() { if (other.isNotEmpty()) { const int len = other.length(); int i = length() - len; if (i >= 0) { const juce_wchar* n = text + i; while (i >= 0) { if (CharacterFunctions::compare (n--, other.text, len) == 0) return i; --i; } } } return -1; } int String::lastIndexOfIgnoreCase (const String& other) const throw() { if (other.isNotEmpty()) { const int len = other.length(); int i = length() - len; if (i >= 0) { const juce_wchar* n = text + i; while (i >= 0) { if (CharacterFunctions::compareIgnoreCase (n--, other.text, len) == 0) return i; --i; } } } return -1; } int String::lastIndexOfAnyOf (const String& charactersToLookFor, const bool ignoreCase) const throw() { for (int i = length(); --i >= 0;) if (CharacterFunctions::indexOfChar (charactersToLookFor.text, text[i], ignoreCase) >= 0) return i; return -1; } bool String::contains (const String& other) const throw() { return indexOf (other) >= 0; } bool String::containsChar (const juce_wchar character) const throw() { const juce_wchar* t = text; for (;;) { if (*t == 0) return false; if (*t == character) return true; ++t; } } bool String::containsIgnoreCase (const String& t) const throw() { return indexOfIgnoreCase (t) >= 0; } int String::indexOfWholeWord (const String& word) const throw() { if (word.isNotEmpty()) { const int wordLen = word.length(); const int end = length() - wordLen; const juce_wchar* t = text; for (int i = 0; i <= end; ++i) { if (CharacterFunctions::compare (t, word.text, wordLen) == 0 && (i == 0 || ! CharacterFunctions::isLetterOrDigit (* (t - 1))) && ! CharacterFunctions::isLetterOrDigit (t [wordLen])) { return i; } ++t; } } return -1; } int String::indexOfWholeWordIgnoreCase (const String& word) const throw() { if (word.isNotEmpty()) { const int wordLen = word.length(); const int end = length() - wordLen; const juce_wchar* t = text; for (int i = 0; i <= end; ++i) { if (CharacterFunctions::compareIgnoreCase (t, word.text, wordLen) == 0 && (i == 0 || ! CharacterFunctions::isLetterOrDigit (* (t - 1))) && ! CharacterFunctions::isLetterOrDigit (t [wordLen])) { return i; } ++t; } } return -1; } bool String::containsWholeWord (const String& wordToLookFor) const throw() { return indexOfWholeWord (wordToLookFor) >= 0; } bool String::containsWholeWordIgnoreCase (const String& wordToLookFor) const throw() { return indexOfWholeWordIgnoreCase (wordToLookFor) >= 0; } static int indexOfMatch (const juce_wchar* const wildcard, const juce_wchar* const test, const bool ignoreCase) throw() { int start = 0; while (test [start] != 0) { int i = 0; for (;;) { const juce_wchar wc = wildcard [i]; const juce_wchar c = test [i + start]; if (wc == c || (ignoreCase && CharacterFunctions::toLowerCase (wc) == CharacterFunctions::toLowerCase (c)) || (wc == '?' && c != 0)) { if (wc == 0) return start; ++i; } else { if (wc == '*' && (wildcard [i + 1] == 0 || indexOfMatch (wildcard + i + 1, test + start + i, ignoreCase) >= 0)) { return start; } break; } } ++start; } return -1; } bool String::matchesWildcard (const String& wildcard, const bool ignoreCase) const throw() { int i = 0; for (;;) { const juce_wchar wc = wildcard.text [i]; const juce_wchar c = text [i]; if (wc == c || (ignoreCase && CharacterFunctions::toLowerCase (wc) == CharacterFunctions::toLowerCase (c)) || (wc == '?' && c != 0)) { if (wc == 0) return true; ++i; } else { return wc == '*' && (wildcard [i + 1] == 0 || indexOfMatch (wildcard.text + i + 1, text + i, ignoreCase) >= 0); } } } const String String::repeatedString (const String& stringToRepeat, int numberOfTimesToRepeat) { const int len = stringToRepeat.length(); String result ((size_t) (len * numberOfTimesToRepeat + 1), (int) 0); juce_wchar* n = result.text; *n = 0; while (--numberOfTimesToRepeat >= 0) { StringHolder::copyChars (n, stringToRepeat.text, len); n += len; } return result; } const String String::paddedLeft (const juce_wchar padCharacter, int minimumLength) const { jassert (padCharacter != 0); const int len = length(); if (len >= minimumLength || padCharacter == 0) return *this; String result ((size_t) minimumLength + 1, (int) 0); juce_wchar* n = result.text; minimumLength -= len; while (--minimumLength >= 0) *n++ = padCharacter; StringHolder::copyChars (n, text, len); return result; } const String String::paddedRight (const juce_wchar padCharacter, int minimumLength) const { jassert (padCharacter != 0); const int len = length(); if (len >= minimumLength || padCharacter == 0) return *this; String result (*this, (size_t) minimumLength); juce_wchar* n = result.text + len; minimumLength -= len; while (--minimumLength >= 0) *n++ = padCharacter; *n = 0; return result; } const String String::replaceSection (int index, int numCharsToReplace, const String& stringToInsert) const { if (index < 0) { // a negative index to replace from? jassertfalse; index = 0; } if (numCharsToReplace < 0) { // replacing a negative number of characters? numCharsToReplace = 0; jassertfalse; } const int len = length(); if (index + numCharsToReplace > len) { if (index > len) { // replacing beyond the end of the string? index = len; jassertfalse; } numCharsToReplace = len - index; } const int newStringLen = stringToInsert.length(); const int newTotalLen = len + newStringLen - numCharsToReplace; if (newTotalLen <= 0) return String::empty; String result ((size_t) newTotalLen, (int) 0); StringHolder::copyChars (result.text, text, index); if (newStringLen > 0) StringHolder::copyChars (result.text + index, stringToInsert.text, newStringLen); const int endStringLen = newTotalLen - (index + newStringLen); if (endStringLen > 0) StringHolder::copyChars (result.text + (index + newStringLen), text + (index + numCharsToReplace), endStringLen); return result; } const String String::replace (const String& stringToReplace, const String& stringToInsert, const bool ignoreCase) const { const int stringToReplaceLen = stringToReplace.length(); const int stringToInsertLen = stringToInsert.length(); int i = 0; String result (*this); while ((i = (ignoreCase ? result.indexOfIgnoreCase (i, stringToReplace) : result.indexOf (i, stringToReplace))) >= 0) { result = result.replaceSection (i, stringToReplaceLen, stringToInsert); i += stringToInsertLen; } return result; } const String String::replaceCharacter (const juce_wchar charToReplace, const juce_wchar charToInsert) const { const int index = indexOfChar (charToReplace); if (index < 0) return *this; String result (*this, size_t()); juce_wchar* t = result.text + index; while (*t != 0) { if (*t == charToReplace) *t = charToInsert; ++t; } return result; } const String String::replaceCharacters (const String& charactersToReplace, const String& charactersToInsertInstead) const { String result (*this, size_t()); juce_wchar* t = result.text; const int len2 = charactersToInsertInstead.length(); // the two strings passed in are supposed to be the same length! jassert (len2 == charactersToReplace.length()); while (*t != 0) { const int index = charactersToReplace.indexOfChar (*t); if (((unsigned int) index) < (unsigned int) len2) *t = charactersToInsertInstead [index]; ++t; } return result; } bool String::startsWith (const String& other) const throw() { return CharacterFunctions::compare (text, other.text, other.length()) == 0; } bool String::startsWithIgnoreCase (const String& other) const throw() { return CharacterFunctions::compareIgnoreCase (text, other.text, other.length()) == 0; } bool String::startsWithChar (const juce_wchar character) const throw() { jassert (character != 0); // strings can't contain a null character! return text[0] == character; } bool String::endsWithChar (const juce_wchar character) const throw() { jassert (character != 0); // strings can't contain a null character! return text[0] != 0 && text [length() - 1] == character; } bool String::endsWith (const String& other) const throw() { const int thisLen = length(); const int otherLen = other.length(); return thisLen >= otherLen && CharacterFunctions::compare (text + thisLen - otherLen, other.text) == 0; } bool String::endsWithIgnoreCase (const String& other) const throw() { const int thisLen = length(); const int otherLen = other.length(); return thisLen >= otherLen && CharacterFunctions::compareIgnoreCase (text + thisLen - otherLen, other.text) == 0; } const String String::toUpperCase() const { String result (*this, size_t()); CharacterFunctions::toUpperCase (result.text); return result; } const String String::toLowerCase() const { String result (*this, size_t()); CharacterFunctions::toLowerCase (result.text); return result; } juce_wchar& String::operator[] (const int index) { jassert (((unsigned int) index) <= (unsigned int) length()); text = StringHolder::makeUnique (text); return text [index]; } juce_wchar String::getLastCharacter() const throw() { return isEmpty() ? juce_wchar() : text [length() - 1]; } const String String::substring (int start, int end) const { if (start < 0) start = 0; else if (end <= start) return empty; int len = 0; while (len <= end && text [len] != 0) ++len; if (end >= len) { if (start == 0) return *this; end = len; } return String (text + start, end - start); } const String String::substring (const int start) const { if (start <= 0) return *this; const int len = length(); if (start >= len) return empty; return String (text + start, len - start); } const String String::dropLastCharacters (const int numberToDrop) const { return String (text, jmax (0, length() - numberToDrop)); } const String String::getLastCharacters (const int numCharacters) const { return String (text + jmax (0, length() - jmax (0, numCharacters))); } const String String::fromFirstOccurrenceOf (const String& sub, const bool includeSubString, const bool ignoreCase) const { const int i = ignoreCase ? indexOfIgnoreCase (sub) : indexOf (sub); if (i < 0) return empty; return substring (includeSubString ? i : i + sub.length()); } const String String::fromLastOccurrenceOf (const String& sub, const bool includeSubString, const bool ignoreCase) const { const int i = ignoreCase ? lastIndexOfIgnoreCase (sub) : lastIndexOf (sub); if (i < 0) return *this; return substring (includeSubString ? i : i + sub.length()); } const String String::upToFirstOccurrenceOf (const String& sub, const bool includeSubString, const bool ignoreCase) const { const int i = ignoreCase ? indexOfIgnoreCase (sub) : indexOf (sub); if (i < 0) return *this; return substring (0, includeSubString ? i + sub.length() : i); } const String String::upToLastOccurrenceOf (const String& sub, const bool includeSubString, const bool ignoreCase) const { const int i = ignoreCase ? lastIndexOfIgnoreCase (sub) : lastIndexOf (sub); if (i < 0) return *this; return substring (0, includeSubString ? i + sub.length() : i); } bool String::isQuotedString() const { const String trimmed (trimStart()); return trimmed[0] == '"' || trimmed[0] == '\''; } const String String::unquoted() const { String s (*this); if (s.text[0] == '"' || s.text[0] == '\'') s = s.substring (1); const int lastCharIndex = s.length() - 1; if (lastCharIndex >= 0 && (s [lastCharIndex] == '"' || s[lastCharIndex] == '\'')) s [lastCharIndex] = 0; return s; } const String String::quoted (const juce_wchar quoteCharacter) const { if (isEmpty()) return charToString (quoteCharacter) + quoteCharacter; String t (*this); if (! t.startsWithChar (quoteCharacter)) t = charToString (quoteCharacter) + t; if (! t.endsWithChar (quoteCharacter)) t += quoteCharacter; return t; } const String String::trim() const { if (isEmpty()) return empty; int start = 0; while (CharacterFunctions::isWhitespace (text [start])) ++start; const int len = length(); int end = len - 1; while ((end >= start) && CharacterFunctions::isWhitespace (text [end])) --end; ++end; if (end <= start) return empty; else if (start > 0 || end < len) return String (text + start, end - start); return *this; } const String String::trimStart() const { if (isEmpty()) return empty; const juce_wchar* t = text; while (CharacterFunctions::isWhitespace (*t)) ++t; if (t == text) return *this; return String (t); } const String String::trimEnd() const { if (isEmpty()) return empty; const juce_wchar* endT = text + (length() - 1); while ((endT >= text) && CharacterFunctions::isWhitespace (*endT)) --endT; return String (text, (int) (++endT - text)); } const String String::trimCharactersAtStart (const String& charactersToTrim) const { const juce_wchar* t = text; while (charactersToTrim.containsChar (*t)) ++t; return t == text ? *this : String (t); } const String String::trimCharactersAtEnd (const String& charactersToTrim) const { if (isEmpty()) return empty; const int len = length(); const juce_wchar* endT = text + (len - 1); int numToRemove = 0; while (numToRemove < len && charactersToTrim.containsChar (*endT)) { ++numToRemove; --endT; } return numToRemove > 0 ? String (text, len - numToRemove) : *this; } const String String::retainCharacters (const String& charactersToRetain) const { if (isEmpty()) return empty; String result (StringHolder::getAllocatedNumChars (text), (int) 0); juce_wchar* dst = result.text; const juce_wchar* src = text; while (*src != 0) { if (charactersToRetain.containsChar (*src)) *dst++ = *src; ++src; } *dst = 0; return result; } const String String::removeCharacters (const String& charactersToRemove) const { if (isEmpty()) return empty; String result (StringHolder::getAllocatedNumChars (text), (int) 0); juce_wchar* dst = result.text; const juce_wchar* src = text; while (*src != 0) { if (! charactersToRemove.containsChar (*src)) *dst++ = *src; ++src; } *dst = 0; return result; } const String String::initialSectionContainingOnly (const String& permittedCharacters) const { int i = 0; for (;;) { if (! permittedCharacters.containsChar (text[i])) break; ++i; } return substring (0, i); } const String String::initialSectionNotContaining (const String& charactersToStopAt) const { const juce_wchar* const t = text; int i = 0; while (t[i] != 0) { if (charactersToStopAt.containsChar (t[i])) return String (text, i); ++i; } return empty; } bool String::containsOnly (const String& chars) const throw() { const juce_wchar* t = text; while (*t != 0) if (! chars.containsChar (*t++)) return false; return true; } bool String::containsAnyOf (const String& chars) const throw() { const juce_wchar* t = text; while (*t != 0) if (chars.containsChar (*t++)) return true; return false; } bool String::containsNonWhitespaceChars() const throw() { const juce_wchar* t = text; while (*t != 0) if (! CharacterFunctions::isWhitespace (*t++)) return true; return false; } const String String::formatted (const juce_wchar* const pf, ... ) { jassert (pf != 0); va_list args; va_start (args, pf); size_t bufferSize = 256; String result (bufferSize, (int) 0); result.text[0] = 0; for (;;) { #if JUCE_LINUX && JUCE_64BIT va_list tempArgs; va_copy (tempArgs, args); const int num = (int) vswprintf (result.text, bufferSize - 1, pf, tempArgs); va_end (tempArgs); #elif JUCE_WINDOWS #if JUCE_MSVC #pragma warning (push) #pragma warning (disable: 4996) #endif const int num = (int) _vsnwprintf (result.text, bufferSize - 1, pf, args); #if JUCE_MSVC #pragma warning (pop) #endif #else const int num = (int) vswprintf (result.text, bufferSize - 1, pf, args); #endif if (num > 0) return result; bufferSize += 256; if (num == 0 || bufferSize > 65536) // the upper limit is a sanity check to avoid situations where vprintf repeatedly break; // returns -1 because of an error rather than because it needs more space. result.preallocateStorage (bufferSize); } return empty; } int String::getIntValue() const throw() { return CharacterFunctions::getIntValue (text); } int String::getTrailingIntValue() const throw() { int n = 0; int mult = 1; const juce_wchar* t = text + length(); while (--t >= text) { const juce_wchar c = *t; if (! CharacterFunctions::isDigit (c)) { if (c == '-') n = -n; break; } n += mult * (c - '0'); mult *= 10; } return n; } int64 String::getLargeIntValue() const throw() { return CharacterFunctions::getInt64Value (text); } float String::getFloatValue() const throw() { return (float) CharacterFunctions::getDoubleValue (text); } double String::getDoubleValue() const throw() { return CharacterFunctions::getDoubleValue (text); } static const juce_wchar* const hexDigits = JUCE_T("0123456789abcdef"); const String String::toHexString (const int number) { juce_wchar buffer[32]; juce_wchar* const end = buffer + 32; juce_wchar* t = end; *--t = 0; unsigned int v = (unsigned int) number; do { *--t = hexDigits [v & 15]; v >>= 4; } while (v != 0); return String (t, (int) (((char*) end) - (char*) t) - 1); } const String String::toHexString (const int64 number) { juce_wchar buffer[32]; juce_wchar* const end = buffer + 32; juce_wchar* t = end; *--t = 0; uint64 v = (uint64) number; do { *--t = hexDigits [(int) (v & 15)]; v >>= 4; } while (v != 0); return String (t, (int) (((char*) end) - (char*) t)); } const String String::toHexString (const short number) { return toHexString ((int) (unsigned short) number); } const String String::toHexString (const unsigned char* data, const int size, const int groupSize) { if (size <= 0) return empty; int numChars = (size * 2) + 2; if (groupSize > 0) numChars += size / groupSize; String s ((size_t) numChars, (int) 0); juce_wchar* d = s.text; for (int i = 0; i < size; ++i) { *d++ = hexDigits [(*data) >> 4]; *d++ = hexDigits [(*data) & 0xf]; ++data; if (groupSize > 0 && (i % groupSize) == (groupSize - 1) && i < (size - 1)) *d++ = ' '; } *d = 0; return s; } int String::getHexValue32() const throw() { int result = 0; const juce_wchar* c = text; for (;;) { const int hexValue = CharacterFunctions::getHexDigitValue (*c); if (hexValue >= 0) result = (result << 4) | hexValue; else if (*c == 0) break; ++c; } return result; } int64 String::getHexValue64() const throw() { int64 result = 0; const juce_wchar* c = text; for (;;) { const int hexValue = CharacterFunctions::getHexDigitValue (*c); if (hexValue >= 0) result = (result << 4) | hexValue; else if (*c == 0) break; ++c; } return result; } const String String::createStringFromData (const void* const data_, const int size) { const char* const data = static_cast (data_); if (size <= 0 || data == 0) { return empty; } else if (size < 2) { return charToString (data[0]); } else if ((data[0] == (char)-2 && data[1] == (char)-1) || (data[0] == (char)-1 && data[1] == (char)-2)) { // assume it's 16-bit unicode const bool bigEndian = (data[0] == (char)-2); const int numChars = size / 2 - 1; String result; result.preallocateStorage (numChars + 2); const uint16* const src = (const uint16*) (data + 2); juce_wchar* const dst = const_cast (static_cast (result)); if (bigEndian) { for (int i = 0; i < numChars; ++i) dst[i] = (juce_wchar) ByteOrder::swapIfLittleEndian (src[i]); } else { for (int i = 0; i < numChars; ++i) dst[i] = (juce_wchar) ByteOrder::swapIfBigEndian (src[i]); } dst [numChars] = 0; return result; } else { return String::fromUTF8 (data, size); } } const char* String::toUTF8() const { if (isEmpty()) { return reinterpret_cast (text); } else { const int currentLen = length() + 1; const int utf8BytesNeeded = getNumBytesAsUTF8(); String* const mutableThis = const_cast (this); mutableThis->text = StringHolder::makeUniqueWithSize (mutableThis->text, currentLen + 1 + utf8BytesNeeded / sizeof (juce_wchar)); char* const otherCopy = reinterpret_cast (mutableThis->text + currentLen); #if JUCE_DEBUG // (This just avoids spurious warnings from valgrind about the uninitialised bytes at the end of the buffer..) *(juce_wchar*) (otherCopy + (utf8BytesNeeded & ~(sizeof (juce_wchar) - 1))) = 0; #endif copyToUTF8 (otherCopy, std::numeric_limits::max()); return otherCopy; } } int String::copyToUTF8 (char* const buffer, const int maxBufferSizeBytes) const throw() { jassert (maxBufferSizeBytes >= 0); // keep this value positive, or no characters will be copied! int num = 0, index = 0; for (;;) { const uint32 c = (uint32) text [index++]; if (c >= 0x80) { int numExtraBytes = 1; if (c >= 0x800) { ++numExtraBytes; if (c >= 0x10000) { ++numExtraBytes; if (c >= 0x200000) { ++numExtraBytes; if (c >= 0x4000000) ++numExtraBytes; } } } if (buffer != 0) { if (num + numExtraBytes >= maxBufferSizeBytes) { buffer [num++] = 0; break; } else { buffer [num++] = (uint8) ((0xff << (7 - numExtraBytes)) | (c >> (numExtraBytes * 6))); while (--numExtraBytes >= 0) buffer [num++] = (uint8) (0x80 | (0x3f & (c >> (numExtraBytes * 6)))); } } else { num += numExtraBytes + 1; } } else { if (buffer != 0) { if (num + 1 >= maxBufferSizeBytes) { buffer [num++] = 0; break; } buffer [num] = (uint8) c; } ++num; } if (c == 0) break; } return num; } int String::getNumBytesAsUTF8() const throw() { int num = 0; const juce_wchar* t = text; for (;;) { const uint32 c = (uint32) *t; if (c >= 0x80) { ++num; if (c >= 0x800) { ++num; if (c >= 0x10000) { ++num; if (c >= 0x200000) { ++num; if (c >= 0x4000000) ++num; } } } } else if (c == 0) break; ++num; ++t; } return num; } const String String::fromUTF8 (const char* const buffer, int bufferSizeBytes) { if (buffer == 0) return empty; if (bufferSizeBytes < 0) bufferSizeBytes = std::numeric_limits::max(); size_t numBytes; for (numBytes = 0; numBytes < (size_t) bufferSizeBytes; ++numBytes) if (buffer [numBytes] == 0) break; String result ((size_t) numBytes + 1, (int) 0); juce_wchar* dest = result.text; size_t i = 0; while (i < numBytes) { const char c = buffer [i++]; if (c < 0) { unsigned int mask = 0x7f; int bit = 0x40; int numExtraValues = 0; while (bit != 0 && (c & bit) != 0) { bit >>= 1; mask >>= 1; ++numExtraValues; } int n = (mask & (unsigned char) c); while (--numExtraValues >= 0 && i < (size_t) bufferSizeBytes) { const char nextByte = buffer[i]; if ((nextByte & 0xc0) != 0x80) break; n <<= 6; n |= (nextByte & 0x3f); ++i; } *dest++ = (juce_wchar) n; } else { *dest++ = (juce_wchar) c; } } *dest = 0; return result; } const char* String::toCString() const { if (isEmpty()) { return reinterpret_cast (text); } else { const int len = length(); String* const mutableThis = const_cast (this); mutableThis->text = StringHolder::makeUniqueWithSize (mutableThis->text, (len + 1) * 2); char* otherCopy = reinterpret_cast (mutableThis->text + len + 1); CharacterFunctions::copy (otherCopy, text, len); otherCopy [len] = 0; return otherCopy; } } #if JUCE_MSVC #pragma warning (push) #pragma warning (disable: 4514 4996) #endif int String::getNumBytesAsCString() const throw() { return (int) wcstombs (0, text, 0); } int String::copyToCString (char* destBuffer, const int maxBufferSizeBytes) const throw() { const int numBytes = (int) wcstombs (destBuffer, text, maxBufferSizeBytes); if (destBuffer != 0 && numBytes >= 0) destBuffer [numBytes] = 0; return numBytes; } #if JUCE_MSVC #pragma warning (pop) #endif void String::copyToUnicode (juce_wchar* const destBuffer, const int maxCharsToCopy) const throw() { StringHolder::copyChars (destBuffer, text, jmin (maxCharsToCopy, length())); } String::Concatenator::Concatenator (String& stringToAppendTo) : result (stringToAppendTo), nextIndex (stringToAppendTo.length()) { } String::Concatenator::~Concatenator() { } void String::Concatenator::append (const String& s) { const int len = s.length(); if (len > 0) { result.preallocateStorage (nextIndex + len); s.copyToUnicode (static_cast (result) + nextIndex, len); nextIndex += len; } } #if JUCE_UNIT_TESTS class StringTests : public UnitTest { public: StringTests() : UnitTest ("String class") {} void runTest() { { beginTest ("Basics"); expect (String().length() == 0); expect (String() == String::empty); String s1, s2 ("abcd"); expect (s1.isEmpty() && ! s1.isNotEmpty()); expect (s2.isNotEmpty() && ! s2.isEmpty()); expect (s2.length() == 4); s1 = "abcd"; expect (s2 == s1 && s1 == s2); expect (s1 == "abcd" && s1 == L"abcd"); expect (String ("abcd") == String (L"abcd")); expect (String ("abcdefg", 4) == L"abcd"); expect (String ("abcdefg", 4) == String (L"abcdefg", 4)); expect (String::charToString ('x') == "x"); expect (String::charToString (0) == String::empty); expect (s2 + "e" == "abcde" && s2 + 'e' == "abcde"); expect (s2 + L'e' == "abcde" && s2 + L"e" == "abcde"); expect (s1.equalsIgnoreCase ("abcD") && s1 < "abce" && s1 > "abbb"); expect (s1.startsWith ("ab") && s1.startsWith ("abcd") && ! s1.startsWith ("abcde")); expect (s1.startsWithIgnoreCase ("aB") && s1.endsWithIgnoreCase ("CD")); expect (s1.endsWith ("bcd") && ! s1.endsWith ("aabcd")); expect (s1.indexOf (String::empty) == 0); expect (s1.startsWith (String::empty) && s1.endsWith (String::empty) && s1.contains (String::empty)); expect (s1.contains ("cd") && s1.contains ("ab") && s1.contains ("abcd")); expect (s1.containsChar ('a') && ! s1.containsChar (0)); expect (String ("abc foo bar").containsWholeWord ("abc") && String ("abc foo bar").containsWholeWord ("abc")); } { beginTest ("Operations"); String s ("012345678"); expect (s.hashCode() != 0); expect (s.hashCode64() != 0); expect (s.hashCode() != (s + s).hashCode()); expect (s.hashCode64() != (s + s).hashCode64()); expect (s.compare (String ("012345678")) == 0); expect (s.compare (String ("012345679")) < 0); expect (s.compare (String ("012345676")) > 0); expect (s.substring (2, 3) == String::charToString (s[2])); expect (s.substring (0, 1) == String::charToString (s[0])); expect (s.getLastCharacter() == s [s.length() - 1]); expect (String::charToString (s.getLastCharacter()) == s.getLastCharacters (1)); expect (s.substring (0, 3) == L"012"); expect (s.substring (0, 100) == s); expect (s.substring (-1, 100) == s); expect (s.substring (3) == "345678"); expect (s.indexOf (L"45") == 4); expect (String ("444445").indexOf ("45") == 4); expect (String ("444445").lastIndexOfChar ('4') == 4); expect (String ("45454545x").lastIndexOf (L"45") == 6); expect (String ("45454545x").lastIndexOfAnyOf ("456") == 7); expect (String ("45454545x").lastIndexOfAnyOf (L"456x") == 8); expect (String ("abABaBaBa").lastIndexOfIgnoreCase ("Ab") == 6); expect (s.indexOfChar (L'4') == 4); expect (s + s == "012345678012345678"); expect (s.startsWith (s)); expect (s.startsWith (s.substring (0, 4))); expect (s.startsWith (s.dropLastCharacters (4))); expect (s.endsWith (s.substring (5))); expect (s.endsWith (s)); expect (s.contains (s.substring (3, 6))); expect (s.contains (s.substring (3))); expect (s.startsWithChar (s[0])); expect (s.endsWithChar (s.getLastCharacter())); expect (s [s.length()] == 0); expect (String ("abcdEFGH").toLowerCase() == String ("abcdefgh")); expect (String ("abcdEFGH").toUpperCase() == String ("ABCDEFGH")); String s2 ("123"); s2 << ((int) 4) << ((short) 5) << "678" << L"9" << '0'; s2 += "xyz"; expect (s2 == "1234567890xyz"); beginTest ("Numeric conversions"); expect (String::empty.getIntValue() == 0); expect (String::empty.getDoubleValue() == 0.0); expect (String::empty.getFloatValue() == 0.0f); expect (s.getIntValue() == 12345678); expect (s.getLargeIntValue() == (int64) 12345678); expect (s.getDoubleValue() == 12345678.0); expect (s.getFloatValue() == 12345678.0f); expect (String (-1234).getIntValue() == -1234); expect (String ((int64) -1234).getLargeIntValue() == -1234); expect (String (-1234.56).getDoubleValue() == -1234.56); expect (String (-1234.56f).getFloatValue() == -1234.56f); expect (("xyz" + s).getTrailingIntValue() == s.getIntValue()); expect (s.getHexValue32() == 0x12345678); expect (s.getHexValue64() == (int64) 0x12345678); expect (String::toHexString (0x1234abcd).equalsIgnoreCase ("1234abcd")); expect (String::toHexString ((int64) 0x1234abcd).equalsIgnoreCase ("1234abcd")); expect (String::toHexString ((short) 0x12ab).equalsIgnoreCase ("12ab")); unsigned char data[] = { 1, 2, 3, 4, 0xa, 0xb, 0xc, 0xd }; expect (String::toHexString (data, 8, 0).equalsIgnoreCase ("010203040a0b0c0d")); expect (String::toHexString (data, 8, 1).equalsIgnoreCase ("01 02 03 04 0a 0b 0c 0d")); expect (String::toHexString (data, 8, 2).equalsIgnoreCase ("0102 0304 0a0b 0c0d")); beginTest ("Subsections"); String s3; s3 = "abcdeFGHIJ"; expect (s3.equalsIgnoreCase ("ABCdeFGhiJ")); expect (s3.compareIgnoreCase (L"ABCdeFGhiJ") == 0); expect (s3.containsIgnoreCase (s3.substring (3))); expect (s3.indexOfAnyOf ("xyzf", 2, true) == 5); expect (s3.indexOfAnyOf (L"xyzf", 2, false) == -1); expect (s3.indexOfAnyOf ("xyzF", 2, false) == 5); expect (s3.containsAnyOf (L"zzzFs")); expect (s3.startsWith ("abcd")); expect (s3.startsWithIgnoreCase (L"abCD")); expect (s3.startsWith (String::empty)); expect (s3.startsWithChar ('a')); expect (s3.endsWith (String ("HIJ"))); expect (s3.endsWithIgnoreCase (L"Hij")); expect (s3.endsWith (String::empty)); expect (s3.endsWithChar (L'J')); expect (s3.indexOf ("HIJ") == 7); expect (s3.indexOf (L"HIJK") == -1); expect (s3.indexOfIgnoreCase ("hij") == 7); expect (s3.indexOfIgnoreCase (L"hijk") == -1); String s4 (s3); s4.append (String ("xyz123"), 3); expect (s4 == s3 + "xyz"); expect (String (1234) < String (1235)); expect (String (1235) > String (1234)); expect (String (1234) >= String (1234)); expect (String (1234) <= String (1234)); expect (String (1235) >= String (1234)); expect (String (1234) <= String (1235)); String s5 ("word word2 word3"); expect (s5.containsWholeWord (String ("word2"))); expect (s5.indexOfWholeWord ("word2") == 5); expect (s5.containsWholeWord (L"word")); expect (s5.containsWholeWord ("word3")); expect (s5.containsWholeWord (s5)); expect (s5.containsWholeWordIgnoreCase (L"Word2")); expect (s5.indexOfWholeWordIgnoreCase ("Word2") == 5); expect (s5.containsWholeWordIgnoreCase (L"Word")); expect (s5.containsWholeWordIgnoreCase ("Word3")); expect (! s5.containsWholeWordIgnoreCase (L"Wordx")); expect (!s5.containsWholeWordIgnoreCase ("xWord2")); expect (s5.containsNonWhitespaceChars()); expect (! String (" \n\r\t").containsNonWhitespaceChars()); expect (s5.matchesWildcard (L"wor*", false)); expect (s5.matchesWildcard ("wOr*", true)); expect (s5.matchesWildcard (L"*word3", true)); expect (s5.matchesWildcard ("*word?", true)); expect (s5.matchesWildcard (L"Word*3", true)); expect (s5.fromFirstOccurrenceOf (String::empty, true, false) == s5); expect (s5.fromFirstOccurrenceOf ("xword2", true, false) == s5.substring (100)); expect (s5.fromFirstOccurrenceOf (L"word2", true, false) == s5.substring (5)); expect (s5.fromFirstOccurrenceOf ("Word2", true, true) == s5.substring (5)); expect (s5.fromFirstOccurrenceOf ("word2", false, false) == s5.getLastCharacters (6)); expect (s5.fromFirstOccurrenceOf (L"Word2", false, true) == s5.getLastCharacters (6)); expect (s5.fromLastOccurrenceOf (String::empty, true, false) == s5); expect (s5.fromLastOccurrenceOf (L"wordx", true, false) == s5); expect (s5.fromLastOccurrenceOf ("word", true, false) == s5.getLastCharacters (5)); expect (s5.fromLastOccurrenceOf (L"worD", true, true) == s5.getLastCharacters (5)); expect (s5.fromLastOccurrenceOf ("word", false, false) == s5.getLastCharacters (1)); expect (s5.fromLastOccurrenceOf (L"worD", false, true) == s5.getLastCharacters (1)); expect (s5.upToFirstOccurrenceOf (String::empty, true, false).isEmpty()); expect (s5.upToFirstOccurrenceOf ("word4", true, false) == s5); expect (s5.upToFirstOccurrenceOf (L"word2", true, false) == s5.substring (0, 10)); expect (s5.upToFirstOccurrenceOf ("Word2", true, true) == s5.substring (0, 10)); expect (s5.upToFirstOccurrenceOf (L"word2", false, false) == s5.substring (0, 5)); expect (s5.upToFirstOccurrenceOf ("Word2", false, true) == s5.substring (0, 5)); expect (s5.upToLastOccurrenceOf (String::empty, true, false) == s5); expect (s5.upToLastOccurrenceOf ("zword", true, false) == s5); expect (s5.upToLastOccurrenceOf ("word", true, false) == s5.dropLastCharacters (1)); expect (s5.dropLastCharacters(1).upToLastOccurrenceOf ("word", true, false) == s5.dropLastCharacters (1)); expect (s5.upToLastOccurrenceOf ("Word", true, true) == s5.dropLastCharacters (1)); expect (s5.upToLastOccurrenceOf ("word", false, false) == s5.dropLastCharacters (5)); expect (s5.upToLastOccurrenceOf ("Word", false, true) == s5.dropLastCharacters (5)); expect (s5.replace ("word", L"xyz", false) == String ("xyz xyz2 xyz3")); expect (s5.replace (L"Word", "xyz", true) == "xyz xyz2 xyz3"); expect (s5.dropLastCharacters (1).replace ("Word", String ("xyz"), true) == L"xyz xyz2 xyz"); expect (s5.replace ("Word", "", true) == " 2 3"); expect (s5.replace ("Word2", L"xyz", true) == String ("word xyz word3")); expect (s5.replaceCharacter (L'w', 'x') != s5); expect (s5.replaceCharacter ('w', L'x').replaceCharacter ('x', 'w') == s5); expect (s5.replaceCharacters ("wo", "xy") != s5); expect (s5.replaceCharacters ("wo", "xy").replaceCharacters ("xy", L"wo") == s5); expect (s5.retainCharacters ("1wordxya") == String ("wordwordword")); expect (s5.retainCharacters (String::empty).isEmpty()); expect (s5.removeCharacters ("1wordxya") == " 2 3"); expect (s5.removeCharacters (String::empty) == s5); expect (s5.initialSectionContainingOnly ("word") == L"word"); expect (s5.initialSectionNotContaining (String ("xyz ")) == String ("word")); expect (! s5.isQuotedString()); expect (s5.quoted().isQuotedString()); expect (! s5.quoted().unquoted().isQuotedString()); expect (! String ("x'").isQuotedString()); expect (String ("'x").isQuotedString()); String s6 (" \t xyz \t\r\n"); expect (s6.trim() == String ("xyz")); expect (s6.trim().trim() == "xyz"); expect (s5.trim() == s5); expect (s6.trimStart().trimEnd() == s6.trim()); expect (s6.trimStart().trimEnd() == s6.trimEnd().trimStart()); expect (s6.trimStart().trimStart().trimEnd().trimEnd() == s6.trimEnd().trimStart()); expect (s6.trimStart() != s6.trimEnd()); expect (("\t\r\n " + s6 + "\t\n \r").trim() == s6.trim()); expect (String::repeatedString ("xyz", 3) == L"xyzxyzxyz"); } { beginTest ("UTF8"); String s ("word word2 word3"); { char buffer [100]; memset (buffer, 0xff, sizeof (buffer)); s.copyToUTF8 (buffer, 100); expect (String::fromUTF8 (buffer, 100) == s); juce_wchar bufferUnicode [100]; memset (bufferUnicode, 0xff, sizeof (bufferUnicode)); s.copyToUnicode (bufferUnicode, 100); expect (String (bufferUnicode, 100) == s); } { juce_wchar wideBuffer [50]; zerostruct (wideBuffer); for (int i = 0; i < numElementsInArray (wideBuffer) - 1; ++i) wideBuffer[i] = (juce_wchar) (1 + Random::getSystemRandom().nextInt (std::numeric_limits::max() - 1)); String wide (wideBuffer); expect (wide == (const juce_wchar*) wideBuffer); expect (wide.length() == numElementsInArray (wideBuffer) - 1); expect (String::fromUTF8 (wide.toUTF8()) == wide); expect (String::fromUTF8 (wide.toUTF8()) == wideBuffer); } } } }; static StringTests stringUnitTests; #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_String.cpp ***/ /*** Start of inlined file: juce_StringArray.cpp ***/ BEGIN_JUCE_NAMESPACE StringArray::StringArray() throw() { } StringArray::StringArray (const StringArray& other) : strings (other.strings) { } StringArray::StringArray (const String& firstValue) { strings.add (firstValue); } StringArray::StringArray (const juce_wchar* const* const initialStrings, const int numberOfStrings) { for (int i = 0; i < numberOfStrings; ++i) strings.add (initialStrings [i]); } StringArray::StringArray (const char* const* const initialStrings, const int numberOfStrings) { for (int i = 0; i < numberOfStrings; ++i) strings.add (initialStrings [i]); } StringArray::StringArray (const juce_wchar* const* const initialStrings) { int i = 0; while (initialStrings[i] != 0) strings.add (initialStrings [i++]); } StringArray::StringArray (const char* const* const initialStrings) { int i = 0; while (initialStrings[i] != 0) strings.add (initialStrings [i++]); } StringArray& StringArray::operator= (const StringArray& other) { strings = other.strings; return *this; } StringArray::~StringArray() { } bool StringArray::operator== (const StringArray& other) const throw() { if (other.size() != size()) return false; for (int i = size(); --i >= 0;) if (other.strings.getReference(i) != strings.getReference(i)) return false; return true; } bool StringArray::operator!= (const StringArray& other) const throw() { return ! operator== (other); } void StringArray::clear() { strings.clear(); } const String& StringArray::operator[] (const int index) const throw() { if (((unsigned int) index) < (unsigned int) strings.size()) return strings.getReference (index); return String::empty; } String& StringArray::getReference (const int index) throw() { jassert (((unsigned int) index) < (unsigned int) strings.size()); return strings.getReference (index); } void StringArray::add (const String& newString) { strings.add (newString); } void StringArray::insert (const int index, const String& newString) { strings.insert (index, newString); } void StringArray::addIfNotAlreadyThere (const String& newString, const bool ignoreCase) { if (! contains (newString, ignoreCase)) add (newString); } void StringArray::addArray (const StringArray& otherArray, int startIndex, int numElementsToAdd) { if (startIndex < 0) { jassertfalse; startIndex = 0; } if (numElementsToAdd < 0 || startIndex + numElementsToAdd > otherArray.size()) numElementsToAdd = otherArray.size() - startIndex; while (--numElementsToAdd >= 0) strings.add (otherArray.strings.getReference (startIndex++)); } void StringArray::set (const int index, const String& newString) { strings.set (index, newString); } bool StringArray::contains (const String& stringToLookFor, const bool ignoreCase) const { if (ignoreCase) { for (int i = size(); --i >= 0;) if (strings.getReference(i).equalsIgnoreCase (stringToLookFor)) return true; } else { for (int i = size(); --i >= 0;) if (stringToLookFor == strings.getReference(i)) return true; } return false; } int StringArray::indexOf (const String& stringToLookFor, const bool ignoreCase, int i) const { if (i < 0) i = 0; const int numElements = size(); if (ignoreCase) { while (i < numElements) { if (strings.getReference(i).equalsIgnoreCase (stringToLookFor)) return i; ++i; } } else { while (i < numElements) { if (stringToLookFor == strings.getReference (i)) return i; ++i; } } return -1; } void StringArray::remove (const int index) { strings.remove (index); } void StringArray::removeString (const String& stringToRemove, const bool ignoreCase) { if (ignoreCase) { for (int i = size(); --i >= 0;) if (strings.getReference(i).equalsIgnoreCase (stringToRemove)) strings.remove (i); } else { for (int i = size(); --i >= 0;) if (stringToRemove == strings.getReference (i)) strings.remove (i); } } void StringArray::removeRange (int startIndex, int numberToRemove) { strings.removeRange (startIndex, numberToRemove); } void StringArray::removeEmptyStrings (const bool removeWhitespaceStrings) { if (removeWhitespaceStrings) { for (int i = size(); --i >= 0;) if (! strings.getReference(i).containsNonWhitespaceChars()) strings.remove (i); } else { for (int i = size(); --i >= 0;) if (strings.getReference(i).isEmpty()) strings.remove (i); } } void StringArray::trim() { for (int i = size(); --i >= 0;) { String& s = strings.getReference(i); s = s.trim(); } } class InternalStringArrayComparator_CaseSensitive { public: static int compareElements (String& first, String& second) { return first.compare (second); } }; class InternalStringArrayComparator_CaseInsensitive { public: static int compareElements (String& first, String& second) { return first.compareIgnoreCase (second); } }; void StringArray::sort (const bool ignoreCase) { if (ignoreCase) { InternalStringArrayComparator_CaseInsensitive comp; strings.sort (comp); } else { InternalStringArrayComparator_CaseSensitive comp; strings.sort (comp); } } void StringArray::move (const int currentIndex, int newIndex) throw() { strings.move (currentIndex, newIndex); } const String StringArray::joinIntoString (const String& separator, int start, int numberToJoin) const { const int last = (numberToJoin < 0) ? size() : jmin (size(), start + numberToJoin); if (start < 0) start = 0; if (start >= last) return String::empty; if (start == last - 1) return strings.getReference (start); const int separatorLen = separator.length(); int charsNeeded = separatorLen * (last - start - 1); for (int i = start; i < last; ++i) charsNeeded += strings.getReference(i).length(); String result; result.preallocateStorage (charsNeeded); juce_wchar* dest = result; while (start < last) { const String& s = strings.getReference (start); const int len = s.length(); if (len > 0) { s.copyToUnicode (dest, len); dest += len; } if (++start < last && separatorLen > 0) { separator.copyToUnicode (dest, separatorLen); dest += separatorLen; } } *dest = 0; return result; } int StringArray::addTokens (const String& text, const bool preserveQuotedStrings) { return addTokens (text, " \n\r\t", preserveQuotedStrings ? "\"" : ""); } int StringArray::addTokens (const String& text, const String& breakCharacters, const String& quoteCharacters) { int num = 0; if (text.isNotEmpty()) { bool insideQuotes = false; juce_wchar currentQuoteChar = 0; int i = 0; int tokenStart = 0; for (;;) { const juce_wchar c = text[i]; const bool isBreak = (c == 0) || ((! insideQuotes) && breakCharacters.containsChar (c)); if (! isBreak) { if (quoteCharacters.containsChar (c)) { if (insideQuotes) { // only break out of quotes-mode if we find a matching quote to the // one that we opened with.. if (currentQuoteChar == c) insideQuotes = false; } else { insideQuotes = true; currentQuoteChar = c; } } } else { add (String (static_cast (text) + tokenStart, i - tokenStart)); ++num; tokenStart = i + 1; } if (c == 0) break; ++i; } } return num; } int StringArray::addLines (const String& sourceText) { int numLines = 0; const juce_wchar* text = sourceText; while (*text != 0) { const juce_wchar* const startOfLine = text; while (*text != 0) { if (*text == '\r') { ++text; if (*text == '\n') ++text; break; } if (*text == '\n') { ++text; break; } ++text; } const juce_wchar* endOfLine = text; if (endOfLine > startOfLine && (*(endOfLine - 1) == '\r' || *(endOfLine - 1) == '\n')) --endOfLine; if (endOfLine > startOfLine && (*(endOfLine - 1) == '\r' || *(endOfLine - 1) == '\n')) --endOfLine; add (String (startOfLine, jmax (0, (int) (endOfLine - startOfLine)))); ++numLines; } return numLines; } void StringArray::removeDuplicates (const bool ignoreCase) { for (int i = 0; i < size() - 1; ++i) { const String s (strings.getReference(i)); int nextIndex = i + 1; for (;;) { nextIndex = indexOf (s, ignoreCase, nextIndex); if (nextIndex < 0) break; strings.remove (nextIndex); } } } void StringArray::appendNumbersToDuplicates (const bool ignoreCase, const bool appendNumberToFirstInstance, const juce_wchar* preNumberString, const juce_wchar* postNumberString) { if (preNumberString == 0) preNumberString = L" ("; if (postNumberString == 0) postNumberString = L")"; for (int i = 0; i < size() - 1; ++i) { String& s = strings.getReference(i); int nextIndex = indexOf (s, ignoreCase, i + 1); if (nextIndex >= 0) { const String original (s); int number = 0; if (appendNumberToFirstInstance) s = original + preNumberString + String (++number) + postNumberString; else ++number; while (nextIndex >= 0) { set (nextIndex, (*this)[nextIndex] + preNumberString + String (++number) + postNumberString); nextIndex = indexOf (original, ignoreCase, nextIndex + 1); } } } } void StringArray::minimiseStorageOverheads() { strings.minimiseStorageOverheads(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_StringArray.cpp ***/ /*** Start of inlined file: juce_StringPairArray.cpp ***/ BEGIN_JUCE_NAMESPACE StringPairArray::StringPairArray (const bool ignoreCase_) : ignoreCase (ignoreCase_) { } StringPairArray::StringPairArray (const StringPairArray& other) : keys (other.keys), values (other.values), ignoreCase (other.ignoreCase) { } StringPairArray::~StringPairArray() { } StringPairArray& StringPairArray::operator= (const StringPairArray& other) { keys = other.keys; values = other.values; return *this; } bool StringPairArray::operator== (const StringPairArray& other) const { for (int i = keys.size(); --i >= 0;) if (other [keys[i]] != values[i]) return false; return true; } bool StringPairArray::operator!= (const StringPairArray& other) const { return ! operator== (other); } const String& StringPairArray::operator[] (const String& key) const { 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) { 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() { keys.clear(); values.clear(); } void StringPairArray::remove (const String& key) { remove (keys.indexOf (key, ignoreCase)); } void StringPairArray::remove (const int index) { keys.remove (index); values.remove (index); } void StringPairArray::setIgnoresCase (const bool shouldIgnoreCase) { ignoreCase = shouldIgnoreCase; } const String StringPairArray::getDescription() const { String s; for (int i = 0; i < keys.size(); ++i) { s << keys[i] << " = " << values[i]; if (i < keys.size()) s << ", "; } return s; } void StringPairArray::minimiseStorageOverheads() { keys.minimiseStorageOverheads(); values.minimiseStorageOverheads(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_StringPairArray.cpp ***/ /*** Start of inlined file: juce_StringPool.cpp ***/ BEGIN_JUCE_NAMESPACE StringPool::StringPool() throw() {} StringPool::~StringPool() {} template static const juce_wchar* getPooledStringFromArray (Array& strings, StringType newString) { int start = 0; int end = strings.size(); for (;;) { if (start >= end) { jassert (start <= end); strings.insert (start, newString); return strings.getReference (start); } else { const String& startString = strings.getReference (start); if (startString == newString) return startString; const int halfway = (start + end) >> 1; if (halfway == start) { if (startString.compare (newString) < 0) ++start; strings.insert (start, newString); return strings.getReference (start); } const int comp = strings.getReference (halfway).compare (newString); if (comp == 0) return strings.getReference (halfway); else if (comp < 0) start = halfway; else end = halfway; } } } const juce_wchar* StringPool::getPooledString (const String& s) { if (s.isEmpty()) return String::empty; return getPooledStringFromArray (strings, s); } const juce_wchar* StringPool::getPooledString (const char* const s) { if (s == 0 || *s == 0) return String::empty; return getPooledStringFromArray (strings, s); } const juce_wchar* StringPool::getPooledString (const juce_wchar* const s) { if (s == 0 || *s == 0) return String::empty; return getPooledStringFromArray (strings, s); } int StringPool::size() const throw() { return strings.size(); } const juce_wchar* StringPool::operator[] (const int index) const throw() { return strings [index]; } END_JUCE_NAMESPACE /*** End of inlined file: juce_StringPool.cpp ***/ /*** Start of inlined file: juce_XmlDocument.cpp ***/ BEGIN_JUCE_NAMESPACE XmlDocument::XmlDocument (const String& documentText) : originalText (documentText), ignoreEmptyTextElements (true) { } XmlDocument::XmlDocument (const File& file) : ignoreEmptyTextElements (true) { inputSource = new FileInputSource (file); } XmlDocument::~XmlDocument() { } void XmlDocument::setInputSource (InputSource* const newSource) throw() { inputSource = newSource; } void XmlDocument::setEmptyTextElementsIgnored (const bool shouldBeIgnored) throw() { ignoreEmptyTextElements = shouldBeIgnored; } bool XmlDocument::isXmlIdentifierCharSlow (const juce_wchar c) throw() { return CharacterFunctions::isLetterOrDigit (c) || c == '_' || c == '-' || c == ':' || c == '.'; } inline bool XmlDocument::isXmlIdentifierChar (const juce_wchar c) const throw() { return (c > 0 && c <= 127) ? identifierLookupTable [(int) c] : isXmlIdentifierCharSlow (c); } XmlElement* XmlDocument::getDocumentElement (const bool onlyReadOuterDocumentElement) { String textToParse (originalText); if (textToParse.isEmpty() && inputSource != 0) { ScopedPointer in (inputSource->createInputStream()); if (in != 0) { MemoryOutputStream data; data.writeFromInputStream (*in, onlyReadOuterDocumentElement ? 8192 : -1); textToParse = data.toString(); if (! onlyReadOuterDocumentElement) originalText = textToParse; } } input = textToParse; lastError = String::empty; errorOccurred = false; outOfData = false; needToLoadDTD = true; for (int i = 0; i < 128; ++i) identifierLookupTable[i] = isXmlIdentifierCharSlow ((juce_wchar) i); if (textToParse.isEmpty()) { lastError = "not enough input"; } else { skipHeader(); if (input != 0) { ScopedPointer result (readNextElement (! onlyReadOuterDocumentElement)); if (! errorOccurred) return result.release(); } else { lastError = "incorrect xml header"; } } return 0; } const String& XmlDocument::getLastParseError() const throw() { return lastError; } void XmlDocument::setLastError (const String& desc, const bool carryOn) { lastError = desc; errorOccurred = ! carryOn; } const String XmlDocument::getFileContents (const String& filename) const { if (inputSource != 0) { const ScopedPointer in (inputSource->createInputStreamFor (filename.trim().unquoted())); if (in != 0) return in->readEntireStreamAsString(); } return String::empty; } juce_wchar XmlDocument::readNextChar() throw() { if (*input != 0) { return *input++; } else { outOfData = true; return 0; } } int XmlDocument::findNextTokenLength() throw() { int len = 0; juce_wchar c = *input; while (isXmlIdentifierChar (c)) c = input [++len]; return len; } void XmlDocument::skipHeader() { const juce_wchar* const found = CharacterFunctions::find (input, JUCE_T("")); if (input == 0) return; input += 2; } skipNextWhiteSpace(); const juce_wchar* docType = CharacterFunctions::find (input, JUCE_T(" 0) { const juce_wchar c = readNextChar(); if (outOfData) return; if (c == '<') ++n; else if (c == '>') --n; } docType += 9; dtdText = String (docType, (int) (input - (docType + 1))).trim(); } void XmlDocument::skipNextWhiteSpace() { for (;;) { juce_wchar c = *input; while (CharacterFunctions::isWhitespace (c)) c = *++input; if (c == 0) { outOfData = true; break; } else if (c == '<') { if (input[1] == '!' && input[2] == '-' && input[3] == '-') { const juce_wchar* const closeComment = CharacterFunctions::find (input, JUCE_T("-->")); if (closeComment == 0) { outOfData = true; break; } input = closeComment + 3; continue; } else if (input[1] == '?') { const juce_wchar* const closeBracket = CharacterFunctions::find (input, JUCE_T("?>")); if (closeBracket == 0) { outOfData = true; break; } input = closeBracket + 2; continue; } } break; } } void XmlDocument::readQuotedString (String& result) { const juce_wchar quote = readNextChar(); while (! outOfData) { const juce_wchar c = readNextChar(); if (c == quote) break; if (c == '&') { --input; readEntity (result); } else { --input; const juce_wchar* const start = input; for (;;) { const juce_wchar character = *input; if (character == quote) { result.append (start, (int) (input - start)); ++input; return; } else if (character == '&') { result.append (start, (int) (input - start)); break; } else if (character == 0) { outOfData = true; setLastError ("unmatched quotes", false); break; } ++input; } } } } XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements) { XmlElement* node = 0; skipNextWhiteSpace(); if (outOfData) return 0; input = CharacterFunctions::find (input, JUCE_T("<")); if (input != 0) { ++input; int tagLen = findNextTokenLength(); if (tagLen == 0) { // no tag name - but allow for a gap after the '<' before giving an error skipNextWhiteSpace(); tagLen = findNextTokenLength(); if (tagLen == 0) { setLastError ("tag name missing", false); return node; } } node = new XmlElement (String (input, tagLen)); input += tagLen; XmlElement::XmlAttributeNode* lastAttribute = 0; // look for attributes for (;;) { skipNextWhiteSpace(); const juce_wchar c = *input; // empty tag.. if (c == '/' && input[1] == '>') { input += 2; break; } // parse the guts of the element.. if (c == '>') { ++input; skipNextWhiteSpace(); if (alsoParseSubElements) readChildElements (node); break; } // get an attribute.. if (isXmlIdentifierChar (c)) { const int attNameLen = findNextTokenLength(); if (attNameLen > 0) { const juce_wchar* attNameStart = input; input += attNameLen; skipNextWhiteSpace(); if (readNextChar() == '=') { skipNextWhiteSpace(); const juce_wchar nextChar = *input; if (nextChar == '"' || nextChar == '\'') { XmlElement::XmlAttributeNode* const newAtt = new XmlElement::XmlAttributeNode (String (attNameStart, attNameLen), String::empty); readQuotedString (newAtt->value); if (lastAttribute == 0) node->attributes = newAtt; else lastAttribute->next = newAtt; lastAttribute = newAtt; continue; } } } } else { if (! outOfData) setLastError ("illegal character found in " + node->getTagName() + ": '" + c + "'", false); } break; } } return node; } void XmlDocument::readChildElements (XmlElement* parent) { XmlElement* lastChildNode = 0; for (;;) { skipNextWhiteSpace(); if (outOfData) { setLastError ("unmatched tags", false); break; } if (*input == '<') { if (input[1] == '/') { // our close tag.. input = CharacterFunctions::find (input, JUCE_T(">")); ++input; break; } else if (input[1] == '!' && input[2] == '[' && input[3] == 'C' && input[4] == 'D' && input[5] == 'A' && input[6] == 'T' && input[7] == 'A' && input[8] == '[') { input += 9; const juce_wchar* const inputStart = input; int len = 0; for (;;) { if (*input == 0) { setLastError ("unterminated CDATA section", false); outOfData = true; break; } else if (input[0] == ']' && input[1] == ']' && input[2] == '>') { input += 3; break; } ++input; ++len; } XmlElement* const e = XmlElement::createTextElement (String (inputStart, len)); if (lastChildNode != 0) lastChildNode->nextElement = e; else parent->addChildElement (e); lastChildNode = e; } else { // this is some other element, so parse and add it.. XmlElement* const n = readNextElement (true); if (n != 0) { if (lastChildNode == 0) parent->addChildElement (n); else lastChildNode->nextElement = n; lastChildNode = n; } else { return; } } } else { // read character block.. XmlElement* const e = new XmlElement ((int)0); if (lastChildNode != 0) lastChildNode->nextElement = e; else parent->addChildElement (e); lastChildNode = e; String textElementContent; for (;;) { const juce_wchar c = *input; if (c == '<') break; if (c == 0) { setLastError ("unmatched tags", false); outOfData = true; return; } if (c == '&') { String entity; readEntity (entity); if (entity.startsWithChar ('<') && entity [1] != 0) { const juce_wchar* const oldInput = input; const bool oldOutOfData = outOfData; input = entity; outOfData = false; for (;;) { XmlElement* const n = readNextElement (true); if (n == 0) break; if (lastChildNode == 0) parent->addChildElement (n); else lastChildNode->nextElement = n; lastChildNode = n; } input = oldInput; outOfData = oldOutOfData; } else { textElementContent += entity; } } else { const juce_wchar* start = input; int len = 0; for (;;) { const juce_wchar nextChar = *input; if (nextChar == '<' || nextChar == '&') { break; } else if (nextChar == 0) { setLastError ("unmatched tags", false); outOfData = true; return; } ++input; ++len; } textElementContent.append (start, len); } } if (ignoreEmptyTextElements ? textElementContent.containsNonWhitespaceChars() : textElementContent.isNotEmpty()) e->setText (textElementContent); } } } void XmlDocument::readEntity (String& result) { // skip over the ampersand ++input; if (CharacterFunctions::compareIgnoreCase (input, JUCE_T("amp;"), 4) == 0) { input += 4; result += '&'; } else if (CharacterFunctions::compareIgnoreCase (input, JUCE_T("quot;"), 5) == 0) { input += 5; result += '"'; } else if (CharacterFunctions::compareIgnoreCase (input, JUCE_T("apos;"), 5) == 0) { input += 5; result += '\''; } else if (CharacterFunctions::compareIgnoreCase (input, JUCE_T("lt;"), 3) == 0) { input += 3; result += '<'; } else if (CharacterFunctions::compareIgnoreCase (input, JUCE_T("gt;"), 3) == 0) { input += 3; result += '>'; } else if (*input == '#') { int charCode = 0; ++input; if (*input == 'x' || *input == 'X') { ++input; int numChars = 0; while (input[0] != ';') { const int hexValue = CharacterFunctions::getHexDigitValue (input[0]); if (hexValue < 0 || ++numChars > 8) { setLastError ("illegal escape sequence", true); break; } charCode = (charCode << 4) | hexValue; ++input; } ++input; } else if (input[0] >= '0' && input[0] <= '9') { int numChars = 0; while (input[0] != ';') { if (++numChars > 12) { setLastError ("illegal escape sequence", true); break; } charCode = charCode * 10 + (input[0] - '0'); ++input; } ++input; } else { setLastError ("illegal escape sequence", true); result += '&'; return; } result << (juce_wchar) charCode; } else { const juce_wchar* const entityNameStart = input; const juce_wchar* const closingSemiColon = CharacterFunctions::find (input, JUCE_T(";")); if (closingSemiColon == 0) { outOfData = true; result += '&'; } else { input = closingSemiColon + 1; result += expandExternalEntity (String (entityNameStart, (int) (closingSemiColon - entityNameStart))); } } } const String XmlDocument::expandEntity (const String& ent) { if (ent.equalsIgnoreCase ("amp")) return String::charToString ('&'); if (ent.equalsIgnoreCase ("quot")) return String::charToString ('"'); if (ent.equalsIgnoreCase ("apos")) return String::charToString ('\''); if (ent.equalsIgnoreCase ("lt")) return String::charToString ('<'); if (ent.equalsIgnoreCase ("gt")) return String::charToString ('>'); if (ent[0] == '#') { if (ent[1] == 'x' || ent[1] == 'X') return String::charToString (static_cast (ent.substring (2).getHexValue32())); if (ent[1] >= '0' && ent[1] <= '9') return String::charToString (static_cast (ent.substring (1).getIntValue())); setLastError ("illegal escape sequence", false); return String::charToString ('&'); } return expandExternalEntity (ent); } const String XmlDocument::expandExternalEntity (const String& entity) { if (needToLoadDTD) { if (dtdText.isNotEmpty()) { dtdText = dtdText.trimCharactersAtEnd (">"); tokenisedDTD.addTokens (dtdText, true); if (tokenisedDTD [tokenisedDTD.size() - 2].equalsIgnoreCase ("system") && tokenisedDTD [tokenisedDTD.size() - 1].isQuotedString()) { const String fn (tokenisedDTD [tokenisedDTD.size() - 1]); tokenisedDTD.clear(); tokenisedDTD.addTokens (getFileContents (fn), true); } else { tokenisedDTD.clear(); const int openBracket = dtdText.indexOfChar ('['); if (openBracket > 0) { const int closeBracket = dtdText.lastIndexOfChar (']'); if (closeBracket > openBracket) tokenisedDTD.addTokens (dtdText.substring (openBracket + 1, closeBracket), true); } } for (int i = tokenisedDTD.size(); --i >= 0;) { if (tokenisedDTD[i].startsWithChar ('%') && tokenisedDTD[i].endsWithChar (';')) { const String parsed (getParameterEntity (tokenisedDTD[i].substring (1, tokenisedDTD[i].length() - 1))); StringArray newToks; newToks.addTokens (parsed, true); tokenisedDTD.remove (i); for (int j = newToks.size(); --j >= 0;) tokenisedDTD.insert (i, newToks[j]); } } } needToLoadDTD = false; } for (int i = 0; i < tokenisedDTD.size(); ++i) { if (tokenisedDTD[i] == entity) { if (tokenisedDTD[i - 1].equalsIgnoreCase ("").trim().unquoted()); // check for sub-entities.. int ampersand = ent.indexOfChar ('&'); while (ampersand >= 0) { const int semiColon = ent.indexOf (i + 1, ";"); if (semiColon < 0) { setLastError ("entity without terminating semi-colon", false); break; } const String resolved (expandEntity (ent.substring (i + 1, semiColon))); ent = ent.substring (0, ampersand) + resolved + ent.substring (semiColon + 1); ampersand = ent.indexOfChar (semiColon + 1, '&'); } return ent; } } } setLastError ("unknown entity", true); return entity; } const String XmlDocument::getParameterEntity (const String& entity) { for (int i = 0; i < tokenisedDTD.size(); ++i) { if (tokenisedDTD[i] == entity) { if (tokenisedDTD [i - 1] == "%" && tokenisedDTD [i - 2].equalsIgnoreCase ("")); if (ent.equalsIgnoreCase ("system")) return getFileContents (tokenisedDTD [i + 2].trimCharactersAtEnd (">")); else return ent.trim().unquoted(); } } } return entity; } END_JUCE_NAMESPACE /*** End of inlined file: juce_XmlDocument.cpp ***/ /*** Start of inlined file: juce_XmlElement.cpp ***/ BEGIN_JUCE_NAMESPACE XmlElement::XmlAttributeNode::XmlAttributeNode (const XmlAttributeNode& other) throw() : name (other.name), value (other.value), next (0) { } XmlElement::XmlAttributeNode::XmlAttributeNode (const String& name_, const String& value_) throw() : name (name_), value (value_), next (0) { } XmlElement::XmlElement (const String& tagName_) throw() : tagName (tagName_), firstChildElement (0), nextElement (0), attributes (0) { // the tag name mustn't be empty, or it'll look like a text element! jassert (tagName_.containsNonWhitespaceChars()) // The tag can't contain spaces or other characters that would create invalid XML! jassert (! tagName_.containsAnyOf (" <>/&")); } XmlElement::XmlElement (int /*dummy*/) throw() : firstChildElement (0), nextElement (0), attributes (0) { } XmlElement::XmlElement (const XmlElement& other) : tagName (other.tagName), firstChildElement (0), nextElement (0), attributes (0) { copyChildrenAndAttributesFrom (other); } XmlElement& XmlElement::operator= (const XmlElement& other) { if (this != &other) { removeAllAttributes(); deleteAllChildElements(); tagName = other.tagName; copyChildrenAndAttributesFrom (other); } return *this; } void XmlElement::copyChildrenAndAttributesFrom (const XmlElement& other) { XmlElement* child = other.firstChildElement; XmlElement* lastChild = 0; while (child != 0) { XmlElement* const copiedChild = new XmlElement (*child); if (lastChild != 0) lastChild->nextElement = copiedChild; else firstChildElement = copiedChild; lastChild = copiedChild; child = child->nextElement; } const XmlAttributeNode* att = other.attributes; XmlAttributeNode* lastAtt = 0; while (att != 0) { XmlAttributeNode* const newAtt = new XmlAttributeNode (*att); if (lastAtt != 0) lastAtt->next = newAtt; else attributes = newAtt; lastAtt = newAtt; att = att->next; } } XmlElement::~XmlElement() throw() { XmlElement* child = firstChildElement; while (child != 0) { XmlElement* const nextChild = child->nextElement; delete child; child = nextChild; } XmlAttributeNode* att = attributes; while (att != 0) { XmlAttributeNode* const nextAtt = att->next; delete att; att = nextAtt; } } namespace XmlOutputFunctions { /*static bool isLegalXmlCharSlow (const juce_wchar character) throw() { if ((character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || (character >= '0' && character <= '9')) return true; const char* t = " .,;:-()_+=?!'#@[]/\\*%~{}$|"; do { if (((juce_wchar) (uint8) *t) == character) return true; } while (*++t != 0); return false; } static void generateLegalCharConstants() { uint8 n[32]; zerostruct (n); for (int i = 0; i < 256; ++i) if (isLegalXmlCharSlow (i)) n[i >> 3] |= (1 << (i & 7)); String s; for (int i = 0; i < 32; ++i) s << (int) n[i] << ", "; DBG (s); }*/ static bool isLegalXmlChar (const uint32 c) throw() { static const unsigned char legalChars[] = { 0, 0, 0, 0, 187, 255, 255, 175, 255, 255, 255, 191, 254, 255, 255, 127 }; return c < sizeof (legalChars) * 8 && (legalChars [c >> 3] & (1 << (c & 7))) != 0; } static void escapeIllegalXmlChars (OutputStream& outputStream, const String& text, const bool changeNewLines) { const juce_wchar* t = text; for (;;) { const juce_wchar character = *t++; if (character == 0) { break; } else if (isLegalXmlChar ((uint32) character)) { outputStream << (char) character; } else { switch (character) { case '&': outputStream << "&"; break; case '"': outputStream << """; break; case '>': outputStream << ">"; break; case '<': outputStream << "<"; break; case '\n': if (changeNewLines) outputStream << " "; else outputStream << (char) character; break; case '\r': if (changeNewLines) outputStream << " "; else outputStream << (char) character; break; default: outputStream << "&#" << ((int) (unsigned int) character) << ';'; break; } } } } static void writeSpaces (OutputStream& out, int numSpaces) { if (numSpaces > 0) { const char* const blanks = " "; const int blankSize = (int) sizeof (blanks) - 1; while (numSpaces > blankSize) { out.write (blanks, blankSize); numSpaces -= blankSize; } out.write (blanks, numSpaces); } } } void XmlElement::writeElementAsText (OutputStream& outputStream, const int indentationLevel, const int lineWrapLength) const { using namespace XmlOutputFunctions; writeSpaces (outputStream, indentationLevel); if (! isTextElement()) { outputStream.writeByte ('<'); outputStream << tagName; const int attIndent = indentationLevel + tagName.length() + 1; int lineLen = 0; const XmlAttributeNode* att = attributes; while (att != 0) { if (lineLen > lineWrapLength && indentationLevel >= 0) { outputStream.write ("\r\n", 2); writeSpaces (outputStream, attIndent); lineLen = 0; } const int64 startPos = outputStream.getPosition(); outputStream.writeByte (' '); outputStream << att->name; outputStream.write ("=\"", 2); escapeIllegalXmlChars (outputStream, att->value, true); outputStream.writeByte ('"'); lineLen += (int) (outputStream.getPosition() - startPos); att = att->next; } if (firstChildElement != 0) { XmlElement* child = firstChildElement; if (child->nextElement == 0 && child->isTextElement()) { outputStream.writeByte ('>'); escapeIllegalXmlChars (outputStream, child->getText(), false); } else { if (indentationLevel >= 0) outputStream.write (">\r\n", 3); else outputStream.writeByte ('>'); bool lastWasTextNode = false; while (child != 0) { if (child->isTextElement()) { if ((! lastWasTextNode) && (indentationLevel >= 0)) writeSpaces (outputStream, indentationLevel + 2); escapeIllegalXmlChars (outputStream, child->getText(), false); lastWasTextNode = true; } else { if (indentationLevel >= 0) { if (lastWasTextNode) outputStream.write ("\r\n", 2); child->writeElementAsText (outputStream, indentationLevel + 2, lineWrapLength); } else { child->writeElementAsText (outputStream, indentationLevel, lineWrapLength); } lastWasTextNode = false; } child = child->nextElement; } if (indentationLevel >= 0) { if (lastWasTextNode) outputStream.write ("\r\n", 2); writeSpaces (outputStream, indentationLevel); } } outputStream.write ("= 0) outputStream.write (">\r\n", 3); else outputStream.writeByte ('>'); } else { if (indentationLevel >= 0) outputStream.write ("/>\r\n", 4); else outputStream.write ("/>", 2); } } else { if (indentationLevel >= 0) writeSpaces (outputStream, indentationLevel + 2); escapeIllegalXmlChars (outputStream, getText(), false); } } const String XmlElement::createDocument (const String& dtdToUse, const bool allOnOneLine, const bool includeXmlHeader, const String& encodingType, const int lineWrapLength) const { MemoryOutputStream mem (2048); writeToStream (mem, dtdToUse, allOnOneLine, includeXmlHeader, encodingType, lineWrapLength); return mem.toUTF8(); } void XmlElement::writeToStream (OutputStream& output, const String& dtdToUse, const bool allOnOneLine, const bool includeXmlHeader, const String& encodingType, const int lineWrapLength) const { if (includeXmlHeader) output << " " : "\"?>\r\n\r\n"); if (dtdToUse.isNotEmpty()) output << dtdToUse << (allOnOneLine ? " " : "\r\n"); writeElementAsText (output, allOnOneLine ? -1 : 0, lineWrapLength); } bool XmlElement::writeToFile (const File& file, const String& dtdToUse, const String& encodingType, const int lineWrapLength) const { if (file.hasWriteAccess()) { TemporaryFile tempFile (file); ScopedPointer out (tempFile.getFile().createOutputStream()); if (out != 0) { writeToStream (*out, dtdToUse, false, true, encodingType, lineWrapLength); out = 0; return tempFile.overwriteTargetFileWithTemporary(); } } return false; } bool XmlElement::hasTagName (const String& tagNameWanted) const throw() { #if JUCE_DEBUG // if debugging, check that the case is actually the same, because // valid xml is case-sensitive, and although this lets it pass, it's // better not to.. if (tagName.equalsIgnoreCase (tagNameWanted)) { jassert (tagName == tagNameWanted); return true; } else { return false; } #else return tagName.equalsIgnoreCase (tagNameWanted); #endif } XmlElement* XmlElement::getNextElementWithTagName (const String& requiredTagName) const { XmlElement* e = nextElement; while (e != 0 && ! e->hasTagName (requiredTagName)) e = e->nextElement; return e; } int XmlElement::getNumAttributes() const throw() { const XmlAttributeNode* att = attributes; int count = 0; while (att != 0) { att = att->next; ++count; } return count; } const String& XmlElement::getAttributeName (const int index) const throw() { const XmlAttributeNode* att = attributes; int count = 0; while (att != 0) { if (count == index) return att->name; att = att->next; ++count; } return String::empty; } const String& XmlElement::getAttributeValue (const int index) const throw() { const XmlAttributeNode* att = attributes; int count = 0; while (att != 0) { if (count == index) return att->value; att = att->next; ++count; } return String::empty; } bool XmlElement::hasAttribute (const String& attributeName) const throw() { const XmlAttributeNode* att = attributes; while (att != 0) { if (att->name.equalsIgnoreCase (attributeName)) return true; att = att->next; } return false; } const String& XmlElement::getStringAttribute (const String& attributeName) const throw() { const XmlAttributeNode* att = attributes; while (att != 0) { if (att->name.equalsIgnoreCase (attributeName)) return att->value; att = att->next; } return String::empty; } const String XmlElement::getStringAttribute (const String& attributeName, const String& defaultReturnValue) const { const XmlAttributeNode* att = attributes; while (att != 0) { if (att->name.equalsIgnoreCase (attributeName)) return att->value; att = att->next; } return defaultReturnValue; } int XmlElement::getIntAttribute (const String& attributeName, const int defaultReturnValue) const { const XmlAttributeNode* att = attributes; while (att != 0) { if (att->name.equalsIgnoreCase (attributeName)) return att->value.getIntValue(); att = att->next; } return defaultReturnValue; } double XmlElement::getDoubleAttribute (const String& attributeName, const double defaultReturnValue) const { const XmlAttributeNode* att = attributes; while (att != 0) { if (att->name.equalsIgnoreCase (attributeName)) return att->value.getDoubleValue(); att = att->next; } return defaultReturnValue; } bool XmlElement::getBoolAttribute (const String& attributeName, const bool defaultReturnValue) const { const XmlAttributeNode* att = attributes; while (att != 0) { if (att->name.equalsIgnoreCase (attributeName)) { juce_wchar firstChar = att->value[0]; if (CharacterFunctions::isWhitespace (firstChar)) firstChar = att->value.trimStart() [0]; return firstChar == '1' || firstChar == 't' || firstChar == 'y' || firstChar == 'T' || firstChar == 'Y'; } att = att->next; } return defaultReturnValue; } bool XmlElement::compareAttribute (const String& attributeName, const String& stringToCompareAgainst, const bool ignoreCase) const throw() { const XmlAttributeNode* att = attributes; while (att != 0) { if (att->name.equalsIgnoreCase (attributeName)) { if (ignoreCase) return att->value.equalsIgnoreCase (stringToCompareAgainst); else return att->value == stringToCompareAgainst; } att = att->next; } return false; } void XmlElement::setAttribute (const String& attributeName, const String& value) { #if JUCE_DEBUG // check the identifier being passed in is legal.. const juce_wchar* t = attributeName; while (*t != 0) { jassert (CharacterFunctions::isLetterOrDigit (*t) || *t == '_' || *t == '-' || *t == ':'); ++t; } #endif if (attributes == 0) { attributes = new XmlAttributeNode (attributeName, value); } else { XmlAttributeNode* att = attributes; for (;;) { if (att->name.equalsIgnoreCase (attributeName)) { att->value = value; break; } else if (att->next == 0) { att->next = new XmlAttributeNode (attributeName, value); break; } att = att->next; } } } void XmlElement::setAttribute (const String& attributeName, const int number) { setAttribute (attributeName, String (number)); } void XmlElement::setAttribute (const String& attributeName, const double number) { setAttribute (attributeName, String (number)); } void XmlElement::removeAttribute (const String& attributeName) throw() { XmlAttributeNode* att = attributes; XmlAttributeNode* lastAtt = 0; while (att != 0) { if (att->name.equalsIgnoreCase (attributeName)) { if (lastAtt == 0) attributes = att->next; else lastAtt->next = att->next; delete att; break; } lastAtt = att; att = att->next; } } void XmlElement::removeAllAttributes() throw() { while (attributes != 0) { XmlAttributeNode* const nextAtt = attributes->next; delete attributes; attributes = nextAtt; } } int XmlElement::getNumChildElements() const throw() { int count = 0; const XmlElement* child = firstChildElement; while (child != 0) { ++count; child = child->nextElement; } return count; } XmlElement* XmlElement::getChildElement (const int index) const throw() { int count = 0; XmlElement* child = firstChildElement; while (child != 0 && count < index) { child = child->nextElement; ++count; } return child; } XmlElement* XmlElement::getChildByName (const String& childName) const throw() { XmlElement* child = firstChildElement; while (child != 0) { if (child->hasTagName (childName)) break; child = child->nextElement; } return child; } void XmlElement::addChildElement (XmlElement* const newNode) throw() { if (newNode != 0) { if (firstChildElement == 0) { firstChildElement = newNode; } else { XmlElement* child = firstChildElement; while (child->nextElement != 0) child = child->nextElement; child->nextElement = newNode; // if this is non-zero, then something's probably // gone wrong.. jassert (newNode->nextElement == 0); } } } void XmlElement::insertChildElement (XmlElement* const newNode, int indexToInsertAt) throw() { if (newNode != 0) { removeChildElement (newNode, false); if (indexToInsertAt == 0) { newNode->nextElement = firstChildElement; firstChildElement = newNode; } else { if (firstChildElement == 0) { firstChildElement = newNode; } else { if (indexToInsertAt < 0) indexToInsertAt = std::numeric_limits::max(); XmlElement* child = firstChildElement; while (child->nextElement != 0 && --indexToInsertAt > 0) child = child->nextElement; newNode->nextElement = child->nextElement; child->nextElement = newNode; } } } } XmlElement* XmlElement::createNewChildElement (const String& childTagName) { XmlElement* const newElement = new XmlElement (childTagName); addChildElement (newElement); return newElement; } bool XmlElement::replaceChildElement (XmlElement* const currentChildElement, XmlElement* const newNode) throw() { if (newNode != 0) { XmlElement* child = firstChildElement; XmlElement* previousNode = 0; while (child != 0) { if (child == currentChildElement) { if (child != newNode) { if (previousNode == 0) firstChildElement = newNode; else previousNode->nextElement = newNode; newNode->nextElement = child->nextElement; delete child; } return true; } previousNode = child; child = child->nextElement; } } return false; } void XmlElement::removeChildElement (XmlElement* const childToRemove, const bool shouldDeleteTheChild) throw() { if (childToRemove != 0) { if (firstChildElement == childToRemove) { firstChildElement = childToRemove->nextElement; childToRemove->nextElement = 0; } else { XmlElement* child = firstChildElement; XmlElement* last = 0; while (child != 0) { if (child == childToRemove) { if (last == 0) firstChildElement = child->nextElement; else last->nextElement = child->nextElement; childToRemove->nextElement = 0; break; } last = child; child = child->nextElement; } } if (shouldDeleteTheChild) delete childToRemove; } } bool XmlElement::isEquivalentTo (const XmlElement* const other, const bool ignoreOrderOfAttributes) const throw() { if (this != other) { if (other == 0 || tagName != other->tagName) { return false; } if (ignoreOrderOfAttributes) { int totalAtts = 0; const XmlAttributeNode* att = attributes; while (att != 0) { if (! other->compareAttribute (att->name, att->value)) return false; att = att->next; ++totalAtts; } if (totalAtts != other->getNumAttributes()) return false; } else { const XmlAttributeNode* thisAtt = attributes; const XmlAttributeNode* otherAtt = other->attributes; for (;;) { if (thisAtt == 0 || otherAtt == 0) { if (thisAtt == otherAtt) // both 0, so it's a match break; return false; } if (thisAtt->name != otherAtt->name || thisAtt->value != otherAtt->value) { return false; } thisAtt = thisAtt->next; otherAtt = otherAtt->next; } } const XmlElement* thisChild = firstChildElement; const XmlElement* otherChild = other->firstChildElement; for (;;) { if (thisChild == 0 || otherChild == 0) { if (thisChild == otherChild) // both 0, so it's a match break; return false; } if (! thisChild->isEquivalentTo (otherChild, ignoreOrderOfAttributes)) return false; thisChild = thisChild->nextElement; otherChild = otherChild->nextElement; } } return true; } void XmlElement::deleteAllChildElements() throw() { while (firstChildElement != 0) { XmlElement* const nextChild = firstChildElement->nextElement; delete firstChildElement; firstChildElement = nextChild; } } void XmlElement::deleteAllChildElementsWithTagName (const String& name) throw() { XmlElement* child = firstChildElement; while (child != 0) { if (child->hasTagName (name)) { XmlElement* const nextChild = child->nextElement; removeChildElement (child, true); child = nextChild; } else { child = child->nextElement; } } } bool XmlElement::containsChildElement (const XmlElement* const possibleChild) const throw() { const XmlElement* child = firstChildElement; while (child != 0) { if (child == possibleChild) return true; child = child->nextElement; } return false; } XmlElement* XmlElement::findParentElementOf (const XmlElement* const elementToLookFor) throw() { if (this == elementToLookFor || elementToLookFor == 0) return 0; XmlElement* child = firstChildElement; while (child != 0) { if (elementToLookFor == child) return this; XmlElement* const found = child->findParentElementOf (elementToLookFor); if (found != 0) return found; child = child->nextElement; } return 0; } void XmlElement::getChildElementsAsArray (XmlElement** elems) const throw() { XmlElement* e = firstChildElement; while (e != 0) { *elems++ = e; e = e->nextElement; } } void XmlElement::reorderChildElements (XmlElement** const elems, const int num) throw() { XmlElement* e = firstChildElement = elems[0]; for (int i = 1; i < num; ++i) { e->nextElement = elems[i]; e = e->nextElement; } e->nextElement = 0; } bool XmlElement::isTextElement() const throw() { return tagName.isEmpty(); } static const juce_wchar* const juce_xmltextContentAttributeName = L"text"; const String& XmlElement::getText() const throw() { jassert (isTextElement()); // you're trying to get the text from an element that // isn't actually a text element.. If this contains text sub-nodes, you // probably want to use getAllSubText instead. return getStringAttribute (juce_xmltextContentAttributeName); } void XmlElement::setText (const String& newText) { if (isTextElement()) setAttribute (juce_xmltextContentAttributeName, newText); else jassertfalse; // you can only change the text in a text element, not a normal one. } const String XmlElement::getAllSubText() const { String result; String::Concatenator concatenator (result); const XmlElement* child = firstChildElement; while (child != 0) { if (child->isTextElement()) concatenator.append (child->getText()); child = child->nextElement; } return result; } const String XmlElement::getChildElementAllSubText (const String& childTagName, const String& defaultReturnValue) const { const XmlElement* const child = getChildByName (childTagName); if (child != 0) return child->getAllSubText(); return defaultReturnValue; } XmlElement* XmlElement::createTextElement (const String& text) { XmlElement* const e = new XmlElement ((int) 0); e->setAttribute (juce_xmltextContentAttributeName, text); return e; } void XmlElement::addTextElement (const String& text) { addChildElement (createTextElement (text)); } void XmlElement::deleteAllTextElements() throw() { XmlElement* child = firstChildElement; while (child != 0) { XmlElement* const next = child->nextElement; if (child->isTextElement()) removeChildElement (child, true); child = next; } } END_JUCE_NAMESPACE /*** End of inlined file: juce_XmlElement.cpp ***/ /*** Start of inlined file: juce_ReadWriteLock.cpp ***/ BEGIN_JUCE_NAMESPACE ReadWriteLock::ReadWriteLock() throw() : numWaitingWriters (0), numWriters (0), writerThreadId (0) { } ReadWriteLock::~ReadWriteLock() throw() { jassert (readerThreads.size() == 0); jassert (numWriters == 0); } void ReadWriteLock::enterRead() const throw() { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); const ScopedLock sl (accessLock); for (;;) { jassert (readerThreads.size() % 2 == 0); int i; for (i = 0; i < readerThreads.size(); i += 2) if (readerThreads.getUnchecked(i) == threadId) break; if (i < readerThreads.size() || numWriters + numWaitingWriters == 0 || (threadId == writerThreadId && numWriters > 0)) { if (i < readerThreads.size()) { readerThreads.set (i + 1, (Thread::ThreadID) (1 + (pointer_sized_int) readerThreads.getUnchecked (i + 1))); } else { readerThreads.add (threadId); readerThreads.add ((Thread::ThreadID) 1); } return; } const ScopedUnlock ul (accessLock); waitEvent.wait (100); } } void ReadWriteLock::exitRead() const throw() { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); const ScopedLock sl (accessLock); for (int i = 0; i < readerThreads.size(); i += 2) { if (readerThreads.getUnchecked(i) == threadId) { const pointer_sized_int newCount = ((pointer_sized_int) readerThreads.getUnchecked (i + 1)) - 1; if (newCount == 0) { readerThreads.removeRange (i, 2); waitEvent.signal(); } else { readerThreads.set (i + 1, (Thread::ThreadID) newCount); } return; } } jassertfalse; // unlocking a lock that wasn't locked.. } void ReadWriteLock::enterWrite() const throw() { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); const ScopedLock sl (accessLock); for (;;) { if (readerThreads.size() + numWriters == 0 || threadId == writerThreadId || (readerThreads.size() == 2 && readerThreads.getUnchecked(0) == threadId)) { writerThreadId = threadId; ++numWriters; break; } ++numWaitingWriters; accessLock.exit(); waitEvent.wait (100); accessLock.enter(); --numWaitingWriters; } } bool ReadWriteLock::tryEnterWrite() const throw() { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); const ScopedLock sl (accessLock); if (readerThreads.size() + numWriters == 0 || threadId == writerThreadId || (readerThreads.size() == 2 && readerThreads.getUnchecked(0) == threadId)) { writerThreadId = threadId; ++numWriters; return true; } return false; } void ReadWriteLock::exitWrite() const throw() { const ScopedLock sl (accessLock); // check this thread actually had the lock.. jassert (numWriters > 0 && writerThreadId == Thread::getCurrentThreadId()); if (--numWriters == 0) { writerThreadId = 0; waitEvent.signal(); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ReadWriteLock.cpp ***/ /*** Start of inlined file: juce_Thread.cpp ***/ BEGIN_JUCE_NAMESPACE // these functions are implemented in the platform-specific code. void* juce_createThread (void* userData); void juce_killThread (void* handle); bool juce_setThreadPriority (void* handle, int priority); void juce_setCurrentThreadName (const String& name); #if JUCE_WINDOWS void juce_CloseThreadHandle (void* handle); #endif void Thread::threadEntryPoint (Thread* const thread) { { const ScopedLock sl (runningThreadsLock); runningThreads.add (thread); } JUCE_TRY { thread->threadId_ = Thread::getCurrentThreadId(); if (thread->threadName_.isNotEmpty()) juce_setCurrentThreadName (thread->threadName_); if (thread->startSuspensionEvent_.wait (10000)) { if (thread->affinityMask_ != 0) setCurrentThreadAffinityMask (thread->affinityMask_); thread->run(); } } JUCE_CATCH_ALL_ASSERT { const ScopedLock sl (runningThreadsLock); jassert (runningThreads.contains (thread)); runningThreads.removeValue (thread); } #if JUCE_WINDOWS juce_CloseThreadHandle (thread->threadHandle_); #endif thread->threadHandle_ = 0; thread->threadId_ = 0; } // used to wrap the incoming call from the platform-specific code void JUCE_API juce_threadEntryPoint (void* userData) { Thread::threadEntryPoint (static_cast (userData)); } Thread::Thread (const String& threadName) : threadName_ (threadName), threadHandle_ (0), threadPriority_ (5), threadId_ (0), affinityMask_ (0), threadShouldExit_ (false) { } Thread::~Thread() { stopThread (100); } void Thread::startThread() { const ScopedLock sl (startStopLock); threadShouldExit_ = false; if (threadHandle_ == 0) { threadHandle_ = juce_createThread (this); juce_setThreadPriority (threadHandle_, threadPriority_); startSuspensionEvent_.signal(); } } void Thread::startThread (const int priority) { const ScopedLock sl (startStopLock); if (threadHandle_ == 0) { threadPriority_ = priority; startThread(); } else { setPriority (priority); } } bool Thread::isThreadRunning() const { return threadHandle_ != 0; } void Thread::signalThreadShouldExit() { threadShouldExit_ = true; } bool Thread::waitForThreadToExit (const int timeOutMilliseconds) const { // Doh! So how exactly do you expect this thread to wait for itself to stop?? jassert (getThreadId() != getCurrentThreadId()); const int sleepMsPerIteration = 5; int count = timeOutMilliseconds / sleepMsPerIteration; while (isThreadRunning()) { if (timeOutMilliseconds > 0 && --count < 0) return false; sleep (sleepMsPerIteration); } return true; } void Thread::stopThread (const int timeOutMilliseconds) { // agh! You can't stop the thread that's calling this method! How on earth // would that work?? jassert (getCurrentThreadId() != getThreadId()); const ScopedLock sl (startStopLock); if (isThreadRunning()) { signalThreadShouldExit(); notify(); if (timeOutMilliseconds != 0) waitForThreadToExit (timeOutMilliseconds); if (isThreadRunning()) { // very bad karma if this point is reached, as // there are bound to be locks and events left in // silly states when a thread is killed by force.. jassertfalse; Logger::writeToLog ("!! killing thread by force !!"); juce_killThread (threadHandle_); threadHandle_ = 0; threadId_ = 0; const ScopedLock sl2 (runningThreadsLock); runningThreads.removeValue (this); } } } bool Thread::setPriority (const int priority) { const ScopedLock sl (startStopLock); const bool worked = juce_setThreadPriority (threadHandle_, priority); if (worked) threadPriority_ = priority; return worked; } bool Thread::setCurrentThreadPriority (const int priority) { return juce_setThreadPriority (0, priority); } void Thread::setAffinityMask (const uint32 affinityMask) { affinityMask_ = affinityMask; } bool Thread::wait (const int timeOutMilliseconds) const { return defaultEvent_.wait (timeOutMilliseconds); } void Thread::notify() const { defaultEvent_.signal(); } int Thread::getNumRunningThreads() { return runningThreads.size(); } Thread* Thread::getCurrentThread() { const ThreadID thisId = getCurrentThreadId(); const ScopedLock sl (runningThreadsLock); for (int i = runningThreads.size(); --i >= 0;) { Thread* const t = runningThreads.getUnchecked(i); if (t->threadId_ == thisId) return t; } return 0; } void Thread::stopAllThreads (const int timeOutMilliseconds) { { const ScopedLock sl (runningThreadsLock); for (int i = runningThreads.size(); --i >= 0;) runningThreads.getUnchecked(i)->signalThreadShouldExit(); } for (;;) { Thread* firstThread; { const ScopedLock sl (runningThreadsLock); firstThread = runningThreads.getFirst(); } if (firstThread == 0) break; firstThread->stopThread (timeOutMilliseconds); } } Array Thread::runningThreads; CriticalSection Thread::runningThreadsLock; END_JUCE_NAMESPACE /*** End of inlined file: juce_Thread.cpp ***/ /*** Start of inlined file: juce_ThreadPool.cpp ***/ BEGIN_JUCE_NAMESPACE ThreadPoolJob::ThreadPoolJob (const String& name) : jobName (name), pool (0), shouldStop (false), isActive (false), shouldBeDeleted (false) { } ThreadPoolJob::~ThreadPoolJob() { // you mustn't delete a job while it's still in a pool! Use ThreadPool::removeJob() // to remove it first! jassert (pool == 0 || ! pool->contains (this)); } const String ThreadPoolJob::getJobName() const { return jobName; } void ThreadPoolJob::setJobName (const String& newName) { jobName = newName; } void ThreadPoolJob::signalJobShouldExit() { shouldStop = true; } class ThreadPool::ThreadPoolThread : public Thread { public: ThreadPoolThread (ThreadPool& pool_) : Thread ("Pool"), pool (pool_), busy (false) { } ~ThreadPoolThread() { } void run() { while (! threadShouldExit()) { if (! pool.runNextJob()) wait (500); } } private: ThreadPool& pool; bool volatile busy; ThreadPoolThread (const ThreadPoolThread&); ThreadPoolThread& operator= (const ThreadPoolThread&); }; ThreadPool::ThreadPool (const int numThreads, const bool startThreadsOnlyWhenNeeded, const int stopThreadsWhenNotUsedTimeoutMs) : threadStopTimeout (stopThreadsWhenNotUsedTimeoutMs), priority (5) { jassert (numThreads > 0); // not much point having one of these with no threads in it. for (int i = jmax (1, numThreads); --i >= 0;) threads.add (new ThreadPoolThread (*this)); if (! startThreadsOnlyWhenNeeded) for (int i = threads.size(); --i >= 0;) threads.getUnchecked(i)->startThread (priority); } ThreadPool::~ThreadPool() { removeAllJobs (true, 4000); int i; for (i = threads.size(); --i >= 0;) threads.getUnchecked(i)->signalThreadShouldExit(); for (i = threads.size(); --i >= 0;) threads.getUnchecked(i)->stopThread (500); } void ThreadPool::addJob (ThreadPoolJob* const job) { jassert (job != 0); jassert (job->pool == 0); if (job->pool == 0) { job->pool = this; job->shouldStop = false; job->isActive = false; { const ScopedLock sl (lock); jobs.add (job); int numRunning = 0; for (int i = threads.size(); --i >= 0;) if (threads.getUnchecked(i)->isThreadRunning() && ! threads.getUnchecked(i)->threadShouldExit()) ++numRunning; if (numRunning < threads.size()) { bool startedOne = false; int n = 1000; while (--n >= 0 && ! startedOne) { for (int i = threads.size(); --i >= 0;) { if (! threads.getUnchecked(i)->isThreadRunning()) { threads.getUnchecked(i)->startThread (priority); startedOne = true; break; } } if (! startedOne) Thread::sleep (2); } } } for (int i = threads.size(); --i >= 0;) threads.getUnchecked(i)->notify(); } } int ThreadPool::getNumJobs() const { return jobs.size(); } ThreadPoolJob* ThreadPool::getJob (const int index) const { const ScopedLock sl (lock); return jobs [index]; } bool ThreadPool::contains (const ThreadPoolJob* const job) const { const ScopedLock sl (lock); return jobs.contains (const_cast (job)); } bool ThreadPool::isJobRunning (const ThreadPoolJob* const job) const { const ScopedLock sl (lock); return jobs.contains (const_cast (job)) && job->isActive; } bool ThreadPool::waitForJobToFinish (const ThreadPoolJob* const job, const int timeOutMs) const { if (job != 0) { const uint32 start = Time::getMillisecondCounter(); while (contains (job)) { if (timeOutMs >= 0 && Time::getMillisecondCounter() >= start + timeOutMs) return false; jobFinishedSignal.wait (2); } } return true; } bool ThreadPool::removeJob (ThreadPoolJob* const job, const bool interruptIfRunning, const int timeOutMs) { bool dontWait = true; if (job != 0) { const ScopedLock sl (lock); if (jobs.contains (job)) { if (job->isActive) { if (interruptIfRunning) job->signalJobShouldExit(); dontWait = false; } else { jobs.removeValue (job); job->pool = 0; } } } return dontWait || waitForJobToFinish (job, timeOutMs); } bool ThreadPool::removeAllJobs (const bool interruptRunningJobs, const int timeOutMs, const bool deleteInactiveJobs, ThreadPool::JobSelector* selectedJobsToRemove) { Array jobsToWaitFor; { const ScopedLock sl (lock); for (int i = jobs.size(); --i >= 0;) { ThreadPoolJob* const job = jobs.getUnchecked(i); if (selectedJobsToRemove == 0 || selectedJobsToRemove->isJobSuitable (job)) { if (job->isActive) { jobsToWaitFor.add (job); if (interruptRunningJobs) job->signalJobShouldExit(); } else { jobs.remove (i); if (deleteInactiveJobs) delete job; else job->pool = 0; } } } } const uint32 start = Time::getMillisecondCounter(); for (;;) { for (int i = jobsToWaitFor.size(); --i >= 0;) if (! isJobRunning (jobsToWaitFor.getUnchecked (i))) jobsToWaitFor.remove (i); if (jobsToWaitFor.size() == 0) break; if (timeOutMs >= 0 && Time::getMillisecondCounter() >= start + timeOutMs) return false; jobFinishedSignal.wait (20); } return true; } const StringArray ThreadPool::getNamesOfAllJobs (const bool onlyReturnActiveJobs) const { StringArray s; const ScopedLock sl (lock); for (int i = 0; i < jobs.size(); ++i) { const ThreadPoolJob* const job = jobs.getUnchecked(i); if (job->isActive || ! onlyReturnActiveJobs) s.add (job->getJobName()); } return s; } bool ThreadPool::setThreadPriorities (const int newPriority) { bool ok = true; if (priority != newPriority) { priority = newPriority; for (int i = threads.size(); --i >= 0;) if (! threads.getUnchecked(i)->setPriority (newPriority)) ok = false; } return ok; } bool ThreadPool::runNextJob() { ThreadPoolJob* job = 0; { const ScopedLock sl (lock); for (int i = 0; i < jobs.size(); ++i) { job = jobs[i]; if (job != 0 && ! (job->isActive || job->shouldStop)) break; job = 0; } if (job != 0) job->isActive = true; } if (job != 0) { JUCE_TRY { ThreadPoolJob::JobStatus result = job->runJob(); lastJobEndTime = Time::getApproximateMillisecondCounter(); const ScopedLock sl (lock); if (jobs.contains (job)) { job->isActive = false; if (result != ThreadPoolJob::jobNeedsRunningAgain || job->shouldStop) { job->pool = 0; job->shouldStop = true; jobs.removeValue (job); if (result == ThreadPoolJob::jobHasFinishedAndShouldBeDeleted) delete job; jobFinishedSignal.signal(); } else { // move the job to the end of the queue if it wants another go jobs.move (jobs.indexOf (job), -1); } } } #if JUCE_CATCH_UNHANDLED_EXCEPTIONS catch (...) { const ScopedLock sl (lock); jobs.removeValue (job); } #endif } else { if (threadStopTimeout > 0 && Time::getApproximateMillisecondCounter() > lastJobEndTime + threadStopTimeout) { const ScopedLock sl (lock); if (jobs.size() == 0) for (int i = threads.size(); --i >= 0;) threads.getUnchecked(i)->signalThreadShouldExit(); } else { return false; } } return true; } END_JUCE_NAMESPACE /*** End of inlined file: juce_ThreadPool.cpp ***/ /*** Start of inlined file: juce_TimeSliceThread.cpp ***/ BEGIN_JUCE_NAMESPACE TimeSliceThread::TimeSliceThread (const String& threadName) : Thread (threadName), index (0), clientBeingCalled (0), clientsChanged (false) { } TimeSliceThread::~TimeSliceThread() { stopThread (2000); } void TimeSliceThread::addTimeSliceClient (TimeSliceClient* const client) { const ScopedLock sl (listLock); clients.addIfNotAlreadyThere (client); clientsChanged = true; notify(); } void TimeSliceThread::removeTimeSliceClient (TimeSliceClient* const client) { const ScopedLock sl1 (listLock); clientsChanged = true; // if there's a chance we're in the middle of calling this client, we need to // also lock the outer lock.. if (clientBeingCalled == client) { const ScopedUnlock ul (listLock); // unlock first to get the order right.. const ScopedLock sl2 (callbackLock); const ScopedLock sl3 (listLock); clients.removeValue (client); } else { clients.removeValue (client); } } int TimeSliceThread::getNumClients() const { return clients.size(); } TimeSliceClient* TimeSliceThread::getClient (const int i) const { const ScopedLock sl (listLock); return clients [i]; } void TimeSliceThread::run() { int numCallsSinceBusy = 0; while (! threadShouldExit()) { int timeToWait = 500; { const ScopedLock sl (callbackLock); { const ScopedLock sl2 (listLock); if (clients.size() > 0) { index = (index + 1) % clients.size(); clientBeingCalled = clients [index]; } else { index = 0; clientBeingCalled = 0; } if (clientsChanged) { clientsChanged = false; numCallsSinceBusy = 0; } } if (clientBeingCalled != 0) { if (clientBeingCalled->useTimeSlice()) numCallsSinceBusy = 0; else ++numCallsSinceBusy; if (numCallsSinceBusy >= clients.size()) timeToWait = 500; else if (index == 0) timeToWait = 1; // throw in an occasional pause, to stop everything locking up else timeToWait = 0; } } if (timeToWait > 0) wait (timeToWait); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_TimeSliceThread.cpp ***/ /*** Start of inlined file: juce_DeletedAtShutdown.cpp ***/ BEGIN_JUCE_NAMESPACE DeletedAtShutdown::DeletedAtShutdown() { const ScopedLock sl (getLock()); getObjects().add (this); } DeletedAtShutdown::~DeletedAtShutdown() { const ScopedLock sl (getLock()); getObjects().removeValue (this); } void DeletedAtShutdown::deleteAll() { // make a local copy of the array, so it can't get into a loop if something // creates another DeletedAtShutdown object during its destructor. Array localCopy; { const ScopedLock sl (getLock()); localCopy = getObjects(); } for (int i = localCopy.size(); --i >= 0;) { JUCE_TRY { DeletedAtShutdown* deletee = localCopy.getUnchecked(i); // double-check that it's not already been deleted during another object's destructor. { const ScopedLock sl (getLock()); if (! getObjects().contains (deletee)) deletee = 0; } delete deletee; } JUCE_CATCH_EXCEPTION } // if no objects got re-created during shutdown, this should have been emptied by their // destructors jassert (getObjects().size() == 0); getObjects().clear(); // just to make sure the array doesn't have any memory still allocated } CriticalSection& DeletedAtShutdown::getLock() { static CriticalSection lock; return lock; } Array & DeletedAtShutdown::getObjects() { static Array objects; return objects; } END_JUCE_NAMESPACE /*** End of inlined file: juce_DeletedAtShutdown.cpp ***/ /*** Start of inlined file: juce_UnitTest.cpp ***/ BEGIN_JUCE_NAMESPACE UnitTest::UnitTest (const String& name_) : name (name_), runner (0) { getAllTests().add (this); } UnitTest::~UnitTest() { getAllTests().removeValue (this); } Array& UnitTest::getAllTests() { static Array tests; return tests; } void UnitTest::performTest (UnitTestRunner* const runner_) { jassert (runner_ != 0); runner = runner_; runTest(); } void UnitTest::logMessage (const String& message) { runner->logMessage (message); } void UnitTest::beginTest (const String& testName) { runner->beginNewTest (this, testName); } void UnitTest::expect (const bool result, const String& failureMessage) { if (result) runner->addPass(); else runner->addFail (failureMessage); } UnitTestRunner::UnitTestRunner() : currentTest (0), assertOnFailure (false) { } UnitTestRunner::~UnitTestRunner() { } void UnitTestRunner::resultsUpdated() { } void UnitTestRunner::runTests (const Array& tests, const bool assertOnFailure_) { results.clear(); assertOnFailure = assertOnFailure_; resultsUpdated(); for (int i = 0; i < tests.size(); ++i) { try { tests.getUnchecked(i)->performTest (this); } catch (...) { addFail ("An unhandled exception was thrown!"); } } endTest(); } void UnitTestRunner::runAllTests (const bool assertOnFailure_) { runTests (UnitTest::getAllTests(), assertOnFailure_); } void UnitTestRunner::logMessage (const String& message) { Logger::writeToLog (message); } void UnitTestRunner::beginNewTest (UnitTest* const test, const String& subCategory) { endTest(); currentTest = test; TestResult* const r = new TestResult(); r->unitTestName = test->getName(); r->subcategoryName = subCategory; r->passes = 0; r->failures = 0; results.add (r); logMessage ("-----------------------------------------------------------------"); logMessage ("Starting test: " + r->unitTestName + " / " + subCategory + "..."); resultsUpdated(); } void UnitTestRunner::endTest() { if (results.size() > 0) { TestResult* const r = results.getLast(); if (r->failures > 0) { String m ("FAILED!!"); m << r->failures << (r->failures == 1 ? "test" : "tests") << " failed, out of a total of " << (r->passes + r->failures); logMessage (String::empty); logMessage (m); logMessage (String::empty); } else { logMessage ("All tests completed successfully"); } } } void UnitTestRunner::addPass() { { const ScopedLock sl (results.getLock()); TestResult* const r = results.getLast(); jassert (r != 0); // You need to call UnitTest::beginTest() before performing any tests! r->passes++; String message ("Test "); message << (r->failures + r->passes) << " passed"; logMessage (message); } resultsUpdated(); } void UnitTestRunner::addFail (const String& failureMessage) { { const ScopedLock sl (results.getLock()); TestResult* const r = results.getLast(); jassert (r != 0); // You need to call UnitTest::beginTest() before performing any tests! r->failures++; String message ("!!! Test "); message << (r->failures + r->passes) << " failed"; if (failureMessage.isNotEmpty()) message << ": " << failureMessage; r->messages.add (message); logMessage (message); } resultsUpdated(); if (assertOnFailure) { jassertfalse } } END_JUCE_NAMESPACE /*** End of inlined file: juce_UnitTest.cpp ***/ #endif #if JUCE_BUILD_MISC /*** Start of inlined file: juce_ValueTree.cpp ***/ BEGIN_JUCE_NAMESPACE class ValueTree::SetPropertyAction : public UndoableAction { public: SetPropertyAction (const SharedObjectPtr& target_, const Identifier& name_, const var& newValue_, const var& oldValue_, const bool isAddingNewProperty_, const bool isDeletingProperty_) : target (target_), name (name_), newValue (newValue_), oldValue (oldValue_), isAddingNewProperty (isAddingNewProperty_), isDeletingProperty (isDeletingProperty_) { } ~SetPropertyAction() {} bool perform() { jassert (! (isAddingNewProperty && target->hasProperty (name))); if (isDeletingProperty) target->removeProperty (name, 0); else target->setProperty (name, newValue, 0); return true; } bool undo() { if (isAddingNewProperty) target->removeProperty (name, 0); else target->setProperty (name, oldValue, 0); return true; } int getSizeInUnits() { return (int) sizeof (*this); //xxx should be more accurate } UndoableAction* createCoalescedAction (UndoableAction* nextAction) { if (! (isAddingNewProperty || isDeletingProperty)) { SetPropertyAction* next = dynamic_cast (nextAction); if (next != 0 && next->target == target && next->name == name && ! (next->isAddingNewProperty || next->isDeletingProperty)) { return new SetPropertyAction (target, name, next->newValue, oldValue, false, false); } } return 0; } private: const SharedObjectPtr target; const Identifier name; const var newValue; var oldValue; const bool isAddingNewProperty : 1, isDeletingProperty : 1; SetPropertyAction (const SetPropertyAction&); SetPropertyAction& operator= (const SetPropertyAction&); }; class ValueTree::AddOrRemoveChildAction : public UndoableAction { public: AddOrRemoveChildAction (const SharedObjectPtr& target_, const int childIndex_, const SharedObjectPtr& newChild_) : target (target_), child (newChild_ != 0 ? newChild_ : target_->children [childIndex_]), childIndex (childIndex_), isDeleting (newChild_ == 0) { jassert (child != 0); } ~AddOrRemoveChildAction() {} bool perform() { if (isDeleting) target->removeChild (childIndex, 0); else target->addChild (child, childIndex, 0); return true; } bool undo() { if (isDeleting) { target->addChild (child, childIndex, 0); } else { // If you hit this, it seems that your object's state is getting confused - probably // because you've interleaved some undoable and non-undoable operations? jassert (childIndex < target->children.size()); target->removeChild (childIndex, 0); } return true; } int getSizeInUnits() { return (int) sizeof (*this); //xxx should be more accurate } private: const SharedObjectPtr target, child; const int childIndex; const bool isDeleting; AddOrRemoveChildAction (const AddOrRemoveChildAction&); AddOrRemoveChildAction& operator= (const AddOrRemoveChildAction&); }; class ValueTree::MoveChildAction : public UndoableAction { public: MoveChildAction (const SharedObjectPtr& parent_, const int startIndex_, const int endIndex_) : parent (parent_), startIndex (startIndex_), endIndex (endIndex_) { } ~MoveChildAction() {} bool perform() { parent->moveChild (startIndex, endIndex, 0); return true; } bool undo() { parent->moveChild (endIndex, startIndex, 0); return true; } int getSizeInUnits() { return (int) sizeof (*this); //xxx should be more accurate } UndoableAction* createCoalescedAction (UndoableAction* nextAction) { MoveChildAction* next = dynamic_cast (nextAction); if (next != 0 && next->parent == parent && next->startIndex == endIndex) return new MoveChildAction (parent, startIndex, next->endIndex); return 0; } private: const SharedObjectPtr parent; const int startIndex, endIndex; MoveChildAction (const MoveChildAction&); MoveChildAction& operator= (const MoveChildAction&); }; ValueTree::SharedObject::SharedObject (const Identifier& type_) : type (type_), parent (0) { } ValueTree::SharedObject::SharedObject (const SharedObject& other) : type (other.type), properties (other.properties), parent (0) { for (int i = 0; i < other.children.size(); ++i) { SharedObject* const child = new SharedObject (*other.children.getUnchecked(i)); child->parent = this; children.add (child); } } ValueTree::SharedObject::~SharedObject() { jassert (parent == 0); // this should never happen unless something isn't obeying the ref-counting! for (int i = children.size(); --i >= 0;) { const SharedObjectPtr c (children.getUnchecked(i)); c->parent = 0; children.remove (i); c->sendParentChangeMessage(); } } void ValueTree::SharedObject::sendPropertyChangeMessage (ValueTree& tree, const Identifier& property) { for (int i = valueTreesWithListeners.size(); --i >= 0;) { ValueTree* const v = valueTreesWithListeners[i]; if (v != 0) v->listeners.call (&ValueTree::Listener::valueTreePropertyChanged, tree, property); } } void ValueTree::SharedObject::sendPropertyChangeMessage (const Identifier& property) { ValueTree tree (this); ValueTree::SharedObject* t = this; while (t != 0) { t->sendPropertyChangeMessage (tree, property); t = t->parent; } } void ValueTree::SharedObject::sendChildChangeMessage (ValueTree& tree) { for (int i = valueTreesWithListeners.size(); --i >= 0;) { ValueTree* const v = valueTreesWithListeners[i]; if (v != 0) v->listeners.call (&ValueTree::Listener::valueTreeChildrenChanged, tree); } } void ValueTree::SharedObject::sendChildChangeMessage() { ValueTree tree (this); ValueTree::SharedObject* t = this; while (t != 0) { t->sendChildChangeMessage (tree); t = t->parent; } } void ValueTree::SharedObject::sendParentChangeMessage() { ValueTree tree (this); int i; for (i = children.size(); --i >= 0;) { SharedObject* const t = children[i]; if (t != 0) t->sendParentChangeMessage(); } for (i = valueTreesWithListeners.size(); --i >= 0;) { ValueTree* const v = valueTreesWithListeners[i]; if (v != 0) v->listeners.call (&ValueTree::Listener::valueTreeParentChanged, tree); } } const var& ValueTree::SharedObject::getProperty (const Identifier& name) const { return properties [name]; } const var ValueTree::SharedObject::getProperty (const Identifier& name, const var& defaultReturnValue) const { return properties.getWithDefault (name, defaultReturnValue); } void ValueTree::SharedObject::setProperty (const Identifier& name, const var& newValue, UndoManager* const undoManager) { if (undoManager == 0) { if (properties.set (name, newValue)) sendPropertyChangeMessage (name); } else { var* const existingValue = properties.getItem (name); if (existingValue != 0) { if (*existingValue != newValue) undoManager->perform (new SetPropertyAction (this, name, newValue, properties [name], false, false)); } else { undoManager->perform (new SetPropertyAction (this, name, newValue, var::null, true, false)); } } } bool ValueTree::SharedObject::hasProperty (const Identifier& name) const { return properties.contains (name); } void ValueTree::SharedObject::removeProperty (const Identifier& name, UndoManager* const undoManager) { if (undoManager == 0) { if (properties.remove (name)) sendPropertyChangeMessage (name); } else { if (properties.contains (name)) undoManager->perform (new SetPropertyAction (this, name, var::null, properties [name], false, true)); } } void ValueTree::SharedObject::removeAllProperties (UndoManager* const undoManager) { if (undoManager == 0) { while (properties.size() > 0) { const Identifier name (properties.getName (properties.size() - 1)); properties.remove (name); sendPropertyChangeMessage (name); } } else { for (int i = properties.size(); --i >= 0;) undoManager->perform (new SetPropertyAction (this, properties.getName(i), var::null, properties.getValueAt(i), false, true)); } } ValueTree ValueTree::SharedObject::getChildWithName (const Identifier& typeToMatch) const { for (int i = 0; i < children.size(); ++i) if (children.getUnchecked(i)->type == typeToMatch) return ValueTree (static_cast (children.getUnchecked(i))); return ValueTree::invalid; } ValueTree ValueTree::SharedObject::getOrCreateChildWithName (const Identifier& typeToMatch, UndoManager* undoManager) { for (int i = 0; i < children.size(); ++i) if (children.getUnchecked(i)->type == typeToMatch) return ValueTree (static_cast (children.getUnchecked(i))); SharedObject* const newObject = new SharedObject (typeToMatch); addChild (newObject, -1, undoManager); return ValueTree (newObject); } ValueTree ValueTree::SharedObject::getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const { for (int i = 0; i < children.size(); ++i) if (children.getUnchecked(i)->getProperty (propertyName) == propertyValue) return ValueTree (static_cast (children.getUnchecked(i))); return ValueTree::invalid; } bool ValueTree::SharedObject::isAChildOf (const SharedObject* const possibleParent) const { const SharedObject* p = parent; while (p != 0) { if (p == possibleParent) return true; p = p->parent; } return false; } int ValueTree::SharedObject::indexOf (const ValueTree& child) const { return children.indexOf (child.object); } void ValueTree::SharedObject::addChild (SharedObject* child, int index, UndoManager* const undoManager) { if (child != 0 && child->parent != this) { if (child != this && ! isAChildOf (child)) { // You should always make sure that a child is removed from its previous parent before // adding it somewhere else - otherwise, it's ambiguous as to whether a different // undomanager should be used when removing it from its current parent.. jassert (child->parent == 0); if (child->parent != 0) { jassert (child->parent->children.indexOf (child) >= 0); child->parent->removeChild (child->parent->children.indexOf (child), undoManager); } if (undoManager == 0) { children.insert (index, child); child->parent = this; sendChildChangeMessage(); child->sendParentChangeMessage(); } else { if (index < 0) index = children.size(); undoManager->perform (new AddOrRemoveChildAction (this, index, child)); } } else { // You're attempting to create a recursive loop! A node // can't be a child of one of its own children! jassertfalse; } } } void ValueTree::SharedObject::removeChild (const int childIndex, UndoManager* const undoManager) { const SharedObjectPtr child (children [childIndex]); if (child != 0) { if (undoManager == 0) { children.remove (childIndex); child->parent = 0; sendChildChangeMessage(); child->sendParentChangeMessage(); } else { undoManager->perform (new AddOrRemoveChildAction (this, childIndex, 0)); } } } void ValueTree::SharedObject::removeAllChildren (UndoManager* const undoManager) { while (children.size() > 0) removeChild (children.size() - 1, undoManager); } void ValueTree::SharedObject::moveChild (int currentIndex, int newIndex, UndoManager* undoManager) { // The source index must be a valid index! jassert (((unsigned int) currentIndex) < (unsigned int) children.size()); if (currentIndex != newIndex && ((unsigned int) currentIndex) < (unsigned int) children.size()) { if (undoManager == 0) { children.move (currentIndex, newIndex); sendChildChangeMessage(); } else { if (((unsigned int) newIndex) >= (unsigned int) children.size()) newIndex = children.size() - 1; undoManager->perform (new MoveChildAction (this, currentIndex, newIndex)); } } } bool ValueTree::SharedObject::isEquivalentTo (const SharedObject& other) const { if (type != other.type || properties.size() != other.properties.size() || children.size() != other.children.size() || properties != other.properties) return false; for (int i = 0; i < children.size(); ++i) if (! children.getUnchecked(i)->isEquivalentTo (*other.children.getUnchecked(i))) return false; return true; } ValueTree::ValueTree() throw() : object (0) { } const ValueTree ValueTree::invalid; ValueTree::ValueTree (const Identifier& type_) : object (new ValueTree::SharedObject (type_)) { jassert (type_.toString().isNotEmpty()); // All objects should be given a sensible type name! } ValueTree::ValueTree (SharedObject* const object_) : object (object_) { } ValueTree::ValueTree (const ValueTree& other) : object (other.object) { } ValueTree& ValueTree::operator= (const ValueTree& other) { if (listeners.size() > 0) { if (object != 0) object->valueTreesWithListeners.removeValue (this); if (other.object != 0) other.object->valueTreesWithListeners.add (this); } object = other.object; return *this; } ValueTree::~ValueTree() { if (listeners.size() > 0 && object != 0) object->valueTreesWithListeners.removeValue (this); } bool ValueTree::operator== (const ValueTree& other) const throw() { return object == other.object; } bool ValueTree::operator!= (const ValueTree& other) const throw() { return object != other.object; } bool ValueTree::isEquivalentTo (const ValueTree& other) const { return object == other.object || (object != 0 && other.object != 0 && object->isEquivalentTo (*other.object)); } ValueTree ValueTree::createCopy() const { return ValueTree (object != 0 ? new SharedObject (*object) : 0); } bool ValueTree::hasType (const Identifier& typeName) const { return object != 0 && object->type == typeName; } const Identifier ValueTree::getType() const { return object != 0 ? object->type : Identifier(); } ValueTree ValueTree::getParent() const { return ValueTree (object != 0 ? object->parent : (SharedObject*) 0); } ValueTree ValueTree::getSibling (const int delta) const { if (object == 0 || object->parent == 0) return invalid; const int index = object->parent->indexOf (*this) + delta; return ValueTree (static_cast (object->parent->children [index])); } const var& ValueTree::operator[] (const Identifier& name) const { return object == 0 ? var::null : object->getProperty (name); } const var& ValueTree::getProperty (const Identifier& name) const { return object == 0 ? var::null : object->getProperty (name); } const var ValueTree::getProperty (const Identifier& name, const var& defaultReturnValue) const { return object == 0 ? defaultReturnValue : object->getProperty (name, defaultReturnValue); } void ValueTree::setProperty (const Identifier& name, const var& newValue, UndoManager* const undoManager) { jassert (name.toString().isNotEmpty()); if (object != 0 && name.toString().isNotEmpty()) object->setProperty (name, newValue, undoManager); } bool ValueTree::hasProperty (const Identifier& name) const { return object != 0 && object->hasProperty (name); } void ValueTree::removeProperty (const Identifier& name, UndoManager* const undoManager) { if (object != 0) object->removeProperty (name, undoManager); } void ValueTree::removeAllProperties (UndoManager* const undoManager) { if (object != 0) object->removeAllProperties (undoManager); } int ValueTree::getNumProperties() const { return object == 0 ? 0 : object->properties.size(); } const Identifier ValueTree::getPropertyName (const int index) const { return object == 0 ? Identifier() : object->properties.getName (index); } class ValueTreePropertyValueSource : public Value::ValueSource, public ValueTree::Listener { public: ValueTreePropertyValueSource (const ValueTree& tree_, const Identifier& property_, UndoManager* const undoManager_) : tree (tree_), property (property_), undoManager (undoManager_) { tree.addListener (this); } ~ValueTreePropertyValueSource() { tree.removeListener (this); } const var getValue() const { return tree [property]; } void setValue (const var& newValue) { tree.setProperty (property, newValue, undoManager); } void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged, const Identifier& changedProperty) { if (tree == treeWhosePropertyHasChanged && property == changedProperty) sendChangeMessage (false); } void valueTreeChildrenChanged (ValueTree&) {} void valueTreeParentChanged (ValueTree&) {} private: ValueTree tree; const Identifier property; UndoManager* const undoManager; ValueTreePropertyValueSource& operator= (const ValueTreePropertyValueSource&); }; Value ValueTree::getPropertyAsValue (const Identifier& name, UndoManager* const undoManager) const { return Value (new ValueTreePropertyValueSource (*this, name, undoManager)); } int ValueTree::getNumChildren() const { return object == 0 ? 0 : object->children.size(); } ValueTree ValueTree::getChild (int index) const { return ValueTree (object != 0 ? (SharedObject*) object->children [index] : (SharedObject*) 0); } ValueTree ValueTree::getChildWithName (const Identifier& type) const { return object != 0 ? object->getChildWithName (type) : ValueTree::invalid; } ValueTree ValueTree::getOrCreateChildWithName (const Identifier& type, UndoManager* undoManager) { return object != 0 ? object->getOrCreateChildWithName (type, undoManager) : ValueTree::invalid; } ValueTree ValueTree::getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const { return object != 0 ? object->getChildWithProperty (propertyName, propertyValue) : ValueTree::invalid; } bool ValueTree::isAChildOf (const ValueTree& possibleParent) const { return object != 0 && object->isAChildOf (possibleParent.object); } int ValueTree::indexOf (const ValueTree& child) const { return object != 0 ? object->indexOf (child) : -1; } void ValueTree::addChild (const ValueTree& child, int index, UndoManager* const undoManager) { if (object != 0) object->addChild (child.object, index, undoManager); } void ValueTree::removeChild (const int childIndex, UndoManager* const undoManager) { if (object != 0) object->removeChild (childIndex, undoManager); } void ValueTree::removeChild (const ValueTree& child, UndoManager* const undoManager) { if (object != 0) object->removeChild (object->children.indexOf (child.object), undoManager); } void ValueTree::removeAllChildren (UndoManager* const undoManager) { if (object != 0) object->removeAllChildren (undoManager); } void ValueTree::moveChild (int currentIndex, int newIndex, UndoManager* undoManager) { if (object != 0) object->moveChild (currentIndex, newIndex, undoManager); } void ValueTree::addListener (Listener* listener) { if (listener != 0) { if (listeners.size() == 0 && object != 0) object->valueTreesWithListeners.add (this); listeners.add (listener); } } void ValueTree::removeListener (Listener* listener) { listeners.remove (listener); if (listeners.size() == 0 && object != 0) object->valueTreesWithListeners.removeValue (this); } XmlElement* ValueTree::SharedObject::createXml() const { XmlElement* xml = new XmlElement (type.toString()); int i; for (i = 0; i < properties.size(); ++i) { Identifier name (properties.getName(i)); const var& v = properties [name]; jassert (! v.isObject()); // DynamicObjects can't be stored as XML! xml->setAttribute (name.toString(), v.toString()); } for (i = 0; i < children.size(); ++i) xml->addChildElement (children.getUnchecked(i)->createXml()); return xml; } XmlElement* ValueTree::createXml() const { return object != 0 ? object->createXml() : 0; } ValueTree ValueTree::fromXml (const XmlElement& xml) { ValueTree v (xml.getTagName()); const int numAtts = xml.getNumAttributes(); // xxx inefficient - should write an att iterator.. for (int i = 0; i < numAtts; ++i) v.setProperty (xml.getAttributeName (i), var (xml.getAttributeValue (i)), 0); forEachXmlChildElement (xml, e) { v.addChild (fromXml (*e), -1, 0); } return v; } void ValueTree::writeToStream (OutputStream& output) { output.writeString (getType().toString()); const int numProps = getNumProperties(); output.writeCompressedInt (numProps); int i; for (i = 0; i < numProps; ++i) { const Identifier name (getPropertyName(i)); output.writeString (name.toString()); getProperty(name).writeToStream (output); } const int numChildren = getNumChildren(); output.writeCompressedInt (numChildren); for (i = 0; i < numChildren; ++i) getChild (i).writeToStream (output); } ValueTree ValueTree::readFromStream (InputStream& input) { const String type (input.readString()); if (type.isEmpty()) return ValueTree::invalid; ValueTree v (type); const int numProps = input.readCompressedInt(); if (numProps < 0) { jassertfalse; // trying to read corrupted data! return v; } int i; for (i = 0; i < numProps; ++i) { const String name (input.readString()); jassert (name.isNotEmpty()); const var value (var::readFromStream (input)); v.setProperty (name, value, 0); } const int numChildren = input.readCompressedInt(); for (i = 0; i < numChildren; ++i) v.addChild (readFromStream (input), -1, 0); return v; } ValueTree ValueTree::readFromData (const void* const data, const size_t numBytes) { MemoryInputStream in (data, numBytes, false); return readFromStream (in); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ValueTree.cpp ***/ /*** Start of inlined file: juce_Value.cpp ***/ BEGIN_JUCE_NAMESPACE Value::ValueSource::ValueSource() { } Value::ValueSource::~ValueSource() { } void Value::ValueSource::sendChangeMessage (const bool synchronous) { if (synchronous) { for (int i = valuesWithListeners.size(); --i >= 0;) { Value* const v = valuesWithListeners[i]; if (v != 0) v->callListeners(); } } else { triggerAsyncUpdate(); } } void Value::ValueSource::handleAsyncUpdate() { sendChangeMessage (true); } class SimpleValueSource : public Value::ValueSource { public: SimpleValueSource() { } SimpleValueSource (const var& initialValue) : value (initialValue) { } ~SimpleValueSource() { } const var getValue() const { return value; } void setValue (const var& newValue) { if (newValue != value) { value = newValue; sendChangeMessage (false); } } private: var value; SimpleValueSource (const SimpleValueSource&); SimpleValueSource& operator= (const SimpleValueSource&); }; Value::Value() : value (new SimpleValueSource()) { } Value::Value (ValueSource* const value_) : value (value_) { jassert (value_ != 0); } Value::Value (const var& initialValue) : value (new SimpleValueSource (initialValue)) { } Value::Value (const Value& other) : value (other.value) { } Value& Value::operator= (const Value& other) { value = other.value; return *this; } Value::~Value() { if (listeners.size() > 0) value->valuesWithListeners.removeValue (this); } const var Value::getValue() const { return value->getValue(); } Value::operator const var() const { return getValue(); } void Value::setValue (const var& newValue) { value->setValue (newValue); } const String Value::toString() const { return value->getValue().toString(); } Value& Value::operator= (const var& newValue) { value->setValue (newValue); return *this; } void Value::referTo (const Value& valueToReferTo) { if (valueToReferTo.value != value) { if (listeners.size() > 0) { value->valuesWithListeners.removeValue (this); valueToReferTo.value->valuesWithListeners.add (this); } value = valueToReferTo.value; callListeners(); } } bool Value::refersToSameSourceAs (const Value& other) const { return value == other.value; } bool Value::operator== (const Value& other) const { return value == other.value || value->getValue() == other.getValue(); } bool Value::operator!= (const Value& other) const { return value != other.value && value->getValue() != other.getValue(); } void Value::addListener (Listener* const listener) { if (listener != 0) { if (listeners.size() == 0) value->valuesWithListeners.add (this); listeners.add (listener); } } void Value::removeListener (Listener* const listener) { listeners.remove (listener); if (listeners.size() == 0) value->valuesWithListeners.removeValue (this); } void Value::callListeners() { Value v (*this); // (create a copy in case this gets deleted by a callback) listeners.call (&Value::Listener::valueChanged, v); } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const Value& value) { return stream << value.toString(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_Value.cpp ***/ /*** Start of inlined file: juce_Application.cpp ***/ BEGIN_JUCE_NAMESPACE #if JUCE_MAC extern void juce_initialiseMacMainMenu(); #endif JUCEApplication::JUCEApplication() : appReturnValue (0), stillInitialising (true) { jassert (isStandaloneApp() && appInstance == 0); appInstance = this; } JUCEApplication::~JUCEApplication() { if (appLock != 0) { appLock->exit(); appLock = 0; } jassert (appInstance == this); appInstance = 0; } JUCEApplication::CreateInstanceFunction JUCEApplication::createInstance = 0; JUCEApplication* JUCEApplication::appInstance = 0; bool JUCEApplication::moreThanOneInstanceAllowed() { return true; } void JUCEApplication::anotherInstanceStarted (const String&) { } void JUCEApplication::systemRequestedQuit() { quit(); } void JUCEApplication::quit() { MessageManager::getInstance()->stopDispatchLoop(); } void JUCEApplication::setApplicationReturnValue (const int newReturnValue) throw() { appReturnValue = newReturnValue; } void JUCEApplication::actionListenerCallback (const String& message) { if (message.startsWith (getApplicationName() + "/")) anotherInstanceStarted (message.substring (getApplicationName().length() + 1)); } void JUCEApplication::unhandledException (const std::exception*, const String&, const int) { jassertfalse; } void JUCEApplication::sendUnhandledException (const std::exception* const e, const char* const sourceFile, const int lineNumber) { if (appInstance != 0) appInstance->unhandledException (e, sourceFile, lineNumber); } ApplicationCommandTarget* JUCEApplication::getNextCommandTarget() { return 0; } void JUCEApplication::getAllCommands (Array & commands) { commands.add (StandardApplicationCommandIDs::quit); } void JUCEApplication::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result) { if (commandID == StandardApplicationCommandIDs::quit) { result.setInfo (TRANS("Quit"), TRANS("Quits the application"), "Application", 0); result.defaultKeypresses.add (KeyPress ('q', ModifierKeys::commandModifier, 0)); } } bool JUCEApplication::perform (const InvocationInfo& info) { if (info.commandID == StandardApplicationCommandIDs::quit) { systemRequestedQuit(); return true; } return false; } bool JUCEApplication::initialiseApp (const String& commandLine) { commandLineParameters = commandLine.trim(); #if ! JUCE_IOS jassert (appLock == 0); // initialiseApp must only be called once! if (! moreThanOneInstanceAllowed()) { appLock = new InterProcessLock ("juceAppLock_" + getApplicationName()); if (! appLock->enter(0)) { appLock = 0; MessageManager::broadcastMessage (getApplicationName() + "/" + commandLineParameters); DBG ("Another instance is running - quitting..."); return false; } } #endif // let the app do its setting-up.. initialise (commandLineParameters); #if JUCE_MAC juce_initialiseMacMainMenu(); // needs to be called after the app object has created, to get its name #endif // register for broadcast new app messages MessageManager::getInstance()->registerBroadcastListener (this); stillInitialising = false; return true; } int JUCEApplication::shutdownApp() { jassert (appInstance == this); MessageManager::getInstance()->deregisterBroadcastListener (this); JUCE_TRY { // give the app a chance to clean up.. shutdown(); } JUCE_CATCH_EXCEPTION return getApplicationReturnValue(); } int JUCEApplication::main (const String& commandLine) { ScopedJuceInitialiser_GUI libraryInitialiser; jassert (createInstance != 0); const ScopedPointer app (createInstance()); if (! app->initialiseApp (commandLine)) return 0; JUCE_TRY { // loop until a quit message is received.. MessageManager::getInstance()->runDispatchLoop(); } JUCE_CATCH_EXCEPTION return app->shutdownApp(); } #if JUCE_IOS extern int juce_iOSMain (int argc, const char* argv[]); #endif #if ! JUCE_WINDOWS extern const char* juce_Argv0; #endif int JUCEApplication::main (int argc, const char* argv[]) { JUCE_AUTORELEASEPOOL #if ! JUCE_WINDOWS jassert (createInstance != 0); juce_Argv0 = argv[0]; #endif #if JUCE_IOS return juce_iOSMain (argc, argv); #else String cmd; for (int i = 1; i < argc; ++i) cmd << argv[i] << ' '; return JUCEApplication::main (cmd); #endif } END_JUCE_NAMESPACE /*** End of inlined file: juce_Application.cpp ***/ /*** Start of inlined file: juce_ApplicationCommandInfo.cpp ***/ BEGIN_JUCE_NAMESPACE ApplicationCommandInfo::ApplicationCommandInfo (const CommandID commandID_) throw() : commandID (commandID_), flags (0) { } void ApplicationCommandInfo::setInfo (const String& shortName_, const String& description_, const String& categoryName_, const int flags_) throw() { shortName = shortName_; description = description_; categoryName = categoryName_; flags = flags_; } void ApplicationCommandInfo::setActive (const bool b) throw() { if (b) flags &= ~isDisabled; else flags |= isDisabled; } void ApplicationCommandInfo::setTicked (const bool b) throw() { if (b) flags |= isTicked; else flags &= ~isTicked; } void ApplicationCommandInfo::addDefaultKeypress (const int keyCode, const ModifierKeys& modifiers) throw() { defaultKeypresses.add (KeyPress (keyCode, modifiers, 0)); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ApplicationCommandInfo.cpp ***/ /*** Start of inlined file: juce_ApplicationCommandManager.cpp ***/ BEGIN_JUCE_NAMESPACE ApplicationCommandManager::ApplicationCommandManager() : firstTarget (0) { keyMappings = new KeyPressMappingSet (this); Desktop::getInstance().addFocusChangeListener (this); } ApplicationCommandManager::~ApplicationCommandManager() { Desktop::getInstance().removeFocusChangeListener (this); keyMappings = 0; } void ApplicationCommandManager::clearCommands() { commands.clear(); keyMappings->clearAllKeyPresses(); triggerAsyncUpdate(); } void ApplicationCommandManager::registerCommand (const ApplicationCommandInfo& newCommand) { // zero isn't a valid command ID! jassert (newCommand.commandID != 0); // the name isn't optional! jassert (newCommand.shortName.isNotEmpty()); if (getCommandForID (newCommand.commandID) == 0) { ApplicationCommandInfo* const newInfo = new ApplicationCommandInfo (newCommand); newInfo->flags &= ~ApplicationCommandInfo::isTicked; commands.add (newInfo); keyMappings->resetToDefaultMapping (newCommand.commandID); triggerAsyncUpdate(); } else { // trying to re-register the same command with different parameters? jassert (newCommand.shortName == getCommandForID (newCommand.commandID)->shortName && (newCommand.description == getCommandForID (newCommand.commandID)->description || newCommand.description.isEmpty()) && newCommand.categoryName == getCommandForID (newCommand.commandID)->categoryName && newCommand.defaultKeypresses == getCommandForID (newCommand.commandID)->defaultKeypresses && (newCommand.flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor)) == (getCommandForID (newCommand.commandID)->flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor))); } } void ApplicationCommandManager::registerAllCommandsForTarget (ApplicationCommandTarget* target) { if (target != 0) { Array commandIDs; target->getAllCommands (commandIDs); for (int i = 0; i < commandIDs.size(); ++i) { ApplicationCommandInfo info (commandIDs.getUnchecked(i)); target->getCommandInfo (info.commandID, info); registerCommand (info); } } } void ApplicationCommandManager::removeCommand (const CommandID commandID) { for (int i = commands.size(); --i >= 0;) { if (commands.getUnchecked (i)->commandID == commandID) { commands.remove (i); triggerAsyncUpdate(); const Array keys (keyMappings->getKeyPressesAssignedToCommand (commandID)); for (int j = keys.size(); --j >= 0;) keyMappings->removeKeyPress (keys.getReference (j)); } } } void ApplicationCommandManager::commandStatusChanged() { triggerAsyncUpdate(); } const ApplicationCommandInfo* ApplicationCommandManager::getCommandForID (const CommandID commandID) const throw() { for (int i = commands.size(); --i >= 0;) if (commands.getUnchecked(i)->commandID == commandID) return commands.getUnchecked(i); return 0; } const String ApplicationCommandManager::getNameOfCommand (const CommandID commandID) const throw() { const ApplicationCommandInfo* const ci = getCommandForID (commandID); return (ci != 0) ? ci->shortName : String::empty; } const String ApplicationCommandManager::getDescriptionOfCommand (const CommandID commandID) const throw() { const ApplicationCommandInfo* const ci = getCommandForID (commandID); return (ci != 0) ? (ci->description.isNotEmpty() ? ci->description : ci->shortName) : String::empty; } const StringArray ApplicationCommandManager::getCommandCategories() const { StringArray s; for (int i = 0; i < commands.size(); ++i) s.addIfNotAlreadyThere (commands.getUnchecked(i)->categoryName, false); return s; } const Array ApplicationCommandManager::getCommandsInCategory (const String& categoryName) const { Array results; for (int i = 0; i < commands.size(); ++i) if (commands.getUnchecked(i)->categoryName == categoryName) results.add (commands.getUnchecked(i)->commandID); return results; } bool ApplicationCommandManager::invokeDirectly (const CommandID commandID, const bool asynchronously) { ApplicationCommandTarget::InvocationInfo info (commandID); info.invocationMethod = ApplicationCommandTarget::InvocationInfo::direct; return invoke (info, asynchronously); } bool ApplicationCommandManager::invoke (const ApplicationCommandTarget::InvocationInfo& info_, const bool asynchronously) { // This call isn't thread-safe for use from a non-UI thread without locking the message // manager first.. jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); ApplicationCommandTarget* const target = getFirstCommandTarget (info_.commandID); if (target == 0) return false; ApplicationCommandInfo commandInfo (0); target->getCommandInfo (info_.commandID, commandInfo); ApplicationCommandTarget::InvocationInfo info (info_); info.commandFlags = commandInfo.flags; sendListenerInvokeCallback (info); const bool ok = target->invoke (info, asynchronously); commandStatusChanged(); return ok; } ApplicationCommandTarget* ApplicationCommandManager::getFirstCommandTarget (const CommandID) { return firstTarget != 0 ? firstTarget : findDefaultComponentTarget(); } void ApplicationCommandManager::setFirstCommandTarget (ApplicationCommandTarget* const newTarget) throw() { firstTarget = newTarget; } ApplicationCommandTarget* ApplicationCommandManager::getTargetForCommand (const CommandID commandID, ApplicationCommandInfo& upToDateInfo) { ApplicationCommandTarget* target = getFirstCommandTarget (commandID); if (target == 0) target = JUCEApplication::getInstance(); if (target != 0) target = target->getTargetForCommand (commandID); if (target != 0) target->getCommandInfo (commandID, upToDateInfo); return target; } ApplicationCommandTarget* ApplicationCommandManager::findTargetForComponent (Component* c) { ApplicationCommandTarget* target = dynamic_cast (c); if (target == 0 && c != 0) // (unable to use the syntax findParentComponentOfClass () because of a VC6 compiler bug) target = c->findParentComponentOfClass ((ApplicationCommandTarget*) 0); return target; } ApplicationCommandTarget* ApplicationCommandManager::findDefaultComponentTarget() { Component* c = Component::getCurrentlyFocusedComponent(); if (c == 0) { TopLevelWindow* const activeWindow = TopLevelWindow::getActiveTopLevelWindow(); if (activeWindow != 0) { c = activeWindow->getPeer()->getLastFocusedSubcomponent(); if (c == 0) c = activeWindow; } } if (c == 0 && Process::isForegroundProcess()) { // getting a bit desperate now - try all desktop comps.. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;) { ApplicationCommandTarget* const target = findTargetForComponent (Desktop::getInstance().getComponent (i) ->getPeer()->getLastFocusedSubcomponent()); if (target != 0) return target; } } if (c != 0) { ResizableWindow* const resizableWindow = dynamic_cast (c); // if we're focused on a ResizableWindow, chances are that it's the content // component that really should get the event. And if not, the event will // still be passed up to the top level window anyway, so let's send it to the // content comp. if (resizableWindow != 0 && resizableWindow->getContentComponent() != 0) c = resizableWindow->getContentComponent(); ApplicationCommandTarget* const target = findTargetForComponent (c); if (target != 0) return target; } return JUCEApplication::getInstance(); } void ApplicationCommandManager::addListener (ApplicationCommandManagerListener* const listener) { listeners.add (listener); } void ApplicationCommandManager::removeListener (ApplicationCommandManagerListener* const listener) { listeners.remove (listener); } void ApplicationCommandManager::sendListenerInvokeCallback (const ApplicationCommandTarget::InvocationInfo& info) { listeners.call (&ApplicationCommandManagerListener::applicationCommandInvoked, info); } void ApplicationCommandManager::handleAsyncUpdate() { listeners.call (&ApplicationCommandManagerListener::applicationCommandListChanged); } void ApplicationCommandManager::globalFocusChanged (Component*) { commandStatusChanged(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ApplicationCommandManager.cpp ***/ /*** Start of inlined file: juce_ApplicationCommandTarget.cpp ***/ BEGIN_JUCE_NAMESPACE ApplicationCommandTarget::ApplicationCommandTarget() { } ApplicationCommandTarget::~ApplicationCommandTarget() { messageInvoker = 0; } bool ApplicationCommandTarget::tryToInvoke (const InvocationInfo& info, const bool async) { if (isCommandActive (info.commandID)) { if (async) { if (messageInvoker == 0) messageInvoker = new CommandTargetMessageInvoker (this); messageInvoker->postMessage (new Message (0, 0, 0, new ApplicationCommandTarget::InvocationInfo (info))); return true; } else { const bool success = perform (info); jassert (success); // hmm - your target should have been able to perform this command. If it can't // do it at the moment for some reason, it should clear the 'isActive' flag when it // returns the command's info. return success; } } return false; } ApplicationCommandTarget* ApplicationCommandTarget::findFirstTargetParentComponent() { Component* c = dynamic_cast (this); if (c != 0) // (unable to use the syntax findParentComponentOfClass () because of a VC6 compiler bug) return c->findParentComponentOfClass ((ApplicationCommandTarget*) 0); return 0; } ApplicationCommandTarget* ApplicationCommandTarget::getTargetForCommand (const CommandID commandID) { ApplicationCommandTarget* target = this; int depth = 0; while (target != 0) { Array commandIDs; target->getAllCommands (commandIDs); if (commandIDs.contains (commandID)) return target; target = target->getNextCommandTarget(); ++depth; jassert (depth < 100); // could be a recursive command chain?? jassert (target != this); // definitely a recursive command chain! if (depth > 100 || target == this) break; } if (target == 0) { target = JUCEApplication::getInstance(); if (target != 0) { Array commandIDs; target->getAllCommands (commandIDs); if (commandIDs.contains (commandID)) return target; } } return 0; } bool ApplicationCommandTarget::isCommandActive (const CommandID commandID) { ApplicationCommandInfo info (commandID); info.flags = ApplicationCommandInfo::isDisabled; getCommandInfo (commandID, info); return (info.flags & ApplicationCommandInfo::isDisabled) == 0; } bool ApplicationCommandTarget::invoke (const InvocationInfo& info, const bool async) { ApplicationCommandTarget* target = this; int depth = 0; while (target != 0) { if (target->tryToInvoke (info, async)) return true; target = target->getNextCommandTarget(); ++depth; jassert (depth < 100); // could be a recursive command chain?? jassert (target != this); // definitely a recursive command chain! if (depth > 100 || target == this) break; } if (target == 0) { target = JUCEApplication::getInstance(); if (target != 0) return target->tryToInvoke (info, async); } return false; } bool ApplicationCommandTarget::invokeDirectly (const CommandID commandID, const bool asynchronously) { ApplicationCommandTarget::InvocationInfo info (commandID); info.invocationMethod = ApplicationCommandTarget::InvocationInfo::direct; return invoke (info, asynchronously); } ApplicationCommandTarget::InvocationInfo::InvocationInfo (const CommandID commandID_) : commandID (commandID_), commandFlags (0), invocationMethod (direct), originatingComponent (0), isKeyDown (false), millisecsSinceKeyPressed (0) { } ApplicationCommandTarget::CommandTargetMessageInvoker::CommandTargetMessageInvoker (ApplicationCommandTarget* const owner_) : owner (owner_) { } ApplicationCommandTarget::CommandTargetMessageInvoker::~CommandTargetMessageInvoker() { } void ApplicationCommandTarget::CommandTargetMessageInvoker::handleMessage (const Message& message) { const ScopedPointer info (static_cast (message.pointerParameter)); owner->tryToInvoke (*info, false); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ApplicationCommandTarget.cpp ***/ /*** Start of inlined file: juce_ApplicationProperties.cpp ***/ BEGIN_JUCE_NAMESPACE juce_ImplementSingleton (ApplicationProperties) ApplicationProperties::ApplicationProperties() : msBeforeSaving (3000), options (PropertiesFile::storeAsBinary), commonSettingsAreReadOnly (0), processLock (0) { } ApplicationProperties::~ApplicationProperties() { closeFiles(); clearSingletonInstance(); } void ApplicationProperties::setStorageParameters (const String& applicationName, const String& fileNameSuffix, const String& folderName_, const int millisecondsBeforeSaving, const int propertiesFileOptions, InterProcessLock* processLock_) { appName = applicationName; fileSuffix = fileNameSuffix; folderName = folderName_; msBeforeSaving = millisecondsBeforeSaving; options = propertiesFileOptions; processLock = processLock_; } bool ApplicationProperties::testWriteAccess (const bool testUserSettings, const bool testCommonSettings, const bool showWarningDialogOnFailure) { const bool userOk = (! testUserSettings) || getUserSettings()->save(); const bool commonOk = (! testCommonSettings) || getCommonSettings (false)->save(); if (! (userOk && commonOk)) { if (showWarningDialogOnFailure) { String filenames; if (userProps != 0 && ! userOk) filenames << '\n' << userProps->getFile().getFullPathName(); if (commonProps != 0 && ! commonOk) filenames << '\n' << commonProps->getFile().getFullPathName(); AlertWindow::showMessageBox (AlertWindow::WarningIcon, appName + TRANS(" - Unable to save settings"), TRANS("An error occurred when trying to save the application's settings file...\n\nIn order to save and restore its settings, ") + appName + TRANS(" needs to be able to write to the following files:\n") + filenames + TRANS("\n\nMake sure that these files aren't read-only, and that the disk isn't full.")); } return false; } return true; } void ApplicationProperties::openFiles() { // You need to call setStorageParameters() before trying to get hold of the // properties! jassert (appName.isNotEmpty()); if (appName.isNotEmpty()) { if (userProps == 0) userProps = PropertiesFile::createDefaultAppPropertiesFile (appName, fileSuffix, folderName, false, msBeforeSaving, options, processLock); if (commonProps == 0) commonProps = PropertiesFile::createDefaultAppPropertiesFile (appName, fileSuffix, folderName, true, msBeforeSaving, options, processLock); userProps->setFallbackPropertySet (commonProps); } } PropertiesFile* ApplicationProperties::getUserSettings() { if (userProps == 0) openFiles(); return userProps; } PropertiesFile* ApplicationProperties::getCommonSettings (const bool returnUserPropsIfReadOnly) { if (commonProps == 0) openFiles(); if (returnUserPropsIfReadOnly) { if (commonSettingsAreReadOnly == 0) commonSettingsAreReadOnly = commonProps->save() ? -1 : 1; if (commonSettingsAreReadOnly > 0) return userProps; } return commonProps; } bool ApplicationProperties::saveIfNeeded() { return (userProps == 0 || userProps->saveIfNeeded()) && (commonProps == 0 || commonProps->saveIfNeeded()); } void ApplicationProperties::closeFiles() { userProps = 0; commonProps = 0; } END_JUCE_NAMESPACE /*** End of inlined file: juce_ApplicationProperties.cpp ***/ /*** Start of inlined file: juce_PropertiesFile.cpp ***/ BEGIN_JUCE_NAMESPACE namespace PropertyFileConstants { static const int magicNumber = (int) ByteOrder::littleEndianInt ("PROP"); static const int magicNumberCompressed = (int) ByteOrder::littleEndianInt ("CPRP"); static const char* const fileTag = "PROPERTIES"; static const char* const valueTag = "VALUE"; static const char* const nameAttribute = "name"; static const char* const valueAttribute = "val"; } PropertiesFile::PropertiesFile (const File& f, const int millisecondsBeforeSaving, const int options_, InterProcessLock* const processLock_) : PropertySet (ignoreCaseOfKeyNames), file (f), timerInterval (millisecondsBeforeSaving), options (options_), loadedOk (false), needsWriting (false), processLock (processLock_) { // You need to correctly specify just one storage format for the file jassert ((options_ & (storeAsBinary | storeAsCompressedBinary | storeAsXML)) == storeAsBinary || (options_ & (storeAsBinary | storeAsCompressedBinary | storeAsXML)) == storeAsCompressedBinary || (options_ & (storeAsBinary | storeAsCompressedBinary | storeAsXML)) == storeAsXML); ProcessScopedLock pl (createProcessLock()); if (pl != 0 && ! pl->isLocked()) return; // locking failure.. ScopedPointer fileStream (f.createInputStream()); if (fileStream != 0) { int magicNumber = fileStream->readInt(); if (magicNumber == PropertyFileConstants::magicNumberCompressed) { fileStream = new GZIPDecompressorInputStream (new SubregionStream (fileStream.release(), 4, -1, true), true); magicNumber = PropertyFileConstants::magicNumber; } if (magicNumber == PropertyFileConstants::magicNumber) { loadedOk = true; BufferedInputStream in (fileStream.release(), 2048, true); int numValues = in.readInt(); while (--numValues >= 0 && ! in.isExhausted()) { const String key (in.readString()); const String value (in.readString()); jassert (key.isNotEmpty()); if (key.isNotEmpty()) getAllProperties().set (key, value); } } else { // Not a binary props file - let's see if it's XML.. fileStream = 0; XmlDocument parser (f); ScopedPointer doc (parser.getDocumentElement (true)); if (doc != 0 && doc->hasTagName (PropertyFileConstants::fileTag)) { doc = parser.getDocumentElement(); if (doc != 0) { loadedOk = true; forEachXmlChildElementWithTagName (*doc, e, PropertyFileConstants::valueTag) { const String name (e->getStringAttribute (PropertyFileConstants::nameAttribute)); if (name.isNotEmpty()) { getAllProperties().set (name, e->getFirstChildElement() != 0 ? e->getFirstChildElement()->createDocument (String::empty, true) : e->getStringAttribute (PropertyFileConstants::valueAttribute)); } } } else { // must be a pretty broken XML file we're trying to parse here, // or a sign that this object needs an InterProcessLock, // or just a failure reading the file. This last reason is why // we don't jassertfalse here. } } } } else { loadedOk = ! f.exists(); } } PropertiesFile::~PropertiesFile() { if (! saveIfNeeded()) jassertfalse; } InterProcessLock::ScopedLockType* PropertiesFile::createProcessLock() const { return processLock != 0 ? new InterProcessLock::ScopedLockType (*processLock) : 0; } bool PropertiesFile::saveIfNeeded() { const ScopedLock sl (getLock()); return (! needsWriting) || save(); } bool PropertiesFile::needsToBeSaved() const { const ScopedLock sl (getLock()); return needsWriting; } void PropertiesFile::setNeedsToBeSaved (const bool needsToBeSaved_) { const ScopedLock sl (getLock()); needsWriting = needsToBeSaved_; } bool PropertiesFile::save() { const ScopedLock sl (getLock()); stopTimer(); if (file == File::nonexistent || file.isDirectory() || ! file.getParentDirectory().createDirectory()) return false; if ((options & storeAsXML) != 0) { XmlElement doc (PropertyFileConstants::fileTag); for (int i = 0; i < getAllProperties().size(); ++i) { XmlElement* const e = doc.createNewChildElement (PropertyFileConstants::valueTag); e->setAttribute (PropertyFileConstants::nameAttribute, getAllProperties().getAllKeys() [i]); // if the value seems to contain xml, store it as such.. XmlDocument xmlContent (getAllProperties().getAllValues() [i]); XmlElement* const childElement = xmlContent.getDocumentElement(); if (childElement != 0) e->addChildElement (childElement); else e->setAttribute (PropertyFileConstants::valueAttribute, getAllProperties().getAllValues() [i]); } ProcessScopedLock pl (createProcessLock()); if (pl != 0 && ! pl->isLocked()) return false; // locking failure.. if (doc.writeToFile (file, String::empty)) { needsWriting = false; return true; } } else { ProcessScopedLock pl (createProcessLock()); if (pl != 0 && ! pl->isLocked()) return false; // locking failure.. TemporaryFile tempFile (file); ScopedPointer out (tempFile.getFile().createOutputStream()); if (out != 0) { if ((options & storeAsCompressedBinary) != 0) { out->writeInt (PropertyFileConstants::magicNumberCompressed); out->flush(); out = new GZIPCompressorOutputStream (out.release(), 9, true); } else { // have you set up the storage option flags correctly? jassert ((options & storeAsBinary) != 0); out->writeInt (PropertyFileConstants::magicNumber); } const int numProperties = getAllProperties().size(); out->writeInt (numProperties); for (int i = 0; i < numProperties; ++i) { out->writeString (getAllProperties().getAllKeys() [i]); out->writeString (getAllProperties().getAllValues() [i]); } out = 0; if (tempFile.overwriteTargetFileWithTemporary()) { needsWriting = false; return true; } } } return false; } void PropertiesFile::timerCallback() { saveIfNeeded(); } void PropertiesFile::propertyChanged() { sendChangeMessage (this); needsWriting = true; if (timerInterval > 0) startTimer (timerInterval); else if (timerInterval == 0) saveIfNeeded(); } const File PropertiesFile::getDefaultAppSettingsFile (const String& applicationName, const String& fileNameSuffix, const String& folderName, const bool commonToAllUsers) { // mustn't have illegal characters in this name.. jassert (applicationName == File::createLegalFileName (applicationName)); #if JUCE_MAC || JUCE_IOS File dir (commonToAllUsers ? "/Library/Preferences" : "~/Library/Preferences"); if (folderName.isNotEmpty()) dir = dir.getChildFile (folderName); #endif #ifdef JUCE_LINUX const File dir ((commonToAllUsers ? "/var/" : "~/") + (folderName.isNotEmpty() ? folderName : ("." + applicationName))); #endif #if JUCE_WINDOWS File dir (File::getSpecialLocation (commonToAllUsers ? File::commonApplicationDataDirectory : File::userApplicationDataDirectory)); if (dir == File::nonexistent) return File::nonexistent; dir = dir.getChildFile (folderName.isNotEmpty() ? folderName : applicationName); #endif return dir.getChildFile (applicationName) .withFileExtension (fileNameSuffix); } PropertiesFile* PropertiesFile::createDefaultAppPropertiesFile (const String& applicationName, const String& fileNameSuffix, const String& folderName, const bool commonToAllUsers, const int millisecondsBeforeSaving, const int propertiesFileOptions, InterProcessLock* processLock_) { const File file (getDefaultAppSettingsFile (applicationName, fileNameSuffix, folderName, commonToAllUsers)); jassert (file != File::nonexistent); if (file == File::nonexistent) return 0; return new PropertiesFile (file, millisecondsBeforeSaving, propertiesFileOptions,processLock_); } END_JUCE_NAMESPACE /*** End of inlined file: juce_PropertiesFile.cpp ***/ /*** Start of inlined file: juce_FileBasedDocument.cpp ***/ BEGIN_JUCE_NAMESPACE FileBasedDocument::FileBasedDocument (const String& fileExtension_, const String& fileWildcard_, const String& openFileDialogTitle_, const String& saveFileDialogTitle_) : changedSinceSave (false), fileExtension (fileExtension_), fileWildcard (fileWildcard_), openFileDialogTitle (openFileDialogTitle_), saveFileDialogTitle (saveFileDialogTitle_) { } FileBasedDocument::~FileBasedDocument() { } void FileBasedDocument::setChangedFlag (const bool hasChanged) { if (changedSinceSave != hasChanged) { changedSinceSave = hasChanged; sendChangeMessage (this); } } void FileBasedDocument::changed() { changedSinceSave = true; sendChangeMessage (this); } void FileBasedDocument::setFile (const File& newFile) { if (documentFile != newFile) { documentFile = newFile; changed(); } } bool FileBasedDocument::loadFrom (const File& newFile, const bool showMessageOnFailure) { MouseCursor::showWaitCursor(); const File oldFile (documentFile); documentFile = newFile; String error; if (newFile.existsAsFile()) { error = loadDocument (newFile); if (error.isEmpty()) { setChangedFlag (false); MouseCursor::hideWaitCursor(); setLastDocumentOpened (newFile); return true; } } else { error = "The file doesn't exist"; } documentFile = oldFile; MouseCursor::hideWaitCursor(); if (showMessageOnFailure) { AlertWindow::showMessageBox (AlertWindow::WarningIcon, TRANS("Failed to open file..."), TRANS("There was an error while trying to load the file:\n\n") + newFile.getFullPathName() + "\n\n" + error); } return false; } bool FileBasedDocument::loadFromUserSpecifiedFile (const bool showMessageOnFailure) { FileChooser fc (openFileDialogTitle, getLastDocumentOpened(), fileWildcard); if (fc.browseForFileToOpen()) return loadFrom (fc.getResult(), showMessageOnFailure); return false; } FileBasedDocument::SaveResult FileBasedDocument::save (const bool askUserForFileIfNotSpecified, const bool showMessageOnFailure) { return saveAs (documentFile, false, askUserForFileIfNotSpecified, showMessageOnFailure); } FileBasedDocument::SaveResult FileBasedDocument::saveAs (const File& newFile, const bool warnAboutOverwritingExistingFiles, const bool askUserForFileIfNotSpecified, const bool showMessageOnFailure) { if (newFile == File::nonexistent) { if (askUserForFileIfNotSpecified) { return saveAsInteractive (true); } else { // can't save to an unspecified file jassertfalse; return failedToWriteToFile; } } if (warnAboutOverwritingExistingFiles && newFile.exists()) { if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, TRANS("File already exists"), TRANS("There's already a file called:\n\n") + newFile.getFullPathName() + TRANS("\n\nAre you sure you want to overwrite it?"), TRANS("overwrite"), TRANS("cancel"))) { return userCancelledSave; } } MouseCursor::showWaitCursor(); const File oldFile (documentFile); documentFile = newFile; String error (saveDocument (newFile)); if (error.isEmpty()) { setChangedFlag (false); MouseCursor::hideWaitCursor(); return savedOk; } documentFile = oldFile; MouseCursor::hideWaitCursor(); if (showMessageOnFailure) { AlertWindow::showMessageBox (AlertWindow::WarningIcon, TRANS("Error writing to file..."), TRANS("An error occurred while trying to save \"") + getDocumentTitle() + TRANS("\" to the file:\n\n") + newFile.getFullPathName() + "\n\n" + error); } return failedToWriteToFile; } FileBasedDocument::SaveResult FileBasedDocument::saveIfNeededAndUserAgrees() { if (! hasChangedSinceSaved()) return savedOk; const int r = AlertWindow::showYesNoCancelBox (AlertWindow::QuestionIcon, TRANS("Closing document..."), TRANS("Do you want to save the changes to \"") + getDocumentTitle() + "\"?", TRANS("save"), TRANS("discard changes"), TRANS("cancel")); if (r == 1) { // save changes return save (true, true); } else if (r == 2) { // discard changes return savedOk; } return userCancelledSave; } FileBasedDocument::SaveResult FileBasedDocument::saveAsInteractive (const bool warnAboutOverwritingExistingFiles) { File f; if (documentFile.existsAsFile()) f = documentFile; else f = getLastDocumentOpened(); String legalFilename (File::createLegalFileName (getDocumentTitle())); if (legalFilename.isEmpty()) legalFilename = "unnamed"; if (f.existsAsFile() || f.getParentDirectory().isDirectory()) f = f.getSiblingFile (legalFilename); else f = File::getSpecialLocation (File::userDocumentsDirectory).getChildFile (legalFilename); f = f.withFileExtension (fileExtension) .getNonexistentSibling (true); FileChooser fc (saveFileDialogTitle, f, fileWildcard); if (fc.browseForFileToSave (warnAboutOverwritingExistingFiles)) { File chosen (fc.getResult()); if (chosen.getFileExtension().isEmpty()) { chosen = chosen.withFileExtension (fileExtension); if (chosen.exists()) { if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, TRANS("File already exists"), TRANS("There's already a file called:") + "\n\n" + chosen.getFullPathName() + "\n\n" + TRANS("Are you sure you want to overwrite it?"), TRANS("overwrite"), TRANS("cancel"))) { return userCancelledSave; } } } setLastDocumentOpened (chosen); return saveAs (chosen, false, false, true); } return userCancelledSave; } END_JUCE_NAMESPACE /*** End of inlined file: juce_FileBasedDocument.cpp ***/ /*** Start of inlined file: juce_RecentlyOpenedFilesList.cpp ***/ BEGIN_JUCE_NAMESPACE RecentlyOpenedFilesList::RecentlyOpenedFilesList() : maxNumberOfItems (10) { } RecentlyOpenedFilesList::~RecentlyOpenedFilesList() { } void RecentlyOpenedFilesList::setMaxNumberOfItems (const int newMaxNumber) { maxNumberOfItems = jmax (1, newMaxNumber); while (getNumFiles() > maxNumberOfItems) files.remove (getNumFiles() - 1); } int RecentlyOpenedFilesList::getNumFiles() const { return files.size(); } const File RecentlyOpenedFilesList::getFile (const int index) const { return File (files [index]); } void RecentlyOpenedFilesList::clear() { files.clear(); } void RecentlyOpenedFilesList::addFile (const File& file) { const String path (file.getFullPathName()); files.removeString (path, true); files.insert (0, path); setMaxNumberOfItems (maxNumberOfItems); } void RecentlyOpenedFilesList::removeNonExistentFiles() { for (int i = getNumFiles(); --i >= 0;) if (! getFile(i).exists()) files.remove (i); } int RecentlyOpenedFilesList::createPopupMenuItems (PopupMenu& menuToAddTo, const int baseItemId, const bool showFullPaths, const bool dontAddNonExistentFiles, const File** filesToAvoid) { int num = 0; for (int i = 0; i < getNumFiles(); ++i) { const File f (getFile(i)); if ((! dontAddNonExistentFiles) || f.exists()) { bool needsAvoiding = false; if (filesToAvoid != 0) { const File** avoid = filesToAvoid; while (*avoid != 0) { if (f == **avoid) { needsAvoiding = true; break; } ++avoid; } } if (! needsAvoiding) { menuToAddTo.addItem (baseItemId + i, showFullPaths ? f.getFullPathName() : f.getFileName()); ++num; } } } return num; } const String RecentlyOpenedFilesList::toString() const { return files.joinIntoString ("\n"); } void RecentlyOpenedFilesList::restoreFromString (const String& stringifiedVersion) { clear(); files.addLines (stringifiedVersion); setMaxNumberOfItems (maxNumberOfItems); } END_JUCE_NAMESPACE /*** End of inlined file: juce_RecentlyOpenedFilesList.cpp ***/ /*** Start of inlined file: juce_UndoManager.cpp ***/ BEGIN_JUCE_NAMESPACE UndoManager::UndoManager (const int maxNumberOfUnitsToKeep, const int minimumTransactions) : totalUnitsStored (0), nextIndex (0), newTransaction (true), reentrancyCheck (false) { setMaxNumberOfStoredUnits (maxNumberOfUnitsToKeep, minimumTransactions); } UndoManager::~UndoManager() { clearUndoHistory(); } void UndoManager::clearUndoHistory() { transactions.clear(); transactionNames.clear(); totalUnitsStored = 0; nextIndex = 0; sendChangeMessage (this); } int UndoManager::getNumberOfUnitsTakenUpByStoredCommands() const { return totalUnitsStored; } void UndoManager::setMaxNumberOfStoredUnits (const int maxNumberOfUnitsToKeep, const int minimumTransactions) { maxNumUnitsToKeep = jmax (1, maxNumberOfUnitsToKeep); minimumTransactionsToKeep = jmax (1, minimumTransactions); } bool UndoManager::perform (UndoableAction* const command_, const String& actionName) { if (command_ != 0) { ScopedPointer command (command_); if (actionName.isNotEmpty()) currentTransactionName = actionName; if (reentrancyCheck) { jassertfalse; // don't call perform() recursively from the UndoableAction::perform() or // undo() methods, or else these actions won't actually get done. return false; } else if (command->perform()) { OwnedArray* commandSet = transactions [nextIndex - 1]; if (commandSet != 0 && ! newTransaction) { UndoableAction* lastAction = commandSet->getLast(); if (lastAction != 0) { UndoableAction* coalescedAction = lastAction->createCoalescedAction (command); if (coalescedAction != 0) { command = coalescedAction; totalUnitsStored -= lastAction->getSizeInUnits(); commandSet->removeLast(); } } } else { commandSet = new OwnedArray(); transactions.insert (nextIndex, commandSet); transactionNames.insert (nextIndex, currentTransactionName); ++nextIndex; } totalUnitsStored += command->getSizeInUnits(); commandSet->add (command.release()); newTransaction = false; while (nextIndex < transactions.size()) { const OwnedArray * const lastSet = transactions.getLast(); for (int i = lastSet->size(); --i >= 0;) totalUnitsStored -= lastSet->getUnchecked (i)->getSizeInUnits(); transactions.removeLast(); transactionNames.remove (transactionNames.size() - 1); } while (nextIndex > 0 && totalUnitsStored > maxNumUnitsToKeep && transactions.size() > minimumTransactionsToKeep) { const OwnedArray * const firstSet = transactions.getFirst(); for (int i = firstSet->size(); --i >= 0;) totalUnitsStored -= firstSet->getUnchecked (i)->getSizeInUnits(); jassert (totalUnitsStored >= 0); // something fishy going on if this fails! transactions.remove (0); transactionNames.remove (0); --nextIndex; } sendChangeMessage (this); return true; } } return false; } void UndoManager::beginNewTransaction (const String& actionName) { newTransaction = true; currentTransactionName = actionName; } void UndoManager::setCurrentTransactionName (const String& newName) { currentTransactionName = newName; } bool UndoManager::canUndo() const { return nextIndex > 0; } bool UndoManager::canRedo() const { return nextIndex < transactions.size(); } const String UndoManager::getUndoDescription() const { return transactionNames [nextIndex - 1]; } const String UndoManager::getRedoDescription() const { return transactionNames [nextIndex]; } bool UndoManager::undo() { const OwnedArray* const commandSet = transactions [nextIndex - 1]; if (commandSet == 0) return false; reentrancyCheck = true; bool failed = false; for (int i = commandSet->size(); --i >= 0;) { if (! commandSet->getUnchecked(i)->undo()) { jassertfalse; failed = true; break; } } reentrancyCheck = false; if (failed) clearUndoHistory(); else --nextIndex; beginNewTransaction(); sendChangeMessage (this); return true; } bool UndoManager::redo() { const OwnedArray* const commandSet = transactions [nextIndex]; if (commandSet == 0) return false; reentrancyCheck = true; bool failed = false; for (int i = 0; i < commandSet->size(); ++i) { if (! commandSet->getUnchecked(i)->perform()) { jassertfalse; failed = true; break; } } reentrancyCheck = false; if (failed) clearUndoHistory(); else ++nextIndex; beginNewTransaction(); sendChangeMessage (this); return true; } bool UndoManager::undoCurrentTransactionOnly() { return newTransaction ? false : undo(); } void UndoManager::getActionsInCurrentTransaction (Array & actionsFound) const { const OwnedArray * const commandSet = transactions [nextIndex - 1]; if (commandSet != 0 && ! newTransaction) { for (int i = 0; i < commandSet->size(); ++i) actionsFound.add (commandSet->getUnchecked(i)); } } int UndoManager::getNumActionsInCurrentTransaction() const { const OwnedArray * const commandSet = transactions [nextIndex - 1]; if (commandSet != 0 && ! newTransaction) return commandSet->size(); return 0; } END_JUCE_NAMESPACE /*** End of inlined file: juce_UndoManager.cpp ***/ /*** Start of inlined file: juce_AiffAudioFormat.cpp ***/ BEGIN_JUCE_NAMESPACE static const char* const aiffFormatName = "AIFF file"; static const char* const aiffExtensions[] = { ".aiff", ".aif", 0 }; class AiffAudioFormatReader : public AudioFormatReader { public: int bytesPerFrame; int64 dataChunkStart; bool littleEndian; AiffAudioFormatReader (InputStream* in) : AudioFormatReader (in, TRANS (aiffFormatName)) { if (input->readInt() == chunkName ("FORM")) { const int len = input->readIntBigEndian(); const int64 end = input->getPosition() + len; const int nextType = input->readInt(); if (nextType == chunkName ("AIFF") || nextType == chunkName ("AIFC")) { bool hasGotVer = false; bool hasGotData = false; bool hasGotType = false; while (input->getPosition() < end) { const int type = input->readInt(); const uint32 length = (uint32) input->readIntBigEndian(); const int64 chunkEnd = input->getPosition() + length; if (type == chunkName ("FVER")) { hasGotVer = true; const int ver = input->readIntBigEndian(); if (ver != 0 && ver != (int)0xa2805140) break; } else if (type == chunkName ("COMM")) { hasGotType = true; numChannels = (unsigned int)input->readShortBigEndian(); lengthInSamples = input->readIntBigEndian(); bitsPerSample = input->readShortBigEndian(); bytesPerFrame = (numChannels * bitsPerSample) >> 3; unsigned char sampleRateBytes[10]; input->read (sampleRateBytes, 10); const int byte0 = sampleRateBytes[0]; if ((byte0 & 0x80) != 0 || byte0 <= 0x3F || byte0 > 0x40 || (byte0 == 0x40 && sampleRateBytes[1] > 0x1C)) break; unsigned int sampRate = ByteOrder::bigEndianInt (sampleRateBytes + 2); sampRate >>= (16414 - ByteOrder::bigEndianShort (sampleRateBytes)); sampleRate = (int) sampRate; if (length <= 18) { // some types don't have a chunk large enough to include a compression // type, so assume it's just big-endian pcm littleEndian = false; } else { const int compType = input->readInt(); if (compType == chunkName ("NONE") || compType == chunkName ("twos")) { littleEndian = false; } else if (compType == chunkName ("sowt")) { littleEndian = true; } else { sampleRate = 0; break; } } } else if (type == chunkName ("SSND")) { hasGotData = true; const int offset = input->readIntBigEndian(); dataChunkStart = input->getPosition() + 4 + offset; lengthInSamples = (bytesPerFrame > 0) ? jmin (lengthInSamples, (int64) (length / bytesPerFrame)) : 0; } else if ((hasGotVer && hasGotData && hasGotType) || chunkEnd < input->getPosition() || input->isExhausted()) { break; } input->setPosition (chunkEnd); } } } } ~AiffAudioFormatReader() { } bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) { const int64 samplesAvailable = lengthInSamples - startSampleInFile; if (samplesAvailable < numSamples) { for (int i = numDestChannels; --i >= 0;) if (destSamples[i] != 0) zeromem (destSamples[i] + startOffsetInDestBuffer, sizeof (int) * numSamples); numSamples = (int) samplesAvailable; } if (numSamples <= 0) return true; input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame); const int tempBufSize = 480 * 3 * 4; // (keep this a multiple of 3) char tempBuffer [tempBufSize]; while (numSamples > 0) { int* left = destSamples[0]; if (left != 0) left += startOffsetInDestBuffer; int* right = numDestChannels > 1 ? destSamples[1] : 0; if (right != 0) right += startOffsetInDestBuffer; const int numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples); const int bytesRead = input->read (tempBuffer, numThisTime * bytesPerFrame); if (bytesRead < numThisTime * bytesPerFrame) zeromem (tempBuffer + bytesRead, numThisTime * bytesPerFrame - bytesRead); if (bitsPerSample == 16) { if (littleEndian) { const short* src = reinterpret_cast (tempBuffer); if (numChannels > 1) { if (left == 0) { for (int i = numThisTime; --i >= 0;) { *right++ = (int) ByteOrder::swapIfBigEndian ((unsigned short) *src++) << 16; ++src; } } else if (right == 0) { for (int i = numThisTime; --i >= 0;) { ++src; *left++ = (int) ByteOrder::swapIfBigEndian ((unsigned short) *src++) << 16; } } else { for (int i = numThisTime; --i >= 0;) { *left++ = (int) ByteOrder::swapIfBigEndian ((unsigned short) *src++) << 16; *right++ = (int) ByteOrder::swapIfBigEndian ((unsigned short) *src++) << 16; } } } else { for (int i = numThisTime; --i >= 0;) { *left++ = (int) ByteOrder::swapIfBigEndian ((unsigned short) *src++) << 16; } } } else { const char* src = tempBuffer; if (numChannels > 1) { if (left == 0) { for (int i = numThisTime; --i >= 0;) { *right++ = ByteOrder::bigEndianShort (src) << 16; src += 4; } } else if (right == 0) { for (int i = numThisTime; --i >= 0;) { src += 2; *left++ = ByteOrder::bigEndianShort (src) << 16; src += 2; } } else { for (int i = numThisTime; --i >= 0;) { *left++ = ByteOrder::bigEndianShort (src) << 16; src += 2; *right++ = ByteOrder::bigEndianShort (src) << 16; src += 2; } } } else { for (int i = numThisTime; --i >= 0;) { *left++ = ByteOrder::bigEndianShort (src) << 16; src += 2; } } } } else if (bitsPerSample == 24) { const char* src = tempBuffer; if (littleEndian) { if (numChannels > 1) { if (left == 0) { for (int i = numThisTime; --i >= 0;) { *right++ = ByteOrder::littleEndian24Bit (src) << 8; src += 6; } } else if (right == 0) { for (int i = numThisTime; --i >= 0;) { src += 3; *left++ = ByteOrder::littleEndian24Bit (src) << 8; src += 3; } } else { for (int i = numThisTime; --i >= 0;) { *left++ = ByteOrder::littleEndian24Bit (src) << 8; src += 3; *right++ = ByteOrder::littleEndian24Bit (src) << 8; src += 3; } } } else { for (int i = numThisTime; --i >= 0;) { *left++ = ByteOrder::littleEndian24Bit (src) << 8; src += 3; } } } else { if (numChannels > 1) { if (left == 0) { for (int i = numThisTime; --i >= 0;) { *right++ = ByteOrder::bigEndian24Bit (src) << 8; src += 6; } } else if (right == 0) { for (int i = numThisTime; --i >= 0;) { src += 3; *left++ = ByteOrder::bigEndian24Bit (src) << 8; src += 3; } } else { for (int i = numThisTime; --i >= 0;) { *left++ = ByteOrder::bigEndian24Bit (src) << 8; src += 3; *right++ = ByteOrder::bigEndian24Bit (src) << 8; src += 3; } } } else { for (int i = numThisTime; --i >= 0;) { *left++ = ByteOrder::bigEndian24Bit (src) << 8; src += 3; } } } } else if (bitsPerSample == 32) { const unsigned int* src = reinterpret_cast (tempBuffer); unsigned int* l = reinterpret_cast (left); unsigned int* r = reinterpret_cast (right); if (littleEndian) { if (numChannels > 1) { if (l == 0) { for (int i = numThisTime; --i >= 0;) { ++src; *r++ = ByteOrder::swapIfBigEndian (*src++); } } else if (r == 0) { for (int i = numThisTime; --i >= 0;) { *l++ = ByteOrder::swapIfBigEndian (*src++); ++src; } } else { for (int i = numThisTime; --i >= 0;) { *l++ = ByteOrder::swapIfBigEndian (*src++); *r++ = ByteOrder::swapIfBigEndian (*src++); } } } else { for (int i = numThisTime; --i >= 0;) { *l++ = ByteOrder::swapIfBigEndian (*src++); } } } else { if (numChannels > 1) { if (l == 0) { for (int i = numThisTime; --i >= 0;) { ++src; *r++ = ByteOrder::swapIfLittleEndian (*src++); } } else if (r == 0) { for (int i = numThisTime; --i >= 0;) { *l++ = ByteOrder::swapIfLittleEndian (*src++); ++src; } } else { for (int i = numThisTime; --i >= 0;) { *l++ = ByteOrder::swapIfLittleEndian (*src++); *r++ = ByteOrder::swapIfLittleEndian (*src++); } } } else { for (int i = numThisTime; --i >= 0;) { *l++ = ByteOrder::swapIfLittleEndian (*src++); } } } left = reinterpret_cast (l); right = reinterpret_cast (r); } else if (bitsPerSample == 8) { const char* src = tempBuffer; if (numChannels > 1) { if (left == 0) { for (int i = numThisTime; --i >= 0;) { *right++ = ((int) *src++) << 24; ++src; } } else if (right == 0) { for (int i = numThisTime; --i >= 0;) { ++src; *left++ = ((int) *src++) << 24; } } else { for (int i = numThisTime; --i >= 0;) { *left++ = ((int) *src++) << 24; *right++ = ((int) *src++) << 24; } } } else { for (int i = numThisTime; --i >= 0;) { *left++ = ((int) *src++) << 24; } } } startOffsetInDestBuffer += numThisTime; numSamples -= numThisTime; } if (numSamples > 0) { for (int i = numDestChannels; --i >= 0;) if (destSamples[i] != 0) zeromem (destSamples[i] + startOffsetInDestBuffer, sizeof (int) * numSamples); } return true; } juce_UseDebuggingNewOperator private: AiffAudioFormatReader (const AiffAudioFormatReader&); AiffAudioFormatReader& operator= (const AiffAudioFormatReader&); static inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); } }; class AiffAudioFormatWriter : public AudioFormatWriter { MemoryBlock tempBlock; uint32 lengthInSamples, bytesWritten; int64 headerPosition; bool writeFailed; static inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); } AiffAudioFormatWriter (const AiffAudioFormatWriter&); AiffAudioFormatWriter& operator= (const AiffAudioFormatWriter&); void writeHeader() { const bool couldSeekOk = output->setPosition (headerPosition); (void) couldSeekOk; // if this fails, you've given it an output stream that can't seek! It needs // to be able to seek back to write the header jassert (couldSeekOk); const int headerLen = 54; int audioBytes = lengthInSamples * ((bitsPerSample * numChannels) / 8); audioBytes += (audioBytes & 1); output->writeInt (chunkName ("FORM")); output->writeIntBigEndian (headerLen + audioBytes - 8); output->writeInt (chunkName ("AIFF")); output->writeInt (chunkName ("COMM")); output->writeIntBigEndian (18); output->writeShortBigEndian ((short) numChannels); output->writeIntBigEndian (lengthInSamples); output->writeShortBigEndian ((short) bitsPerSample); uint8 sampleRateBytes[10]; zeromem (sampleRateBytes, 10); if (sampleRate <= 1) { sampleRateBytes[0] = 0x3f; sampleRateBytes[1] = 0xff; sampleRateBytes[2] = 0x80; } else { int mask = 0x40000000; sampleRateBytes[0] = 0x40; if (sampleRate >= mask) { jassertfalse; sampleRateBytes[1] = 0x1d; } else { int n = (int) sampleRate; int i; for (i = 0; i <= 32 ; ++i) { if ((n & mask) != 0) break; mask >>= 1; } n = n << (i + 1); sampleRateBytes[1] = (uint8) (29 - i); sampleRateBytes[2] = (uint8) ((n >> 24) & 0xff); sampleRateBytes[3] = (uint8) ((n >> 16) & 0xff); sampleRateBytes[4] = (uint8) ((n >> 8) & 0xff); sampleRateBytes[5] = (uint8) (n & 0xff); } } output->write (sampleRateBytes, 10); output->writeInt (chunkName ("SSND")); output->writeIntBigEndian (audioBytes + 8); output->writeInt (0); output->writeInt (0); jassert (output->getPosition() == headerLen); } public: AiffAudioFormatWriter (OutputStream* out, const double sampleRate_, const unsigned int chans, const int bits) : AudioFormatWriter (out, TRANS (aiffFormatName), sampleRate_, chans, bits), lengthInSamples (0), bytesWritten (0), writeFailed (false) { headerPosition = out->getPosition(); writeHeader(); } ~AiffAudioFormatWriter() { if ((bytesWritten & 1) != 0) output->writeByte (0); writeHeader(); } bool write (const int** data, int numSamples) { if (writeFailed) return false; const int bytes = numChannels * numSamples * bitsPerSample / 8; tempBlock.ensureSize (bytes, false); char* buffer = static_cast (tempBlock.getData()); const int* left = data[0]; const int* right = data[1]; if (right == 0) right = left; if (bitsPerSample == 16) { short* b = reinterpret_cast (buffer); if (numChannels > 1) { for (int i = numSamples; --i >= 0;) { *b++ = (short) ByteOrder::swapIfLittleEndian ((uint16) (*left++ >> 16)); *b++ = (short) ByteOrder::swapIfLittleEndian ((uint16) (*right++ >> 16)); } } else { for (int i = numSamples; --i >= 0;) { *b++ = (short) ByteOrder::swapIfLittleEndian ((uint16) (*left++ >> 16)); } } } else if (bitsPerSample == 24) { char* b = buffer; if (numChannels > 1) { for (int i = numSamples; --i >= 0;) { ByteOrder::bigEndian24BitToChars (*left++ >> 8, b); b += 3; ByteOrder::bigEndian24BitToChars (*right++ >> 8, b); b += 3; } } else { for (int i = numSamples; --i >= 0;) { ByteOrder::bigEndian24BitToChars (*left++ >> 8, b); b += 3; } } } else if (bitsPerSample == 32) { uint32* b = reinterpret_cast (buffer); if (numChannels > 1) { for (int i = numSamples; --i >= 0;) { *b++ = ByteOrder::swapIfLittleEndian ((uint32) *left++); *b++ = ByteOrder::swapIfLittleEndian ((uint32) *right++); } } else { for (int i = numSamples; --i >= 0;) { *b++ = ByteOrder::swapIfLittleEndian ((uint32) *left++); } } } else if (bitsPerSample == 8) { char* b = buffer; if (numChannels > 1) { for (int i = numSamples; --i >= 0;) { *b++ = (char) (*left++ >> 24); *b++ = (char) (*right++ >> 24); } } else { for (int i = numSamples; --i >= 0;) { *b++ = (char) (*left++ >> 24); } } } if (bytesWritten + bytes >= (uint32) 0xfff00000 || ! output->write (buffer, bytes)) { // failed to write to disk, so let's try writing the header. // If it's just run out of disk space, then if it does manage // to write the header, we'll still have a useable file.. writeHeader(); writeFailed = true; return false; } else { bytesWritten += bytes; lengthInSamples += numSamples; return true; } } juce_UseDebuggingNewOperator }; AiffAudioFormat::AiffAudioFormat() : AudioFormat (TRANS (aiffFormatName), StringArray (aiffExtensions)) { } AiffAudioFormat::~AiffAudioFormat() { } const Array AiffAudioFormat::getPossibleSampleRates() { const int rates[] = { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000, 0 }; return Array (rates); } const Array AiffAudioFormat::getPossibleBitDepths() { const int depths[] = { 8, 16, 24, 0 }; return Array (depths); } bool AiffAudioFormat::canDoStereo() { return true; } bool AiffAudioFormat::canDoMono() { return true; } #if JUCE_MAC bool AiffAudioFormat::canHandleFile (const File& f) { if (AudioFormat::canHandleFile (f)) return true; const OSType type = PlatformUtilities::getTypeOfFile (f.getFullPathName()); return type == 'AIFF' || type == 'AIFC' || type == 'aiff' || type == 'aifc'; } #endif AudioFormatReader* AiffAudioFormat::createReaderFor (InputStream* sourceStream, const bool deleteStreamIfOpeningFails) { ScopedPointer w (new AiffAudioFormatReader (sourceStream)); if (w->sampleRate != 0) return w.release(); if (! deleteStreamIfOpeningFails) w->input = 0; return 0; } AudioFormatWriter* AiffAudioFormat::createWriterFor (OutputStream* out, double sampleRate, unsigned int chans, int bitsPerSample, const StringPairArray& /*metadataValues*/, int /*qualityOptionIndex*/) { if (getPossibleBitDepths().contains (bitsPerSample)) { return new AiffAudioFormatWriter (out, sampleRate, chans, bitsPerSample); } return 0; } END_JUCE_NAMESPACE /*** End of inlined file: juce_AiffAudioFormat.cpp ***/ /*** Start of inlined file: juce_AudioFormat.cpp ***/ BEGIN_JUCE_NAMESPACE AudioFormatReader::AudioFormatReader (InputStream* const in, const String& formatName_) : sampleRate (0), bitsPerSample (0), lengthInSamples (0), numChannels (0), usesFloatingPointData (false), input (in), formatName (formatName_) { } AudioFormatReader::~AudioFormatReader() { delete input; } bool AudioFormatReader::read (int* const* destSamples, int numDestChannels, int64 startSampleInSource, int numSamplesToRead, const bool fillLeftoverChannelsWithCopies) { jassert (numDestChannels > 0); // you have to actually give this some channels to work with! int startOffsetInDestBuffer = 0; if (startSampleInSource < 0) { const int silence = (int) jmin (-startSampleInSource, (int64) numSamplesToRead); for (int i = numDestChannels; --i >= 0;) if (destSamples[i] != 0) zeromem (destSamples[i], sizeof (int) * silence); startOffsetInDestBuffer += silence; numSamplesToRead -= silence; startSampleInSource = 0; } if (numSamplesToRead <= 0) return true; if (! readSamples (const_cast (destSamples), jmin ((int) numChannels, numDestChannels), startOffsetInDestBuffer, startSampleInSource, numSamplesToRead)) return false; if (numDestChannels > (int) numChannels) { if (fillLeftoverChannelsWithCopies) { int* lastFullChannel = destSamples[0]; for (int i = (int) numChannels; --i > 0;) { if (destSamples[i] != 0) { lastFullChannel = destSamples[i]; break; } } if (lastFullChannel != 0) for (int i = numChannels; i < numDestChannels; ++i) if (destSamples[i] != 0) memcpy (destSamples[i], lastFullChannel, sizeof (int) * numSamplesToRead); } else { for (int i = numChannels; i < numDestChannels; ++i) if (destSamples[i] != 0) zeromem (destSamples[i], sizeof (int) * numSamplesToRead); } } return true; } static void findAudioBufferMaxMin (const float* const buffer, const int num, float& maxVal, float& minVal) throw() { float mn = buffer[0]; float mx = mn; for (int i = 1; i < num; ++i) { const float s = buffer[i]; if (s > mx) mx = s; if (s < mn) mn = s; } maxVal = mx; minVal = mn; } void AudioFormatReader::readMaxLevels (int64 startSampleInFile, int64 numSamples, float& lowestLeft, float& highestLeft, float& lowestRight, float& highestRight) { if (numSamples <= 0) { lowestLeft = 0; lowestRight = 0; highestLeft = 0; highestRight = 0; return; } const int bufferSize = (int) jmin (numSamples, (int64) 4096); HeapBlock tempSpace (bufferSize * 2 + 64); int* tempBuffer[3]; tempBuffer[0] = tempSpace.getData(); tempBuffer[1] = tempSpace.getData() + bufferSize; tempBuffer[2] = 0; if (usesFloatingPointData) { float lmin = 1.0e6f; float lmax = -lmin; float rmin = lmin; float rmax = lmax; while (numSamples > 0) { const int numToDo = (int) jmin (numSamples, (int64) bufferSize); read (tempBuffer, 2, startSampleInFile, numToDo, false); numSamples -= numToDo; startSampleInFile += numToDo; float bufmin, bufmax; findAudioBufferMaxMin (reinterpret_cast (tempBuffer[0]), numToDo, bufmax, bufmin); lmin = jmin (lmin, bufmin); lmax = jmax (lmax, bufmax); if (numChannels > 1) { findAudioBufferMaxMin (reinterpret_cast (tempBuffer[1]), numToDo, bufmax, bufmin); rmin = jmin (rmin, bufmin); rmax = jmax (rmax, bufmax); } } if (numChannels <= 1) { rmax = lmax; rmin = lmin; } lowestLeft = lmin; highestLeft = lmax; lowestRight = rmin; highestRight = rmax; } else { int lmax = std::numeric_limits::min(); int lmin = std::numeric_limits::max(); int rmax = std::numeric_limits::min(); int rmin = std::numeric_limits::max(); while (numSamples > 0) { const int numToDo = (int) jmin (numSamples, (int64) bufferSize); read (tempBuffer, 2, startSampleInFile, numToDo, false); numSamples -= numToDo; startSampleInFile += numToDo; for (int j = numChannels; --j >= 0;) { int bufMax = std::numeric_limits::min(); int bufMin = std::numeric_limits::max(); const int* const b = tempBuffer[j]; for (int i = 0; i < numToDo; ++i) { const int samp = b[i]; if (samp < bufMin) bufMin = samp; if (samp > bufMax) bufMax = samp; } if (j == 0) { lmax = jmax (lmax, bufMax); lmin = jmin (lmin, bufMin); } else { rmax = jmax (rmax, bufMax); rmin = jmin (rmin, bufMin); } } } if (numChannels <= 1) { rmax = lmax; rmin = lmin; } lowestLeft = lmin / (float) std::numeric_limits::max(); highestLeft = lmax / (float) std::numeric_limits::max(); lowestRight = rmin / (float) std::numeric_limits::max(); highestRight = rmax / (float) std::numeric_limits::max(); } } int64 AudioFormatReader::searchForLevel (int64 startSample, int64 numSamplesToSearch, const double magnitudeRangeMinimum, const double magnitudeRangeMaximum, const int minimumConsecutiveSamples) { if (numSamplesToSearch == 0) return -1; const int bufferSize = 4096; HeapBlock tempSpace (bufferSize * 2 + 64); int* tempBuffer[3]; tempBuffer[0] = tempSpace.getData(); tempBuffer[1] = tempSpace.getData() + bufferSize; tempBuffer[2] = 0; int consecutive = 0; int64 firstMatchPos = -1; jassert (magnitudeRangeMaximum > magnitudeRangeMinimum); const double doubleMin = jlimit (0.0, (double) std::numeric_limits::max(), magnitudeRangeMinimum * std::numeric_limits::max()); const double doubleMax = jlimit (doubleMin, (double) std::numeric_limits::max(), magnitudeRangeMaximum * std::numeric_limits::max()); const int intMagnitudeRangeMinimum = roundToInt (doubleMin); const int intMagnitudeRangeMaximum = roundToInt (doubleMax); while (numSamplesToSearch != 0) { const int numThisTime = (int) jmin (abs64 (numSamplesToSearch), (int64) bufferSize); int64 bufferStart = startSample; if (numSamplesToSearch < 0) bufferStart -= numThisTime; if (bufferStart >= (int) lengthInSamples) break; read (tempBuffer, 2, bufferStart, numThisTime, false); int num = numThisTime; while (--num >= 0) { if (numSamplesToSearch < 0) --startSample; bool matches = false; const int index = (int) (startSample - bufferStart); if (usesFloatingPointData) { const float sample1 = std::abs (((float*) tempBuffer[0]) [index]); if (sample1 >= magnitudeRangeMinimum && sample1 <= magnitudeRangeMaximum) { matches = true; } else if (numChannels > 1) { const float sample2 = std::abs (((float*) tempBuffer[1]) [index]); matches = (sample2 >= magnitudeRangeMinimum && sample2 <= magnitudeRangeMaximum); } } else { const int sample1 = abs (tempBuffer[0] [index]); if (sample1 >= intMagnitudeRangeMinimum && sample1 <= intMagnitudeRangeMaximum) { matches = true; } else if (numChannels > 1) { const int sample2 = abs (tempBuffer[1][index]); matches = (sample2 >= intMagnitudeRangeMinimum && sample2 <= intMagnitudeRangeMaximum); } } if (matches) { if (firstMatchPos < 0) firstMatchPos = startSample; if (++consecutive >= minimumConsecutiveSamples) { if (firstMatchPos < 0 || firstMatchPos >= lengthInSamples) return -1; return firstMatchPos; } } else { consecutive = 0; firstMatchPos = -1; } if (numSamplesToSearch > 0) ++startSample; } if (numSamplesToSearch > 0) numSamplesToSearch -= numThisTime; else numSamplesToSearch += numThisTime; } return -1; } AudioFormatWriter::AudioFormatWriter (OutputStream* const out, const String& formatName_, const double rate, const unsigned int numChannels_, const unsigned int bitsPerSample_) : sampleRate (rate), numChannels (numChannels_), bitsPerSample (bitsPerSample_), usesFloatingPointData (false), output (out), formatName (formatName_) { } AudioFormatWriter::~AudioFormatWriter() { delete output; } bool AudioFormatWriter::writeFromAudioReader (AudioFormatReader& reader, int64 startSample, int64 numSamplesToRead) { const int bufferSize = 16384; AudioSampleBuffer tempBuffer (numChannels, bufferSize); int* buffers [128]; zerostruct (buffers); for (int i = tempBuffer.getNumChannels(); --i >= 0;) buffers[i] = reinterpret_cast (tempBuffer.getSampleData (i, 0)); if (numSamplesToRead < 0) numSamplesToRead = reader.lengthInSamples; while (numSamplesToRead > 0) { const int numToDo = (int) jmin (numSamplesToRead, (int64) bufferSize); if (! reader.read (buffers, numChannels, startSample, numToDo, false)) return false; if (reader.usesFloatingPointData != isFloatingPoint()) { int** bufferChan = buffers; while (*bufferChan != 0) { int* b = *bufferChan++; if (isFloatingPoint()) { // int -> float const double factor = 1.0 / std::numeric_limits::max(); for (int i = 0; i < numToDo; ++i) ((float*) b)[i] = (float) (factor * b[i]); } else { // float -> int for (int i = 0; i < numToDo; ++i) { const double samp = *(const float*) b; if (samp <= -1.0) *b++ = std::numeric_limits::min(); else if (samp >= 1.0) *b++ = std::numeric_limits::max(); else *b++ = roundToInt (std::numeric_limits::max() * samp); } } } } if (! write (const_cast (buffers), numToDo)) return false; numSamplesToRead -= numToDo; startSample += numToDo; } return true; } bool AudioFormatWriter::writeFromAudioSource (AudioSource& source, int numSamplesToRead, const int samplesPerBlock) { AudioSampleBuffer tempBuffer (getNumChannels(), samplesPerBlock); int* buffers [128]; zerostruct (buffers); for (int i = tempBuffer.getNumChannels(); --i >= 0;) buffers[i] = reinterpret_cast (tempBuffer.getSampleData (i, 0)); while (numSamplesToRead > 0) { const int numToDo = jmin (numSamplesToRead, samplesPerBlock); AudioSourceChannelInfo info; info.buffer = &tempBuffer; info.startSample = 0; info.numSamples = numToDo; info.clearActiveBufferRegion(); source.getNextAudioBlock (info); if (! isFloatingPoint()) { int** bufferChan = buffers; while (*bufferChan != 0) { int* b = *bufferChan++; // float -> int for (int j = numToDo; --j >= 0;) { const double samp = *(const float*) b; if (samp <= -1.0) *b++ = std::numeric_limits::min(); else if (samp >= 1.0) *b++ = std::numeric_limits::max(); else *b++ = roundToInt (std::numeric_limits::max() * samp); } } } if (! write ((const int**) buffers, numToDo)) return false; numSamplesToRead -= numToDo; } return true; } AudioFormat::AudioFormat (const String& name, const StringArray& extensions) : formatName (name), fileExtensions (extensions) { } AudioFormat::~AudioFormat() { } const String& AudioFormat::getFormatName() const { return formatName; } const StringArray& AudioFormat::getFileExtensions() const { return fileExtensions; } bool AudioFormat::canHandleFile (const File& f) { for (int i = 0; i < fileExtensions.size(); ++i) if (f.hasFileExtension (fileExtensions[i])) return true; return false; } bool AudioFormat::isCompressed() { return false; } const StringArray AudioFormat::getQualityOptions() { return StringArray(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioFormat.cpp ***/ /*** Start of inlined file: juce_AudioFormatManager.cpp ***/ BEGIN_JUCE_NAMESPACE AudioFormatManager::AudioFormatManager() : defaultFormatIndex (0) { } AudioFormatManager::~AudioFormatManager() { clearFormats(); clearSingletonInstance(); } juce_ImplementSingleton (AudioFormatManager); void AudioFormatManager::registerFormat (AudioFormat* newFormat, const bool makeThisTheDefaultFormat) { jassert (newFormat != 0); if (newFormat != 0) { #if JUCE_DEBUG for (int i = getNumKnownFormats(); --i >= 0;) { if (getKnownFormat (i)->getFormatName() == newFormat->getFormatName()) { jassertfalse; // trying to add the same format twice! } } #endif if (makeThisTheDefaultFormat) defaultFormatIndex = getNumKnownFormats(); knownFormats.add (newFormat); } } void AudioFormatManager::registerBasicFormats() { #if JUCE_MAC registerFormat (new AiffAudioFormat(), true); registerFormat (new WavAudioFormat(), false); #else registerFormat (new WavAudioFormat(), true); registerFormat (new AiffAudioFormat(), false); #endif #if JUCE_USE_FLAC registerFormat (new FlacAudioFormat(), false); #endif #if JUCE_USE_OGGVORBIS registerFormat (new OggVorbisAudioFormat(), false); #endif } void AudioFormatManager::clearFormats() { knownFormats.clear(); defaultFormatIndex = 0; } int AudioFormatManager::getNumKnownFormats() const { return knownFormats.size(); } AudioFormat* AudioFormatManager::getKnownFormat (const int index) const { return knownFormats [index]; } AudioFormat* AudioFormatManager::getDefaultFormat() const { return getKnownFormat (defaultFormatIndex); } AudioFormat* AudioFormatManager::findFormatForFileExtension (const String& fileExtension) const { String e (fileExtension); if (! e.startsWithChar ('.')) e = "." + e; for (int i = 0; i < getNumKnownFormats(); ++i) if (getKnownFormat(i)->getFileExtensions().contains (e, true)) return getKnownFormat(i); return 0; } const String AudioFormatManager::getWildcardForAllFormats() const { StringArray allExtensions; int i; for (i = 0; i < getNumKnownFormats(); ++i) allExtensions.addArray (getKnownFormat (i)->getFileExtensions()); allExtensions.trim(); allExtensions.removeEmptyStrings(); String s; for (i = 0; i < allExtensions.size(); ++i) { s << '*'; if (! allExtensions[i].startsWithChar ('.')) s << '.'; s << allExtensions[i]; if (i < allExtensions.size() - 1) s << ';'; } return s; } AudioFormatReader* AudioFormatManager::createReaderFor (const File& file) { // you need to actually register some formats before the manager can // use them to open a file! jassert (getNumKnownFormats() > 0); for (int i = 0; i < getNumKnownFormats(); ++i) { AudioFormat* const af = getKnownFormat(i); if (af->canHandleFile (file)) { InputStream* const in = file.createInputStream(); if (in != 0) { AudioFormatReader* const r = af->createReaderFor (in, true); if (r != 0) return r; } } } return 0; } AudioFormatReader* AudioFormatManager::createReaderFor (InputStream* audioFileStream) { // you need to actually register some formats before the manager can // use them to open a file! jassert (getNumKnownFormats() > 0); ScopedPointer in (audioFileStream); if (in != 0) { const int64 originalStreamPos = in->getPosition(); for (int i = 0; i < getNumKnownFormats(); ++i) { AudioFormatReader* const r = getKnownFormat(i)->createReaderFor (in, false); if (r != 0) { in.release(); return r; } in->setPosition (originalStreamPos); // the stream that is passed-in must be capable of being repositioned so // that all the formats can have a go at opening it. jassert (in->getPosition() == originalStreamPos); } } return 0; } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioFormatManager.cpp ***/ /*** Start of inlined file: juce_AudioSubsectionReader.cpp ***/ BEGIN_JUCE_NAMESPACE AudioSubsectionReader::AudioSubsectionReader (AudioFormatReader* const source_, const int64 startSample_, const int64 length_, const bool deleteSourceWhenDeleted_) : AudioFormatReader (0, source_->getFormatName()), source (source_), startSample (startSample_), deleteSourceWhenDeleted (deleteSourceWhenDeleted_) { length = jmin (jmax ((int64) 0, source->lengthInSamples - startSample), length_); sampleRate = source->sampleRate; bitsPerSample = source->bitsPerSample; lengthInSamples = length; numChannels = source->numChannels; usesFloatingPointData = source->usesFloatingPointData; } AudioSubsectionReader::~AudioSubsectionReader() { if (deleteSourceWhenDeleted) delete source; } bool AudioSubsectionReader::readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) { if (startSampleInFile + numSamples > length) { for (int i = numDestChannels; --i >= 0;) if (destSamples[i] != 0) zeromem (destSamples[i], sizeof (int) * numSamples); numSamples = jmin (numSamples, (int) (length - startSampleInFile)); if (numSamples <= 0) return true; } return source->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, startSampleInFile + startSample, numSamples); } void AudioSubsectionReader::readMaxLevels (int64 startSampleInFile, int64 numSamples, float& lowestLeft, float& highestLeft, float& lowestRight, float& highestRight) { startSampleInFile = jmax ((int64) 0, startSampleInFile); numSamples = jmax ((int64) 0, jmin (numSamples, length - startSampleInFile)); source->readMaxLevels (startSampleInFile + startSample, numSamples, lowestLeft, highestLeft, lowestRight, highestRight); } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioSubsectionReader.cpp ***/ /*** Start of inlined file: juce_AudioThumbnail.cpp ***/ BEGIN_JUCE_NAMESPACE AudioThumbnail::AudioThumbnail (const int orginalSamplesPerThumbnailSample_, AudioFormatManager& formatManagerToUse_, AudioThumbnailCache& cacheToUse) : formatManagerToUse (formatManagerToUse_), cache (cacheToUse), orginalSamplesPerThumbnailSample (orginalSamplesPerThumbnailSample_), timeBeforeDeletingReader (2000) { clear(); } AudioThumbnail::~AudioThumbnail() { cache.removeThumbnail (this); const ScopedLock sl (readerLock); reader = 0; } AudioThumbnail::DataFormat* AudioThumbnail::getData() const throw() { jassert (data.getData() != 0); return static_cast (data.getData()); } void AudioThumbnail::setSource (InputSource* const newSource) { cache.removeThumbnail (this); timerCallback(); // stops the timer and deletes the reader source = newSource; clear(); if (newSource != 0 && ! (cache.loadThumb (*this, newSource->hashCode()) && isFullyLoaded())) { { const ScopedLock sl (readerLock); reader = createReader(); } if (reader != 0) { initialiseFromAudioFile (*reader); cache.addThumbnail (this); } } sendChangeMessage (this); } bool AudioThumbnail::useTimeSlice() { const ScopedLock sl (readerLock); if (isFullyLoaded()) { if (reader != 0) startTimer (timeBeforeDeletingReader); cache.removeThumbnail (this); return false; } if (reader == 0) reader = createReader(); if (reader != 0) { readNextBlockFromAudioFile (*reader); stopTimer(); sendChangeMessage (this); const bool justFinished = isFullyLoaded(); if (justFinished) cache.storeThumb (*this, source->hashCode()); return ! justFinished; } return false; } AudioFormatReader* AudioThumbnail::createReader() const { if (source != 0) { InputStream* const audioFileStream = source->createInputStream(); if (audioFileStream != 0) return formatManagerToUse.createReaderFor (audioFileStream); } return 0; } void AudioThumbnail::timerCallback() { stopTimer(); const ScopedLock sl (readerLock); reader = 0; } void AudioThumbnail::clear() { data.setSize (sizeof (DataFormat) + 3); DataFormat* const d = getData(); d->thumbnailMagic[0] = 'j'; d->thumbnailMagic[1] = 'a'; d->thumbnailMagic[2] = 't'; d->thumbnailMagic[3] = 'm'; d->samplesPerThumbSample = orginalSamplesPerThumbnailSample; d->totalSamples = 0; d->numFinishedSamples = 0; d->numThumbnailSamples = 0; d->numChannels = 0; d->sampleRate = 0; numSamplesCached = 0; cacheNeedsRefilling = true; } void AudioThumbnail::loadFrom (InputStream& input) { const ScopedLock sl (readerLock); data.setSize (0); input.readIntoMemoryBlock (data); DataFormat* const d = getData(); d->flipEndiannessIfBigEndian(); if (! (d->thumbnailMagic[0] == 'j' && d->thumbnailMagic[1] == 'a' && d->thumbnailMagic[2] == 't' && d->thumbnailMagic[3] == 'm')) { clear(); } numSamplesCached = 0; cacheNeedsRefilling = true; } void AudioThumbnail::saveTo (OutputStream& output) const { const ScopedLock sl (readerLock); DataFormat* const d = getData(); d->flipEndiannessIfBigEndian(); output.write (d, (int) data.getSize()); d->flipEndiannessIfBigEndian(); } bool AudioThumbnail::initialiseFromAudioFile (AudioFormatReader& fileReader) { DataFormat* d = getData(); d->totalSamples = fileReader.lengthInSamples; d->numChannels = jmin ((uint32) 2, fileReader.numChannels); d->numFinishedSamples = 0; d->sampleRate = roundToInt (fileReader.sampleRate); d->numThumbnailSamples = (int) (d->totalSamples / d->samplesPerThumbSample) + 1; data.setSize (sizeof (DataFormat) + 3 + d->numThumbnailSamples * d->numChannels * 2); d = getData(); zeromem (d->data, d->numThumbnailSamples * d->numChannels * 2); return d->totalSamples > 0; } bool AudioThumbnail::readNextBlockFromAudioFile (AudioFormatReader& fileReader) { DataFormat* const d = getData(); if (d->numFinishedSamples < d->totalSamples) { const int numToDo = (int) jmin ((int64) 65536, d->totalSamples - d->numFinishedSamples); generateSection (fileReader, d->numFinishedSamples, numToDo); d->numFinishedSamples += numToDo; } cacheNeedsRefilling = true; return d->numFinishedSamples < d->totalSamples; } int AudioThumbnail::getNumChannels() const throw() { return getData()->numChannels; } double AudioThumbnail::getTotalLength() const throw() { const DataFormat* const d = getData(); if (d->sampleRate > 0) return d->totalSamples / (double) d->sampleRate; else return 0.0; } void AudioThumbnail::generateSection (AudioFormatReader& fileReader, int64 startSample, int numSamples) { DataFormat* const d = getData(); const int firstDataPos = (int) (startSample / d->samplesPerThumbSample); const int lastDataPos = (int) ((startSample + numSamples) / d->samplesPerThumbSample); char* const l = getChannelData (0); char* const r = getChannelData (1); for (int i = firstDataPos; i < lastDataPos; ++i) { const int sourceStart = i * d->samplesPerThumbSample; const int sourceEnd = sourceStart + d->samplesPerThumbSample; float lowestLeft, highestLeft, lowestRight, highestRight; fileReader.readMaxLevels (sourceStart, sourceEnd - sourceStart, lowestLeft, highestLeft, lowestRight, highestRight); int n = i * 2; if (r != 0) { l [n] = (char) jlimit (-128.0f, 127.0f, lowestLeft * 127.0f); r [n++] = (char) jlimit (-128.0f, 127.0f, lowestRight * 127.0f); l [n] = (char) jlimit (-128.0f, 127.0f, highestLeft * 127.0f); r [n++] = (char) jlimit (-128.0f, 127.0f, highestRight * 127.0f); } else { l [n++] = (char) jlimit (-128.0f, 127.0f, lowestLeft * 127.0f); l [n++] = (char) jlimit (-128.0f, 127.0f, highestLeft * 127.0f); } } } char* AudioThumbnail::getChannelData (int channel) const { DataFormat* const d = getData(); if (channel >= 0 && channel < d->numChannels) return d->data + (channel * 2 * d->numThumbnailSamples); return 0; } bool AudioThumbnail::isFullyLoaded() const throw() { const DataFormat* const d = getData(); return d->numFinishedSamples >= d->totalSamples; } void AudioThumbnail::refillCache (const int numSamples, double startTime, const double timePerPixel) { const DataFormat* const d = getData(); if (numSamples <= 0 || timePerPixel <= 0.0 || d->sampleRate <= 0) { numSamplesCached = 0; cacheNeedsRefilling = true; return; } if (numSamples == numSamplesCached && numChannelsCached == d->numChannels && startTime == cachedStart && timePerPixel == cachedTimePerPixel && ! cacheNeedsRefilling) { return; } numSamplesCached = numSamples; numChannelsCached = d->numChannels; cachedStart = startTime; cachedTimePerPixel = timePerPixel; cachedLevels.ensureSize (2 * numChannelsCached * numSamples); const bool needExtraDetail = (timePerPixel * d->sampleRate <= d->samplesPerThumbSample); const ScopedLock sl (readerLock); cacheNeedsRefilling = false; if (needExtraDetail && reader == 0) reader = createReader(); if (reader != 0 && timePerPixel * d->sampleRate <= d->samplesPerThumbSample) { startTimer (timeBeforeDeletingReader); char* cacheData = static_cast (cachedLevels.getData()); int sample = roundToInt (startTime * d->sampleRate); for (int i = numSamples; --i >= 0;) { const int nextSample = roundToInt ((startTime + timePerPixel) * d->sampleRate); if (sample >= 0) { if (sample >= reader->lengthInSamples) break; float lmin, lmax, rmin, rmax; reader->readMaxLevels (sample, jmax (1, nextSample - sample), lmin, lmax, rmin, rmax); cacheData[0] = (char) jlimit (-128, 127, roundFloatToInt (lmin * 127.0f)); cacheData[1] = (char) jlimit (-128, 127, roundFloatToInt (lmax * 127.0f)); if (numChannelsCached > 1) { cacheData[2] = (char) jlimit (-128, 127, roundFloatToInt (rmin * 127.0f)); cacheData[3] = (char) jlimit (-128, 127, roundFloatToInt (rmax * 127.0f)); } cacheData += 2 * numChannelsCached; } startTime += timePerPixel; sample = nextSample; } } else { for (int channelNum = 0; channelNum < numChannelsCached; ++channelNum) { char* const channelData = getChannelData (channelNum); char* cacheData = static_cast (cachedLevels.getData()) + channelNum * 2; const double timeToThumbSampleFactor = d->sampleRate / (double) d->samplesPerThumbSample; startTime = cachedStart; int sample = roundToInt (startTime * timeToThumbSampleFactor); const int numFinished = (int) (d->numFinishedSamples / d->samplesPerThumbSample); for (int i = numSamples; --i >= 0;) { const int nextSample = roundToInt ((startTime + timePerPixel) * timeToThumbSampleFactor); if (sample >= 0 && channelData != 0) { char mx = -128; char mn = 127; while (sample <= nextSample) { if (sample >= numFinished) break; const int n = sample << 1; const char sampMin = channelData [n]; const char sampMax = channelData [n + 1]; if (sampMin < mn) mn = sampMin; if (sampMax > mx) mx = sampMax; ++sample; } if (mn <= mx) { cacheData[0] = mn; cacheData[1] = mx; } else { cacheData[0] = 1; cacheData[1] = 0; } } else { cacheData[0] = 1; cacheData[1] = 0; } cacheData += numChannelsCached * 2; startTime += timePerPixel; sample = nextSample; } } } } void AudioThumbnail::drawChannel (Graphics& g, int x, int y, int w, int h, double startTime, double endTime, int channelNum, const float verticalZoomFactor) { refillCache (w, startTime, (endTime - startTime) / w); if (numSamplesCached >= w && channelNum >= 0 && channelNum < numChannelsCached) { const float topY = (float) y; const float bottomY = topY + h; const float midY = topY + h * 0.5f; const float vscale = verticalZoomFactor * h / 256.0f; const Rectangle clip (g.getClipBounds()); const int skipLeft = jlimit (0, w, clip.getX() - x); w -= skipLeft; x += skipLeft; const char* cacheData = static_cast (cachedLevels.getData()) + (channelNum << 1) + skipLeft * (numChannelsCached << 1); while (--w >= 0) { const char mn = cacheData[0]; const char mx = cacheData[1]; cacheData += numChannelsCached << 1; if (mn <= mx) // if the wrong way round, signifies that the sample's not yet known g.drawVerticalLine (x, jmax (midY - mx * vscale - 0.3f, topY), jmin (midY - mn * vscale + 0.3f, bottomY)); if (++x >= clip.getRight()) break; } } } void AudioThumbnail::DataFormat::flipEndiannessIfBigEndian() throw() { #if JUCE_BIG_ENDIAN struct Flipper { static void flip (int32& n) { n = (int32) ByteOrder::swap ((uint32) n); } static void flip (int64& n) { n = (int64) ByteOrder::swap ((uint64) n); } }; Flipper::flip (samplesPerThumbSample); Flipper::flip (totalSamples); Flipper::flip (numFinishedSamples); Flipper::flip (numThumbnailSamples); Flipper::flip (numChannels); Flipper::flip (sampleRate); #endif } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioThumbnail.cpp ***/ /*** Start of inlined file: juce_AudioThumbnailCache.cpp ***/ BEGIN_JUCE_NAMESPACE struct ThumbnailCacheEntry { int64 hash; uint32 lastUsed; MemoryBlock data; juce_UseDebuggingNewOperator }; AudioThumbnailCache::AudioThumbnailCache (const int maxNumThumbsToStore_) : TimeSliceThread ("thumb cache"), maxNumThumbsToStore (maxNumThumbsToStore_) { startThread (2); } AudioThumbnailCache::~AudioThumbnailCache() { } bool AudioThumbnailCache::loadThumb (AudioThumbnail& thumb, const int64 hashCode) { for (int i = thumbs.size(); --i >= 0;) { if (thumbs[i]->hash == hashCode) { MemoryInputStream in (thumbs[i]->data, false); thumb.loadFrom (in); thumbs[i]->lastUsed = Time::getMillisecondCounter(); return true; } } return false; } void AudioThumbnailCache::storeThumb (const AudioThumbnail& thumb, const int64 hashCode) { MemoryOutputStream out; thumb.saveTo (out); ThumbnailCacheEntry* te = 0; for (int i = thumbs.size(); --i >= 0;) { if (thumbs[i]->hash == hashCode) { te = thumbs[i]; break; } } if (te == 0) { te = new ThumbnailCacheEntry(); te->hash = hashCode; if (thumbs.size() < maxNumThumbsToStore) { thumbs.add (te); } else { int oldest = 0; unsigned int oldestTime = Time::getMillisecondCounter() + 1; int i; for (i = thumbs.size(); --i >= 0;) if (thumbs[i]->lastUsed < oldestTime) oldest = i; thumbs.set (i, te); } } te->lastUsed = Time::getMillisecondCounter(); te->data.setSize (0); te->data.append (out.getData(), out.getDataSize()); } void AudioThumbnailCache::clear() { thumbs.clear(); } void AudioThumbnailCache::addThumbnail (AudioThumbnail* const thumb) { addTimeSliceClient (thumb); } void AudioThumbnailCache::removeThumbnail (AudioThumbnail* const thumb) { removeTimeSliceClient (thumb); } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioThumbnailCache.cpp ***/ /*** Start of inlined file: juce_QuickTimeAudioFormat.cpp ***/ #if JUCE_QUICKTIME && ! (JUCE_64BIT || JUCE_IOS) #if ! JUCE_WINDOWS #include #include #include #include #include #else #if JUCE_MSVC #pragma warning (push) #pragma warning (disable : 4100) #endif /* If you've got an include error here, you probably need to install the QuickTime SDK and add its header directory to your include path. Alternatively, if you don't need any QuickTime services, just turn off the JUC_QUICKTIME flag in juce_Config.h */ #include #include #include #include #include #if JUCE_MSVC #pragma warning (pop) #endif #endif BEGIN_JUCE_NAMESPACE bool juce_OpenQuickTimeMovieFromStream (InputStream* input, Movie& movie, Handle& dataHandle); static const char* const quickTimeFormatName = "QuickTime file"; static const char* const quickTimeExtensions[] = { ".mov", ".mp3", ".mp4", 0 }; class QTAudioReader : public AudioFormatReader { public: QTAudioReader (InputStream* const input_, const int trackNum_) : AudioFormatReader (input_, TRANS (quickTimeFormatName)), ok (false), movie (0), trackNum (trackNum_), lastSampleRead (0), lastThreadId (0), extractor (0), dataHandle (0) { bufferList.calloc (256, 1); #if JUCE_WINDOWS if (InitializeQTML (0) != noErr) return; #endif if (EnterMovies() != noErr) return; bool opened = juce_OpenQuickTimeMovieFromStream (input_, movie, dataHandle); if (! opened) return; { const int numTracks = GetMovieTrackCount (movie); int trackCount = 0; for (int i = 1; i <= numTracks; ++i) { track = GetMovieIndTrack (movie, i); media = GetTrackMedia (track); OSType mediaType; GetMediaHandlerDescription (media, &mediaType, 0, 0); if (mediaType == SoundMediaType && trackCount++ == trackNum_) { ok = true; break; } } } if (! ok) return; ok = false; lengthInSamples = GetMediaDecodeDuration (media); usesFloatingPointData = false; samplesPerFrame = (int) (GetMediaDecodeDuration (media) / GetMediaSampleCount (media)); trackUnitsPerFrame = GetMovieTimeScale (movie) * samplesPerFrame / GetMediaTimeScale (media); OSStatus err = MovieAudioExtractionBegin (movie, 0, &extractor); unsigned long output_layout_size; err = MovieAudioExtractionGetPropertyInfo (extractor, kQTPropertyClass_MovieAudioExtraction_Audio, kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout, 0, &output_layout_size, 0); if (err != noErr) return; HeapBlock qt_audio_channel_layout; qt_audio_channel_layout.calloc (output_layout_size, 1); err = MovieAudioExtractionGetProperty (extractor, kQTPropertyClass_MovieAudioExtraction_Audio, kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout, output_layout_size, qt_audio_channel_layout, 0); qt_audio_channel_layout[0].mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; err = MovieAudioExtractionSetProperty (extractor, kQTPropertyClass_MovieAudioExtraction_Audio, kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout, output_layout_size, qt_audio_channel_layout); err = MovieAudioExtractionGetProperty (extractor, kQTPropertyClass_MovieAudioExtraction_Audio, kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription, sizeof (inputStreamDesc), &inputStreamDesc, 0); if (err != noErr) return; inputStreamDesc.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian; inputStreamDesc.mBitsPerChannel = sizeof (SInt16) * 8; inputStreamDesc.mChannelsPerFrame = jmin ((UInt32) 2, inputStreamDesc.mChannelsPerFrame); inputStreamDesc.mBytesPerFrame = sizeof (SInt16) * inputStreamDesc.mChannelsPerFrame; inputStreamDesc.mBytesPerPacket = inputStreamDesc.mBytesPerFrame; err = MovieAudioExtractionSetProperty (extractor, kQTPropertyClass_MovieAudioExtraction_Audio, kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription, sizeof (inputStreamDesc), &inputStreamDesc); if (err != noErr) return; Boolean allChannelsDiscrete = false; err = MovieAudioExtractionSetProperty (extractor, kQTPropertyClass_MovieAudioExtraction_Movie, kQTMovieAudioExtractionMoviePropertyID_AllChannelsDiscrete, sizeof (allChannelsDiscrete), &allChannelsDiscrete); if (err != noErr) return; bufferList->mNumberBuffers = 1; bufferList->mBuffers[0].mNumberChannels = inputStreamDesc.mChannelsPerFrame; bufferList->mBuffers[0].mDataByteSize = (UInt32) (samplesPerFrame * inputStreamDesc.mBytesPerFrame) + 16; dataBuffer.malloc (bufferList->mBuffers[0].mDataByteSize); bufferList->mBuffers[0].mData = dataBuffer; sampleRate = inputStreamDesc.mSampleRate; bitsPerSample = 16; numChannels = inputStreamDesc.mChannelsPerFrame; detachThread(); ok = true; } ~QTAudioReader() { if (dataHandle != 0) DisposeHandle (dataHandle); if (extractor != 0) { MovieAudioExtractionEnd (extractor); extractor = 0; } checkThreadIsAttached(); DisposeMovie (movie); #if JUCE_MAC ExitMoviesOnThread (); #endif } bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) { checkThreadIsAttached(); while (numSamples > 0) { if (! loadFrame ((int) startSampleInFile)) return false; const int numToDo = jmin (numSamples, samplesPerFrame); for (int j = numDestChannels; --j >= 0;) { if (destSamples[j] != 0) { const short* const src = ((const short*) bufferList->mBuffers[0].mData) + j; for (int i = 0; i < numToDo; ++i) destSamples[j][startOffsetInDestBuffer + i] = src [i << 1] << 16; } } startOffsetInDestBuffer += numToDo; startSampleInFile += numToDo; numSamples -= numToDo; } detachThread(); return true; } bool loadFrame (const int sampleNum) { if (lastSampleRead != sampleNum) { TimeRecord time; time.scale = (TimeScale) inputStreamDesc.mSampleRate; time.base = 0; time.value.hi = 0; time.value.lo = (UInt32) sampleNum; OSStatus err = MovieAudioExtractionSetProperty (extractor, kQTPropertyClass_MovieAudioExtraction_Movie, kQTMovieAudioExtractionMoviePropertyID_CurrentTime, sizeof (time), &time); if (err != noErr) return false; } bufferList->mBuffers[0].mDataByteSize = inputStreamDesc.mBytesPerFrame * samplesPerFrame; UInt32 outFlags = 0; UInt32 actualNumSamples = samplesPerFrame; OSStatus err = MovieAudioExtractionFillBuffer (extractor, &actualNumSamples, bufferList, &outFlags); lastSampleRead = sampleNum + samplesPerFrame; return err == noErr; } juce_UseDebuggingNewOperator bool ok; private: Movie movie; Media media; Track track; const int trackNum; double trackUnitsPerFrame; int samplesPerFrame; int lastSampleRead; Thread::ThreadID lastThreadId; MovieAudioExtractionRef extractor; AudioStreamBasicDescription inputStreamDesc; HeapBlock bufferList; HeapBlock dataBuffer; Handle dataHandle; void checkThreadIsAttached() { #if JUCE_MAC if (Thread::getCurrentThreadId() != lastThreadId) EnterMoviesOnThread (0); AttachMovieToCurrentThread (movie); #endif } void detachThread() { #if JUCE_MAC DetachMovieFromCurrentThread (movie); #endif } QTAudioReader (const QTAudioReader&); QTAudioReader& operator= (const QTAudioReader&); }; QuickTimeAudioFormat::QuickTimeAudioFormat() : AudioFormat (TRANS (quickTimeFormatName), StringArray (quickTimeExtensions)) { } QuickTimeAudioFormat::~QuickTimeAudioFormat() { } const Array QuickTimeAudioFormat::getPossibleSampleRates() { return Array(); } const Array QuickTimeAudioFormat::getPossibleBitDepths() { return Array(); } bool QuickTimeAudioFormat::canDoStereo() { return true; } bool QuickTimeAudioFormat::canDoMono() { return true; } AudioFormatReader* QuickTimeAudioFormat::createReaderFor (InputStream* sourceStream, const bool deleteStreamIfOpeningFails) { ScopedPointer r (new QTAudioReader (sourceStream, 0)); if (r->ok) return r.release(); if (! deleteStreamIfOpeningFails) r->input = 0; return 0; } AudioFormatWriter* QuickTimeAudioFormat::createWriterFor (OutputStream* /*streamToWriteTo*/, double /*sampleRateToUse*/, unsigned int /*numberOfChannels*/, int /*bitsPerSample*/, const StringPairArray& /*metadataValues*/, int /*qualityOptionIndex*/) { jassertfalse; // not yet implemented! return 0; } END_JUCE_NAMESPACE #endif /*** End of inlined file: juce_QuickTimeAudioFormat.cpp ***/ /*** Start of inlined file: juce_WavAudioFormat.cpp ***/ BEGIN_JUCE_NAMESPACE static const char* const wavFormatName = "WAV file"; static const char* const wavExtensions[] = { ".wav", ".bwf", 0 }; const char* const WavAudioFormat::bwavDescription = "bwav description"; const char* const WavAudioFormat::bwavOriginator = "bwav originator"; const char* const WavAudioFormat::bwavOriginatorRef = "bwav originator ref"; const char* const WavAudioFormat::bwavOriginationDate = "bwav origination date"; const char* const WavAudioFormat::bwavOriginationTime = "bwav origination time"; const char* const WavAudioFormat::bwavTimeReference = "bwav time reference"; const char* const WavAudioFormat::bwavCodingHistory = "bwav coding history"; const StringPairArray WavAudioFormat::createBWAVMetadata (const String& description, const String& originator, const String& originatorRef, const Time& date, const int64 timeReferenceSamples, const String& codingHistory) { StringPairArray m; m.set (bwavDescription, description); m.set (bwavOriginator, originator); m.set (bwavOriginatorRef, originatorRef); m.set (bwavOriginationDate, date.formatted ("%Y-%m-%d")); m.set (bwavOriginationTime, date.formatted ("%H:%M:%S")); m.set (bwavTimeReference, String (timeReferenceSamples)); m.set (bwavCodingHistory, codingHistory); return m; } #if JUCE_MSVC #pragma pack (push, 1) #define PACKED #elif JUCE_GCC #define PACKED __attribute__((packed)) #else #define PACKED #endif struct BWAVChunk { char description [256]; char originator [32]; char originatorRef [32]; char originationDate [10]; char originationTime [8]; uint32 timeRefLow; uint32 timeRefHigh; uint16 version; uint8 umid[64]; uint8 reserved[190]; char codingHistory[1]; void copyTo (StringPairArray& values) const { values.set (WavAudioFormat::bwavDescription, String::fromUTF8 (description, 256)); values.set (WavAudioFormat::bwavOriginator, String::fromUTF8 (originator, 32)); values.set (WavAudioFormat::bwavOriginatorRef, String::fromUTF8 (originatorRef, 32)); values.set (WavAudioFormat::bwavOriginationDate, String::fromUTF8 (originationDate, 10)); values.set (WavAudioFormat::bwavOriginationTime, String::fromUTF8 (originationTime, 8)); const uint32 timeLow = ByteOrder::swapIfBigEndian (timeRefLow); const uint32 timeHigh = ByteOrder::swapIfBigEndian (timeRefHigh); const int64 time = (((int64)timeHigh) << 32) + timeLow; values.set (WavAudioFormat::bwavTimeReference, String (time)); values.set (WavAudioFormat::bwavCodingHistory, String::fromUTF8 (codingHistory)); } static MemoryBlock createFrom (const StringPairArray& values) { const size_t sizeNeeded = sizeof (BWAVChunk) + values [WavAudioFormat::bwavCodingHistory].getNumBytesAsUTF8(); MemoryBlock data ((sizeNeeded + 3) & ~3); data.fillWith (0); BWAVChunk* b = (BWAVChunk*) data.getData(); // Allow these calls to overwrite an extra byte at the end, which is fine as long // as they get called in the right order.. values [WavAudioFormat::bwavDescription].copyToUTF8 (b->description, 257); values [WavAudioFormat::bwavOriginator].copyToUTF8 (b->originator, 33); values [WavAudioFormat::bwavOriginatorRef].copyToUTF8 (b->originatorRef, 33); values [WavAudioFormat::bwavOriginationDate].copyToUTF8 (b->originationDate, 11); values [WavAudioFormat::bwavOriginationTime].copyToUTF8 (b->originationTime, 9); const int64 time = values [WavAudioFormat::bwavTimeReference].getLargeIntValue(); b->timeRefLow = ByteOrder::swapIfBigEndian ((uint32) (time & 0xffffffff)); b->timeRefHigh = ByteOrder::swapIfBigEndian ((uint32) (time >> 32)); values [WavAudioFormat::bwavCodingHistory].copyToUTF8 (b->codingHistory, 0x7fffffff); if (b->description[0] != 0 || b->originator[0] != 0 || b->originationDate[0] != 0 || b->originationTime[0] != 0 || b->codingHistory[0] != 0 || time != 0) { return data; } return MemoryBlock(); } } PACKED; struct SMPLChunk { struct SampleLoop { uint32 identifier; uint32 type; uint32 start; uint32 end; uint32 fraction; uint32 playCount; } PACKED; uint32 manufacturer; uint32 product; uint32 samplePeriod; uint32 midiUnityNote; uint32 midiPitchFraction; uint32 smpteFormat; uint32 smpteOffset; uint32 numSampleLoops; uint32 samplerData; SampleLoop loops[1]; void copyTo (StringPairArray& values, const int totalSize) const { values.set ("Manufacturer", String (ByteOrder::swapIfBigEndian (manufacturer))); values.set ("Product", String (ByteOrder::swapIfBigEndian (product))); values.set ("SamplePeriod", String (ByteOrder::swapIfBigEndian (samplePeriod))); values.set ("MidiUnityNote", String (ByteOrder::swapIfBigEndian (midiUnityNote))); values.set ("MidiPitchFraction", String (ByteOrder::swapIfBigEndian (midiPitchFraction))); values.set ("SmpteFormat", String (ByteOrder::swapIfBigEndian (smpteFormat))); values.set ("SmpteOffset", String (ByteOrder::swapIfBigEndian (smpteOffset))); values.set ("NumSampleLoops", String (ByteOrder::swapIfBigEndian (numSampleLoops))); values.set ("SamplerData", String (ByteOrder::swapIfBigEndian (samplerData))); for (uint32 i = 0; i < numSampleLoops; ++i) { if ((uint8*) (loops + (i + 1)) > ((uint8*) this) + totalSize) break; const String prefix ("Loop" + String(i)); values.set (prefix + "Identifier", String (ByteOrder::swapIfBigEndian (loops[i].identifier))); values.set (prefix + "Type", String (ByteOrder::swapIfBigEndian (loops[i].type))); values.set (prefix + "Start", String (ByteOrder::swapIfBigEndian (loops[i].start))); values.set (prefix + "End", String (ByteOrder::swapIfBigEndian (loops[i].end))); values.set (prefix + "Fraction", String (ByteOrder::swapIfBigEndian (loops[i].fraction))); values.set (prefix + "PlayCount", String (ByteOrder::swapIfBigEndian (loops[i].playCount))); } } static MemoryBlock createFrom (const StringPairArray& values) { const int numLoops = jmin (64, values.getValue ("NumSampleLoops", "0").getIntValue()); if (numLoops <= 0) return MemoryBlock(); const size_t sizeNeeded = sizeof (SMPLChunk) + (numLoops - 1) * sizeof (SampleLoop); MemoryBlock data ((sizeNeeded + 3) & ~3); data.fillWith (0); SMPLChunk* s = (SMPLChunk*) data.getData(); // Allow these calls to overwrite an extra byte at the end, which is fine as long // as they get called in the right order.. s->manufacturer = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("Manufacturer", "0").getIntValue()); s->product = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("Product", "0").getIntValue()); s->samplePeriod = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("SamplePeriod", "0").getIntValue()); s->midiUnityNote = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("MidiUnityNote", "60").getIntValue()); s->midiPitchFraction = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("MidiPitchFraction", "0").getIntValue()); s->smpteFormat = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("SmpteFormat", "0").getIntValue()); s->smpteOffset = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("SmpteOffset", "0").getIntValue()); s->numSampleLoops = ByteOrder::swapIfBigEndian ((uint32) numLoops); s->samplerData = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("SamplerData", "0").getIntValue()); for (int i = 0; i < numLoops; ++i) { const String prefix ("Loop" + String(i)); s->loops[i].identifier = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "Identifier", "0").getIntValue()); s->loops[i].type = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "Type", "0").getIntValue()); s->loops[i].start = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "Start", "0").getIntValue()); s->loops[i].end = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "End", "0").getIntValue()); s->loops[i].fraction = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "Fraction", "0").getIntValue()); s->loops[i].playCount = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "PlayCount", "0").getIntValue()); } return data; } } PACKED; struct ExtensibleWavSubFormat { uint32 data1; uint16 data2; uint16 data3; uint8 data4[8]; } PACKED; #if JUCE_MSVC #pragma pack (pop) #endif #undef PACKED class WavAudioFormatReader : public AudioFormatReader { int bytesPerFrame; int64 dataChunkStart, dataLength; static inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); } WavAudioFormatReader (const WavAudioFormatReader&); WavAudioFormatReader& operator= (const WavAudioFormatReader&); public: int64 bwavChunkStart, bwavSize; WavAudioFormatReader (InputStream* const in) : AudioFormatReader (in, TRANS (wavFormatName)), dataLength (0), bwavChunkStart (0), bwavSize (0) { if (input->readInt() == chunkName ("RIFF")) { const uint32 len = (uint32) input->readInt(); const int64 end = input->getPosition() + len; bool hasGotType = false; bool hasGotData = false; if (input->readInt() == chunkName ("WAVE")) { while (input->getPosition() < end && ! input->isExhausted()) { const int chunkType = input->readInt(); uint32 length = (uint32) input->readInt(); const int64 chunkEnd = input->getPosition() + length + (length & 1); if (chunkType == chunkName ("fmt ")) { // read the format chunk const unsigned short format = input->readShort(); const short numChans = input->readShort(); sampleRate = input->readInt(); const int bytesPerSec = input->readInt(); numChannels = numChans; bytesPerFrame = bytesPerSec / (int)sampleRate; bitsPerSample = 8 * bytesPerFrame / numChans; if (format == 3) { usesFloatingPointData = true; } else if (format == 0xfffe /*WAVE_FORMAT_EXTENSIBLE*/) { if (length < 40) // too short { bytesPerFrame = 0; } else { input->skipNextBytes (12); // skip over blockAlign, bitsPerSample and speakerPosition mask ExtensibleWavSubFormat subFormat; subFormat.data1 = input->readInt(); subFormat.data2 = input->readShort(); subFormat.data3 = input->readShort(); input->read (subFormat.data4, sizeof (subFormat.data4)); const ExtensibleWavSubFormat pcmFormat = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; if (memcmp (&subFormat, &pcmFormat, sizeof (subFormat)) != 0) { const ExtensibleWavSubFormat ambisonicFormat = { 0x00000001, 0x0721, 0x11d3, { 0x86, 0x44, 0xC8, 0xC1, 0xCA, 0x00, 0x00, 0x00 } }; if (memcmp (&subFormat, &ambisonicFormat, sizeof (subFormat)) != 0) bytesPerFrame = 0; } } } else if (format != 1) { bytesPerFrame = 0; } hasGotType = true; } else if (chunkType == chunkName ("data")) { // get the data chunk's position dataLength = length; dataChunkStart = input->getPosition(); lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0; hasGotData = true; } else if (chunkType == chunkName ("bext")) { bwavChunkStart = input->getPosition(); bwavSize = length; // Broadcast-wav extension chunk.. HeapBlock bwav; bwav.calloc (jmax ((size_t) length + 1, sizeof (BWAVChunk)), 1); input->read (bwav, length); bwav->copyTo (metadataValues); } else if (chunkType == chunkName ("smpl")) { HeapBlock smpl; smpl.calloc (jmax ((size_t) length + 1, sizeof (SMPLChunk)), 1); input->read (smpl, length); smpl->copyTo (metadataValues, length); } else if (chunkEnd <= input->getPosition()) { break; } input->setPosition (chunkEnd); } } } } ~WavAudioFormatReader() { } bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) { const int64 samplesAvailable = lengthInSamples - startSampleInFile; if (samplesAvailable < numSamples) { for (int i = numDestChannels; --i >= 0;) if (destSamples[i] != 0) zeromem (destSamples[i] + startOffsetInDestBuffer, sizeof (int) * numSamples); numSamples = (int) samplesAvailable; } if (numSamples <= 0) return true; input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame); const int tempBufSize = 480 * 3 * 4; // (keep this a multiple of 3) char tempBuffer [tempBufSize]; while (numSamples > 0) { int* left = destSamples[0]; if (left != 0) left += startOffsetInDestBuffer; int* right = numDestChannels > 1 ? destSamples[1] : 0; if (right != 0) right += startOffsetInDestBuffer; const int numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples); const int bytesRead = input->read (tempBuffer, numThisTime * bytesPerFrame); if (bytesRead < numThisTime * bytesPerFrame) zeromem (tempBuffer + bytesRead, numThisTime * bytesPerFrame - bytesRead); if (bitsPerSample == 16) { const short* src = reinterpret_cast (tempBuffer); if (numChannels > 1) { if (left == 0) { for (int i = numThisTime; --i >= 0;) { ++src; *right++ = (int) ByteOrder::swapIfBigEndian ((unsigned short) *src++) << 16; } } else if (right == 0) { for (int i = numThisTime; --i >= 0;) { *left++ = (int) ByteOrder::swapIfBigEndian ((unsigned short) *src++) << 16; ++src; } } else { for (int i = numThisTime; --i >= 0;) { *left++ = (int) ByteOrder::swapIfBigEndian ((unsigned short) *src++) << 16; *right++ = (int) ByteOrder::swapIfBigEndian ((unsigned short) *src++) << 16; } } } else { for (int i = numThisTime; --i >= 0;) { *left++ = (int) ByteOrder::swapIfBigEndian ((unsigned short) *src++) << 16; } } } else if (bitsPerSample == 24) { const char* src = tempBuffer; if (numChannels > 1) { if (left == 0) { for (int i = numThisTime; --i >= 0;) { src += 3; *right++ = ByteOrder::littleEndian24Bit (src) << 8; src += 3; } } else if (right == 0) { for (int i = numThisTime; --i >= 0;) { *left++ = ByteOrder::littleEndian24Bit (src) << 8; src += 6; } } else { for (int i = 0; i < numThisTime; ++i) { *left++ = ByteOrder::littleEndian24Bit (src) << 8; src += 3; *right++ = ByteOrder::littleEndian24Bit (src) << 8; src += 3; } } } else { for (int i = 0; i < numThisTime; ++i) { *left++ = ByteOrder::littleEndian24Bit (src) << 8; src += 3; } } } else if (bitsPerSample == 32) { const unsigned int* src = (const unsigned int*) tempBuffer; unsigned int* l = reinterpret_cast (left); unsigned int* r = reinterpret_cast (right); if (numChannels > 1) { if (l == 0) { for (int i = numThisTime; --i >= 0;) { ++src; *r++ = ByteOrder::swapIfBigEndian (*src++); } } else if (r == 0) { for (int i = numThisTime; --i >= 0;) { *l++ = ByteOrder::swapIfBigEndian (*src++); ++src; } } else { for (int i = numThisTime; --i >= 0;) { *l++ = ByteOrder::swapIfBigEndian (*src++); *r++ = ByteOrder::swapIfBigEndian (*src++); } } } else { for (int i = numThisTime; --i >= 0;) { *l++ = ByteOrder::swapIfBigEndian (*src++); } } left = reinterpret_cast (l); right = reinterpret_cast (r); } else if (bitsPerSample == 8) { const unsigned char* src = reinterpret_cast (tempBuffer); if (numChannels > 1) { if (left == 0) { for (int i = numThisTime; --i >= 0;) { ++src; *right++ = ((int) *src++ - 128) << 24; } } else if (right == 0) { for (int i = numThisTime; --i >= 0;) { *left++ = ((int) *src++ - 128) << 24; ++src; } } else { for (int i = numThisTime; --i >= 0;) { *left++ = ((int) *src++ - 128) << 24; *right++ = ((int) *src++ - 128) << 24; } } } else { for (int i = numThisTime; --i >= 0;) { *left++ = ((int)*src++ - 128) << 24; } } } startOffsetInDestBuffer += numThisTime; numSamples -= numThisTime; } if (numSamples > 0) { for (int i = numDestChannels; --i >= 0;) if (destSamples[i] != 0) zeromem (destSamples[i] + startOffsetInDestBuffer, sizeof (int) * numSamples); } return true; } juce_UseDebuggingNewOperator }; class WavAudioFormatWriter : public AudioFormatWriter { MemoryBlock tempBlock, bwavChunk, smplChunk; uint32 lengthInSamples, bytesWritten; int64 headerPosition; bool writeFailed; static inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); } WavAudioFormatWriter (const WavAudioFormatWriter&); WavAudioFormatWriter& operator= (const WavAudioFormatWriter&); void writeHeader() { const bool seekedOk = output->setPosition (headerPosition); (void) seekedOk; // if this fails, you've given it an output stream that can't seek! It needs // to be able to seek back to write the header jassert (seekedOk); const int bytesPerFrame = numChannels * bitsPerSample / 8; output->writeInt (chunkName ("RIFF")); output->writeInt ((int) (lengthInSamples * bytesPerFrame + ((bwavChunk.getSize() > 0) ? (44 + bwavChunk.getSize()) : 36))); output->writeInt (chunkName ("WAVE")); output->writeInt (chunkName ("fmt ")); output->writeInt (16); output->writeShort ((bitsPerSample < 32) ? (short) 1 /*WAVE_FORMAT_PCM*/ : (short) 3 /*WAVE_FORMAT_IEEE_FLOAT*/); output->writeShort ((short) numChannels); output->writeInt ((int) sampleRate); output->writeInt (bytesPerFrame * (int) sampleRate); output->writeShort ((short) bytesPerFrame); output->writeShort ((short) bitsPerSample); if (bwavChunk.getSize() > 0) { output->writeInt (chunkName ("bext")); output->writeInt ((int) bwavChunk.getSize()); output->write (bwavChunk.getData(), (int) bwavChunk.getSize()); } if (smplChunk.getSize() > 0) { output->writeInt (chunkName ("smpl")); output->writeInt ((int) smplChunk.getSize()); output->write (smplChunk.getData(), (int) smplChunk.getSize()); } output->writeInt (chunkName ("data")); output->writeInt (lengthInSamples * bytesPerFrame); usesFloatingPointData = (bitsPerSample == 32); } public: WavAudioFormatWriter (OutputStream* const out, const double sampleRate_, const unsigned int numChannels_, const int bits, const StringPairArray& metadataValues) : AudioFormatWriter (out, TRANS (wavFormatName), sampleRate_, numChannels_, bits), lengthInSamples (0), bytesWritten (0), writeFailed (false) { if (metadataValues.size() > 0) { bwavChunk = BWAVChunk::createFrom (metadataValues); smplChunk = SMPLChunk::createFrom (metadataValues); } headerPosition = out->getPosition(); writeHeader(); } ~WavAudioFormatWriter() { writeHeader(); } bool write (const int** data, int numSamples) { if (writeFailed) return false; const int bytes = numChannels * numSamples * bitsPerSample / 8; tempBlock.ensureSize (bytes, false); char* buffer = static_cast (tempBlock.getData()); const int* left = data[0]; const int* right = data[1]; if (right == 0) right = left; if (bitsPerSample == 16) { short* b = (short*) buffer; if (numChannels > 1) { for (int i = numSamples; --i >= 0;) { *b++ = (short) ByteOrder::swapIfBigEndian ((unsigned short) (*left++ >> 16)); *b++ = (short) ByteOrder::swapIfBigEndian ((unsigned short) (*right++ >> 16)); } } else { for (int i = numSamples; --i >= 0;) { *b++ = (short) ByteOrder::swapIfBigEndian ((unsigned short) (*left++ >> 16)); } } } else if (bitsPerSample == 24) { char* b = buffer; if (numChannels > 1) { for (int i = numSamples; --i >= 0;) { ByteOrder::littleEndian24BitToChars ((*left++) >> 8, b); b += 3; ByteOrder::littleEndian24BitToChars ((*right++) >> 8, b); b += 3; } } else { for (int i = numSamples; --i >= 0;) { ByteOrder::littleEndian24BitToChars ((*left++) >> 8, b); b += 3; } } } else if (bitsPerSample == 32) { unsigned int* b = (unsigned int*) buffer; if (numChannels > 1) { for (int i = numSamples; --i >= 0;) { *b++ = ByteOrder::swapIfBigEndian ((unsigned int) *left++); *b++ = ByteOrder::swapIfBigEndian ((unsigned int) *right++); } } else { for (int i = numSamples; --i >= 0;) { *b++ = ByteOrder::swapIfBigEndian ((unsigned int) *left++); } } } else if (bitsPerSample == 8) { unsigned char* b = (unsigned char*) buffer; if (numChannels > 1) { for (int i = numSamples; --i >= 0;) { *b++ = (unsigned char) (128 + (*left++ >> 24)); *b++ = (unsigned char) (128 + (*right++ >> 24)); } } else { for (int i = numSamples; --i >= 0;) { *b++ = (unsigned char) (128 + (*left++ >> 24)); } } } if (bytesWritten + bytes >= (uint32) 0xfff00000 || ! output->write (buffer, bytes)) { // failed to write to disk, so let's try writing the header. // If it's just run out of disk space, then if it does manage // to write the header, we'll still have a useable file.. writeHeader(); writeFailed = true; return false; } else { bytesWritten += bytes; lengthInSamples += numSamples; return true; } } juce_UseDebuggingNewOperator }; WavAudioFormat::WavAudioFormat() : AudioFormat (TRANS (wavFormatName), StringArray (wavExtensions)) { } WavAudioFormat::~WavAudioFormat() { } const Array WavAudioFormat::getPossibleSampleRates() { const int rates[] = { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000, 0 }; return Array (rates); } const Array WavAudioFormat::getPossibleBitDepths() { const int depths[] = { 8, 16, 24, 32, 0 }; return Array (depths); } bool WavAudioFormat::canDoStereo() { return true; } bool WavAudioFormat::canDoMono() { return true; } AudioFormatReader* WavAudioFormat::createReaderFor (InputStream* sourceStream, const bool deleteStreamIfOpeningFails) { ScopedPointer r (new WavAudioFormatReader (sourceStream)); if (r->sampleRate != 0) return r.release(); if (! deleteStreamIfOpeningFails) r->input = 0; return 0; } AudioFormatWriter* WavAudioFormat::createWriterFor (OutputStream* out, double sampleRate, unsigned int numChannels, int bitsPerSample, const StringPairArray& metadataValues, int /*qualityOptionIndex*/) { if (getPossibleBitDepths().contains (bitsPerSample)) { return new WavAudioFormatWriter (out, sampleRate, numChannels, bitsPerSample, metadataValues); } return 0; } static bool juce_slowCopyOfWavFileWithNewMetadata (const File& file, const StringPairArray& metadata) { TemporaryFile tempFile (file); WavAudioFormat wav; ScopedPointer reader (wav.createReaderFor (file.createInputStream(), true)); if (reader != 0) { ScopedPointer outStream (tempFile.getFile().createOutputStream()); if (outStream != 0) { ScopedPointer writer (wav.createWriterFor (outStream, reader->sampleRate, reader->numChannels, reader->bitsPerSample, metadata, 0)); if (writer != 0) { outStream.release(); bool ok = writer->writeFromAudioReader (*reader, 0, -1); writer = 0; reader = 0; return ok && tempFile.overwriteTargetFileWithTemporary(); } } } return false; } bool WavAudioFormat::replaceMetadataInFile (const File& wavFile, const StringPairArray& newMetadata) { ScopedPointer reader ((WavAudioFormatReader*) createReaderFor (wavFile.createInputStream(), true)); if (reader != 0) { const int64 bwavPos = reader->bwavChunkStart; const int64 bwavSize = reader->bwavSize; reader = 0; if (bwavSize > 0) { MemoryBlock chunk = BWAVChunk::createFrom (newMetadata); if (chunk.getSize() <= (size_t) bwavSize) { // the new one will fit in the space available, so write it directly.. const int64 oldSize = wavFile.getSize(); { ScopedPointer out (wavFile.createOutputStream()); out->setPosition (bwavPos); out->write (chunk.getData(), (int) chunk.getSize()); out->setPosition (oldSize); } jassert (wavFile.getSize() == oldSize); return true; } } } return juce_slowCopyOfWavFileWithNewMetadata (wavFile, newMetadata); } END_JUCE_NAMESPACE /*** End of inlined file: juce_WavAudioFormat.cpp ***/ /*** Start of inlined file: juce_AudioCDReader.cpp ***/ #if JUCE_USE_CDREADER BEGIN_JUCE_NAMESPACE int AudioCDReader::getNumTracks() const { return trackStartSamples.size() - 1; } int AudioCDReader::getPositionOfTrackStart (int trackNum) const { return trackStartSamples [trackNum]; } const Array& AudioCDReader::getTrackOffsets() const { return trackStartSamples; } int AudioCDReader::getCDDBId() { int checksum = 0; const int numTracks = getNumTracks(); for (int i = 0; i < numTracks; ++i) for (int offset = (trackStartSamples.getUnchecked(i) + 88200) / 44100; offset > 0; offset /= 10) checksum += offset % 10; const int length = (trackStartSamples.getLast() - trackStartSamples.getFirst()) / 44100; // CCLLLLTT: checksum, length, tracks return ((checksum & 0xff) << 24) | (length << 8) | numTracks; } END_JUCE_NAMESPACE #endif /*** End of inlined file: juce_AudioCDReader.cpp ***/ /*** Start of inlined file: juce_AudioFormatReaderSource.cpp ***/ BEGIN_JUCE_NAMESPACE AudioFormatReaderSource::AudioFormatReaderSource (AudioFormatReader* const reader_, const bool deleteReaderWhenThisIsDeleted) : reader (reader_), deleteReader (deleteReaderWhenThisIsDeleted), nextPlayPos (0), looping (false) { jassert (reader != 0); } AudioFormatReaderSource::~AudioFormatReaderSource() { releaseResources(); if (deleteReader) delete reader; } void AudioFormatReaderSource::setNextReadPosition (int newPosition) { nextPlayPos = newPosition; } void AudioFormatReaderSource::setLooping (bool shouldLoop) { looping = shouldLoop; } int AudioFormatReaderSource::getNextReadPosition() const { return (looping) ? (nextPlayPos % (int) reader->lengthInSamples) : nextPlayPos; } int AudioFormatReaderSource::getTotalLength() const { return (int) reader->lengthInSamples; } void AudioFormatReaderSource::prepareToPlay (int /*samplesPerBlockExpected*/, double /*sampleRate*/) { } void AudioFormatReaderSource::releaseResources() { } void AudioFormatReaderSource::getNextAudioBlock (const AudioSourceChannelInfo& info) { if (info.numSamples > 0) { const int start = nextPlayPos; if (looping) { const int newStart = start % (int) reader->lengthInSamples; const int newEnd = (start + info.numSamples) % (int) reader->lengthInSamples; if (newEnd > newStart) { info.buffer->readFromAudioReader (reader, info.startSample, newEnd - newStart, newStart, true, true); } else { const int endSamps = (int) reader->lengthInSamples - newStart; info.buffer->readFromAudioReader (reader, info.startSample, endSamps, newStart, true, true); info.buffer->readFromAudioReader (reader, info.startSample + endSamps, newEnd, 0, true, true); } nextPlayPos = newEnd; } else { info.buffer->readFromAudioReader (reader, info.startSample, info.numSamples, start, true, true); nextPlayPos += info.numSamples; } } } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioFormatReaderSource.cpp ***/ /*** Start of inlined file: juce_AudioSourcePlayer.cpp ***/ BEGIN_JUCE_NAMESPACE AudioSourcePlayer::AudioSourcePlayer() : source (0), sampleRate (0), bufferSize (0), tempBuffer (2, 8), lastGain (1.0f), gain (1.0f) { } AudioSourcePlayer::~AudioSourcePlayer() { setSource (0); } void AudioSourcePlayer::setSource (AudioSource* newSource) { if (source != newSource) { AudioSource* const oldSource = source; if (newSource != 0 && bufferSize > 0 && sampleRate > 0) newSource->prepareToPlay (bufferSize, sampleRate); { const ScopedLock sl (readLock); source = newSource; } if (oldSource != 0) oldSource->releaseResources(); } } void AudioSourcePlayer::setGain (const float newGain) throw() { gain = newGain; } void AudioSourcePlayer::audioDeviceIOCallback (const float** inputChannelData, int totalNumInputChannels, float** outputChannelData, int totalNumOutputChannels, int numSamples) { // these should have been prepared by audioDeviceAboutToStart()... jassert (sampleRate > 0 && bufferSize > 0); const ScopedLock sl (readLock); if (source != 0) { AudioSourceChannelInfo info; int i, numActiveChans = 0, numInputs = 0, numOutputs = 0; // messy stuff needed to compact the channels down into an array // of non-zero pointers.. for (i = 0; i < totalNumInputChannels; ++i) { if (inputChannelData[i] != 0) { inputChans [numInputs++] = inputChannelData[i]; if (numInputs >= numElementsInArray (inputChans)) break; } } for (i = 0; i < totalNumOutputChannels; ++i) { if (outputChannelData[i] != 0) { outputChans [numOutputs++] = outputChannelData[i]; if (numOutputs >= numElementsInArray (outputChans)) break; } } if (numInputs > numOutputs) { // if there aren't enough output channels for the number of // inputs, we need to create some temporary extra ones (can't // use the input data in case it gets written to) tempBuffer.setSize (numInputs - numOutputs, numSamples, false, false, true); for (i = 0; i < numOutputs; ++i) { channels[numActiveChans] = outputChans[i]; memcpy (channels[numActiveChans], inputChans[i], sizeof (float) * numSamples); ++numActiveChans; } for (i = numOutputs; i < numInputs; ++i) { channels[numActiveChans] = tempBuffer.getSampleData (i - numOutputs, 0); memcpy (channels[numActiveChans], inputChans[i], sizeof (float) * numSamples); ++numActiveChans; } } else { for (i = 0; i < numInputs; ++i) { channels[numActiveChans] = outputChans[i]; memcpy (channels[numActiveChans], inputChans[i], sizeof (float) * numSamples); ++numActiveChans; } for (i = numInputs; i < numOutputs; ++i) { channels[numActiveChans] = outputChans[i]; zeromem (channels[numActiveChans], sizeof (float) * numSamples); ++numActiveChans; } } AudioSampleBuffer buffer (channels, numActiveChans, numSamples); info.buffer = &buffer; info.startSample = 0; info.numSamples = numSamples; source->getNextAudioBlock (info); for (i = info.buffer->getNumChannels(); --i >= 0;) info.buffer->applyGainRamp (i, info.startSample, info.numSamples, lastGain, gain); lastGain = gain; } else { for (int i = 0; i < totalNumOutputChannels; ++i) if (outputChannelData[i] != 0) zeromem (outputChannelData[i], sizeof (float) * numSamples); } } void AudioSourcePlayer::audioDeviceAboutToStart (AudioIODevice* device) { sampleRate = device->getCurrentSampleRate(); bufferSize = device->getCurrentBufferSizeSamples(); zeromem (channels, sizeof (channels)); if (source != 0) source->prepareToPlay (bufferSize, sampleRate); } void AudioSourcePlayer::audioDeviceStopped() { if (source != 0) source->releaseResources(); sampleRate = 0.0; bufferSize = 0; tempBuffer.setSize (2, 8); } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioSourcePlayer.cpp ***/ /*** Start of inlined file: juce_AudioTransportSource.cpp ***/ BEGIN_JUCE_NAMESPACE AudioTransportSource::AudioTransportSource() : source (0), resamplerSource (0), bufferingSource (0), positionableSource (0), masterSource (0), gain (1.0f), lastGain (1.0f), playing (false), stopped (true), sampleRate (44100.0), sourceSampleRate (0.0), blockSize (128), readAheadBufferSize (0), isPrepared (false), inputStreamEOF (false) { } AudioTransportSource::~AudioTransportSource() { setSource (0); releaseResources(); } void AudioTransportSource::setSource (PositionableAudioSource* const newSource, int readAheadBufferSize_, double sourceSampleRateToCorrectFor) { if (source == newSource) { if (source == 0) return; setSource (0, 0, 0); // deselect and reselect to avoid releasing resources wrongly } readAheadBufferSize = readAheadBufferSize_; sourceSampleRate = sourceSampleRateToCorrectFor; ResamplingAudioSource* newResamplerSource = 0; BufferingAudioSource* newBufferingSource = 0; PositionableAudioSource* newPositionableSource = 0; AudioSource* newMasterSource = 0; ScopedPointer oldResamplerSource (resamplerSource); ScopedPointer oldBufferingSource (bufferingSource); AudioSource* oldMasterSource = masterSource; if (newSource != 0) { newPositionableSource = newSource; if (readAheadBufferSize_ > 0) newPositionableSource = newBufferingSource = new BufferingAudioSource (newPositionableSource, false, readAheadBufferSize_); newPositionableSource->setNextReadPosition (0); if (sourceSampleRateToCorrectFor != 0) newMasterSource = newResamplerSource = new ResamplingAudioSource (newPositionableSource, false); else newMasterSource = newPositionableSource; if (isPrepared) { if (newResamplerSource != 0 && sourceSampleRate > 0 && sampleRate > 0) newResamplerSource->setResamplingRatio (sourceSampleRate / sampleRate); newMasterSource->prepareToPlay (blockSize, sampleRate); } } { const ScopedLock sl (callbackLock); source = newSource; resamplerSource = newResamplerSource; bufferingSource = newBufferingSource; masterSource = newMasterSource; positionableSource = newPositionableSource; playing = false; } if (oldMasterSource != 0) oldMasterSource->releaseResources(); } void AudioTransportSource::start() { if ((! playing) && masterSource != 0) { { const ScopedLock sl (callbackLock); playing = true; stopped = false; inputStreamEOF = false; } sendChangeMessage (this); } } void AudioTransportSource::stop() { if (playing) { { const ScopedLock sl (callbackLock); playing = false; } int n = 500; while (--n >= 0 && ! stopped) Thread::sleep (2); sendChangeMessage (this); } } void AudioTransportSource::setPosition (double newPosition) { if (sampleRate > 0.0) setNextReadPosition (roundToInt (newPosition * sampleRate)); } double AudioTransportSource::getCurrentPosition() const { if (sampleRate > 0.0) return getNextReadPosition() / sampleRate; else return 0.0; } void AudioTransportSource::setNextReadPosition (int newPosition) { if (positionableSource != 0) { if (sampleRate > 0 && sourceSampleRate > 0) newPosition = roundToInt (newPosition * sourceSampleRate / sampleRate); positionableSource->setNextReadPosition (newPosition); } } int AudioTransportSource::getNextReadPosition() const { if (positionableSource != 0) { const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0; return roundToInt (positionableSource->getNextReadPosition() * ratio); } return 0; } int AudioTransportSource::getTotalLength() const { const ScopedLock sl (callbackLock); if (positionableSource != 0) { const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0; return roundToInt (positionableSource->getTotalLength() * ratio); } return 0; } bool AudioTransportSource::isLooping() const { const ScopedLock sl (callbackLock); return positionableSource != 0 && positionableSource->isLooping(); } void AudioTransportSource::setGain (const float newGain) throw() { gain = newGain; } void AudioTransportSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate_) { const ScopedLock sl (callbackLock); sampleRate = sampleRate_; blockSize = samplesPerBlockExpected; if (masterSource != 0) masterSource->prepareToPlay (samplesPerBlockExpected, sampleRate); if (resamplerSource != 0 && sourceSampleRate != 0) resamplerSource->setResamplingRatio (sourceSampleRate / sampleRate); isPrepared = true; } void AudioTransportSource::releaseResources() { const ScopedLock sl (callbackLock); if (masterSource != 0) masterSource->releaseResources(); isPrepared = false; } void AudioTransportSource::getNextAudioBlock (const AudioSourceChannelInfo& info) { const ScopedLock sl (callbackLock); inputStreamEOF = false; if (masterSource != 0 && ! stopped) { masterSource->getNextAudioBlock (info); if (! playing) { // just stopped playing, so fade out the last block.. for (int i = info.buffer->getNumChannels(); --i >= 0;) info.buffer->applyGainRamp (i, info.startSample, jmin (256, info.numSamples), 1.0f, 0.0f); if (info.numSamples > 256) info.buffer->clear (info.startSample + 256, info.numSamples - 256); } if (positionableSource->getNextReadPosition() > positionableSource->getTotalLength() + 1 && ! positionableSource->isLooping()) { playing = false; inputStreamEOF = true; sendChangeMessage (this); } stopped = ! playing; for (int i = info.buffer->getNumChannels(); --i >= 0;) { info.buffer->applyGainRamp (i, info.startSample, info.numSamples, lastGain, gain); } } else { info.clearActiveBufferRegion(); stopped = true; } lastGain = gain; } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioTransportSource.cpp ***/ /*** Start of inlined file: juce_BufferingAudioSource.cpp ***/ BEGIN_JUCE_NAMESPACE class SharedBufferingAudioSourceThread : public DeletedAtShutdown, public Thread, private Timer { public: SharedBufferingAudioSourceThread() : Thread ("Audio Buffer") { } ~SharedBufferingAudioSourceThread() { stopThread (10000); clearSingletonInstance(); } juce_DeclareSingleton (SharedBufferingAudioSourceThread, false) void addSource (BufferingAudioSource* source) { const ScopedLock sl (lock); if (! sources.contains (source)) { sources.add (source); startThread(); stopTimer(); } notify(); } void removeSource (BufferingAudioSource* source) { const ScopedLock sl (lock); sources.removeValue (source); if (sources.size() == 0) startTimer (5000); } private: Array sources; CriticalSection lock; void run() { while (! threadShouldExit()) { bool busy = false; for (int i = sources.size(); --i >= 0;) { if (threadShouldExit()) return; const ScopedLock sl (lock); BufferingAudioSource* const b = sources[i]; if (b != 0 && b->readNextBufferChunk()) busy = true; } if (! busy) wait (500); } } void timerCallback() { stopTimer(); if (sources.size() == 0) deleteInstance(); } SharedBufferingAudioSourceThread (const SharedBufferingAudioSourceThread&); SharedBufferingAudioSourceThread& operator= (const SharedBufferingAudioSourceThread&); }; juce_ImplementSingleton (SharedBufferingAudioSourceThread) BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* source_, const bool deleteSourceWhenDeleted_, int numberOfSamplesToBuffer_) : source (source_), deleteSourceWhenDeleted (deleteSourceWhenDeleted_), numberOfSamplesToBuffer (jmax (1024, numberOfSamplesToBuffer_)), buffer (2, 0), bufferValidStart (0), bufferValidEnd (0), nextPlayPos (0), wasSourceLooping (false) { jassert (source_ != 0); jassert (numberOfSamplesToBuffer_ > 1024); // not much point using this class if you're // not using a larger buffer.. } BufferingAudioSource::~BufferingAudioSource() { SharedBufferingAudioSourceThread* const thread = SharedBufferingAudioSourceThread::getInstanceWithoutCreating(); if (thread != 0) thread->removeSource (this); if (deleteSourceWhenDeleted) delete source; } void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate_) { source->prepareToPlay (samplesPerBlockExpected, sampleRate_); sampleRate = sampleRate_; buffer.setSize (2, jmax (samplesPerBlockExpected * 2, numberOfSamplesToBuffer)); buffer.clear(); bufferValidStart = 0; bufferValidEnd = 0; SharedBufferingAudioSourceThread::getInstance()->addSource (this); while (bufferValidEnd - bufferValidStart < jmin (((int) sampleRate_) / 4, buffer.getNumSamples() / 2)) { SharedBufferingAudioSourceThread::getInstance()->notify(); Thread::sleep (5); } } void BufferingAudioSource::releaseResources() { SharedBufferingAudioSourceThread* const thread = SharedBufferingAudioSourceThread::getInstanceWithoutCreating(); if (thread != 0) thread->removeSource (this); buffer.setSize (2, 0); source->releaseResources(); } void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) { const ScopedLock sl (bufferStartPosLock); const int validStart = jlimit (bufferValidStart, bufferValidEnd, nextPlayPos) - nextPlayPos; const int validEnd = jlimit (bufferValidStart, bufferValidEnd, nextPlayPos + info.numSamples) - nextPlayPos; if (validStart == validEnd) { // total cache miss info.clearActiveBufferRegion(); } else { if (validStart > 0) info.buffer->clear (info.startSample, validStart); // partial cache miss at start if (validEnd < info.numSamples) info.buffer->clear (info.startSample + validEnd, info.numSamples - validEnd); // partial cache miss at end if (validStart < validEnd) { for (int chan = jmin (2, info.buffer->getNumChannels()); --chan >= 0;) { const int startBufferIndex = (validStart + nextPlayPos) % buffer.getNumSamples(); const int endBufferIndex = (validEnd + nextPlayPos) % buffer.getNumSamples(); if (startBufferIndex < endBufferIndex) { info.buffer->copyFrom (chan, info.startSample + validStart, buffer, chan, startBufferIndex, validEnd - validStart); } else { const int initialSize = buffer.getNumSamples() - startBufferIndex; info.buffer->copyFrom (chan, info.startSample + validStart, buffer, chan, startBufferIndex, initialSize); info.buffer->copyFrom (chan, info.startSample + validStart + initialSize, buffer, chan, 0, (validEnd - validStart) - initialSize); } } } nextPlayPos += info.numSamples; if (source->isLooping() && nextPlayPos > 0) nextPlayPos %= source->getTotalLength(); } SharedBufferingAudioSourceThread* const thread = SharedBufferingAudioSourceThread::getInstanceWithoutCreating(); if (thread != 0) thread->notify(); } int BufferingAudioSource::getNextReadPosition() const { return (source->isLooping() && nextPlayPos > 0) ? nextPlayPos % source->getTotalLength() : nextPlayPos; } void BufferingAudioSource::setNextReadPosition (int newPosition) { const ScopedLock sl (bufferStartPosLock); nextPlayPos = newPosition; SharedBufferingAudioSourceThread* const thread = SharedBufferingAudioSourceThread::getInstanceWithoutCreating(); if (thread != 0) thread->notify(); } bool BufferingAudioSource::readNextBufferChunk() { int newBVS, newBVE, sectionToReadStart, sectionToReadEnd; { const ScopedLock sl (bufferStartPosLock); if (wasSourceLooping != isLooping()) { wasSourceLooping = isLooping(); bufferValidStart = 0; bufferValidEnd = 0; } newBVS = jmax (0, nextPlayPos); newBVE = newBVS + buffer.getNumSamples() - 4; sectionToReadStart = 0; sectionToReadEnd = 0; const int maxChunkSize = 2048; if (newBVS < bufferValidStart || newBVS >= bufferValidEnd) { newBVE = jmin (newBVE, newBVS + maxChunkSize); sectionToReadStart = newBVS; sectionToReadEnd = newBVE; bufferValidStart = 0; bufferValidEnd = 0; } else if (abs (newBVS - bufferValidStart) > 512 || abs (newBVE - bufferValidEnd) > 512) { newBVE = jmin (newBVE, bufferValidEnd + maxChunkSize); sectionToReadStart = bufferValidEnd; sectionToReadEnd = newBVE; bufferValidStart = newBVS; bufferValidEnd = jmin (bufferValidEnd, newBVE); } } if (sectionToReadStart != sectionToReadEnd) { const int bufferIndexStart = sectionToReadStart % buffer.getNumSamples(); const int bufferIndexEnd = sectionToReadEnd % buffer.getNumSamples(); if (bufferIndexStart < bufferIndexEnd) { readBufferSection (sectionToReadStart, sectionToReadEnd - sectionToReadStart, bufferIndexStart); } else { const int initialSize = buffer.getNumSamples() - bufferIndexStart; readBufferSection (sectionToReadStart, initialSize, bufferIndexStart); readBufferSection (sectionToReadStart + initialSize, (sectionToReadEnd - sectionToReadStart) - initialSize, 0); } const ScopedLock sl2 (bufferStartPosLock); bufferValidStart = newBVS; bufferValidEnd = newBVE; return true; } else { return false; } } void BufferingAudioSource::readBufferSection (int start, int length, int bufferOffset) { if (source->getNextReadPosition() != start) source->setNextReadPosition (start); AudioSourceChannelInfo info; info.buffer = &buffer; info.startSample = bufferOffset; info.numSamples = length; source->getNextAudioBlock (info); } END_JUCE_NAMESPACE /*** End of inlined file: juce_BufferingAudioSource.cpp ***/ /*** Start of inlined file: juce_ChannelRemappingAudioSource.cpp ***/ BEGIN_JUCE_NAMESPACE ChannelRemappingAudioSource::ChannelRemappingAudioSource (AudioSource* const source_, const bool deleteSourceWhenDeleted_) : requiredNumberOfChannels (2), source (source_), deleteSourceWhenDeleted (deleteSourceWhenDeleted_), buffer (2, 16) { remappedInfo.buffer = &buffer; remappedInfo.startSample = 0; } ChannelRemappingAudioSource::~ChannelRemappingAudioSource() { if (deleteSourceWhenDeleted) delete source; } void ChannelRemappingAudioSource::setNumberOfChannelsToProduce (const int requiredNumberOfChannels_) { const ScopedLock sl (lock); requiredNumberOfChannels = requiredNumberOfChannels_; } void ChannelRemappingAudioSource::clearAllMappings() { const ScopedLock sl (lock); remappedInputs.clear(); remappedOutputs.clear(); } void ChannelRemappingAudioSource::setInputChannelMapping (const int destIndex, const int sourceIndex) { const ScopedLock sl (lock); while (remappedInputs.size() < destIndex) remappedInputs.add (-1); remappedInputs.set (destIndex, sourceIndex); } void ChannelRemappingAudioSource::setOutputChannelMapping (const int sourceIndex, const int destIndex) { const ScopedLock sl (lock); while (remappedOutputs.size() < sourceIndex) remappedOutputs.add (-1); remappedOutputs.set (sourceIndex, destIndex); } int ChannelRemappingAudioSource::getRemappedInputChannel (const int inputChannelIndex) const { const ScopedLock sl (lock); if (inputChannelIndex >= 0 && inputChannelIndex < remappedInputs.size()) return remappedInputs.getUnchecked (inputChannelIndex); return -1; } int ChannelRemappingAudioSource::getRemappedOutputChannel (const int outputChannelIndex) const { const ScopedLock sl (lock); if (outputChannelIndex >= 0 && outputChannelIndex < remappedOutputs.size()) return remappedOutputs .getUnchecked (outputChannelIndex); return -1; } void ChannelRemappingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) { source->prepareToPlay (samplesPerBlockExpected, sampleRate); } void ChannelRemappingAudioSource::releaseResources() { source->releaseResources(); } void ChannelRemappingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) { const ScopedLock sl (lock); buffer.setSize (requiredNumberOfChannels, bufferToFill.numSamples, false, false, true); const int numChans = bufferToFill.buffer->getNumChannels(); int i; for (i = 0; i < buffer.getNumChannels(); ++i) { const int remappedChan = getRemappedInputChannel (i); if (remappedChan >= 0 && remappedChan < numChans) { buffer.copyFrom (i, 0, *bufferToFill.buffer, remappedChan, bufferToFill.startSample, bufferToFill.numSamples); } else { buffer.clear (i, 0, bufferToFill.numSamples); } } remappedInfo.numSamples = bufferToFill.numSamples; source->getNextAudioBlock (remappedInfo); bufferToFill.clearActiveBufferRegion(); for (i = 0; i < requiredNumberOfChannels; ++i) { const int remappedChan = getRemappedOutputChannel (i); if (remappedChan >= 0 && remappedChan < numChans) { bufferToFill.buffer->addFrom (remappedChan, bufferToFill.startSample, buffer, i, 0, bufferToFill.numSamples); } } } XmlElement* ChannelRemappingAudioSource::createXml() const { XmlElement* e = new XmlElement ("MAPPINGS"); String ins, outs; int i; const ScopedLock sl (lock); for (i = 0; i < remappedInputs.size(); ++i) ins << remappedInputs.getUnchecked(i) << ' '; for (i = 0; i < remappedOutputs.size(); ++i) outs << remappedOutputs.getUnchecked(i) << ' '; e->setAttribute ("inputs", ins.trimEnd()); e->setAttribute ("outputs", outs.trimEnd()); return e; } void ChannelRemappingAudioSource::restoreFromXml (const XmlElement& e) { if (e.hasTagName ("MAPPINGS")) { const ScopedLock sl (lock); clearAllMappings(); StringArray ins, outs; ins.addTokens (e.getStringAttribute ("inputs"), false); outs.addTokens (e.getStringAttribute ("outputs"), false); int i; for (i = 0; i < ins.size(); ++i) remappedInputs.add (ins[i].getIntValue()); for (i = 0; i < outs.size(); ++i) remappedOutputs.add (outs[i].getIntValue()); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ChannelRemappingAudioSource.cpp ***/ /*** Start of inlined file: juce_IIRFilterAudioSource.cpp ***/ BEGIN_JUCE_NAMESPACE IIRFilterAudioSource::IIRFilterAudioSource (AudioSource* const inputSource, const bool deleteInputWhenDeleted_) : input (inputSource), deleteInputWhenDeleted (deleteInputWhenDeleted_) { jassert (inputSource != 0); for (int i = 2; --i >= 0;) iirFilters.add (new IIRFilter()); } IIRFilterAudioSource::~IIRFilterAudioSource() { if (deleteInputWhenDeleted) delete input; } void IIRFilterAudioSource::setFilterParameters (const IIRFilter& newSettings) { for (int i = iirFilters.size(); --i >= 0;) iirFilters.getUnchecked(i)->copyCoefficientsFrom (newSettings); } void IIRFilterAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) { input->prepareToPlay (samplesPerBlockExpected, sampleRate); for (int i = iirFilters.size(); --i >= 0;) iirFilters.getUnchecked(i)->reset(); } void IIRFilterAudioSource::releaseResources() { input->releaseResources(); } void IIRFilterAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) { input->getNextAudioBlock (bufferToFill); const int numChannels = bufferToFill.buffer->getNumChannels(); while (numChannels > iirFilters.size()) iirFilters.add (new IIRFilter (*iirFilters.getUnchecked (0))); for (int i = 0; i < numChannels; ++i) iirFilters.getUnchecked(i) ->processSamples (bufferToFill.buffer->getSampleData (i, bufferToFill.startSample), bufferToFill.numSamples); } END_JUCE_NAMESPACE /*** End of inlined file: juce_IIRFilterAudioSource.cpp ***/ /*** Start of inlined file: juce_MixerAudioSource.cpp ***/ BEGIN_JUCE_NAMESPACE MixerAudioSource::MixerAudioSource() : tempBuffer (2, 0), currentSampleRate (0.0), bufferSizeExpected (0) { } MixerAudioSource::~MixerAudioSource() { removeAllInputs(); } void MixerAudioSource::addInputSource (AudioSource* input, const bool deleteWhenRemoved) { if (input != 0 && ! inputs.contains (input)) { double localRate; int localBufferSize; { const ScopedLock sl (lock); localRate = currentSampleRate; localBufferSize = bufferSizeExpected; } if (localRate != 0.0) input->prepareToPlay (localBufferSize, localRate); const ScopedLock sl (lock); inputsToDelete.setBit (inputs.size(), deleteWhenRemoved); inputs.add (input); } } void MixerAudioSource::removeInputSource (AudioSource* input, const bool deleteInput) { if (input != 0) { int index; { const ScopedLock sl (lock); index = inputs.indexOf (input); if (index >= 0) { inputsToDelete.shiftBits (index, 1); inputs.remove (index); } } if (index >= 0) { input->releaseResources(); if (deleteInput) delete input; } } } void MixerAudioSource::removeAllInputs() { OwnedArray toDelete; { const ScopedLock sl (lock); for (int i = inputs.size(); --i >= 0;) if (inputsToDelete[i]) toDelete.add (inputs.getUnchecked(i)); } } void MixerAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) { tempBuffer.setSize (2, samplesPerBlockExpected); const ScopedLock sl (lock); currentSampleRate = sampleRate; bufferSizeExpected = samplesPerBlockExpected; for (int i = inputs.size(); --i >= 0;) inputs.getUnchecked(i)->prepareToPlay (samplesPerBlockExpected, sampleRate); } void MixerAudioSource::releaseResources() { const ScopedLock sl (lock); for (int i = inputs.size(); --i >= 0;) inputs.getUnchecked(i)->releaseResources(); tempBuffer.setSize (2, 0); currentSampleRate = 0; bufferSizeExpected = 0; } void MixerAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) { const ScopedLock sl (lock); if (inputs.size() > 0) { inputs.getUnchecked(0)->getNextAudioBlock (info); if (inputs.size() > 1) { tempBuffer.setSize (jmax (1, info.buffer->getNumChannels()), info.buffer->getNumSamples()); AudioSourceChannelInfo info2; info2.buffer = &tempBuffer; info2.numSamples = info.numSamples; info2.startSample = 0; for (int i = 1; i < inputs.size(); ++i) { inputs.getUnchecked(i)->getNextAudioBlock (info2); for (int chan = 0; chan < info.buffer->getNumChannels(); ++chan) info.buffer->addFrom (chan, info.startSample, tempBuffer, chan, 0, info.numSamples); } } } else { info.clearActiveBufferRegion(); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_MixerAudioSource.cpp ***/ /*** Start of inlined file: juce_ResamplingAudioSource.cpp ***/ BEGIN_JUCE_NAMESPACE ResamplingAudioSource::ResamplingAudioSource (AudioSource* const inputSource, const bool deleteInputWhenDeleted_, const int numChannels_) : input (inputSource), deleteInputWhenDeleted (deleteInputWhenDeleted_), ratio (1.0), lastRatio (1.0), buffer (numChannels_, 0), sampsInBuffer (0), numChannels (numChannels_) { jassert (input != 0); } ResamplingAudioSource::~ResamplingAudioSource() { if (deleteInputWhenDeleted) delete input; } void ResamplingAudioSource::setResamplingRatio (const double samplesInPerOutputSample) { jassert (samplesInPerOutputSample > 0); const ScopedLock sl (ratioLock); ratio = jmax (0.0, samplesInPerOutputSample); } void ResamplingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) { const ScopedLock sl (ratioLock); input->prepareToPlay (samplesPerBlockExpected, sampleRate); buffer.setSize (numChannels, roundToInt (samplesPerBlockExpected * ratio) + 32); buffer.clear(); sampsInBuffer = 0; bufferPos = 0; subSampleOffset = 0.0; filterStates.calloc (numChannels); srcBuffers.calloc (numChannels); destBuffers.calloc (numChannels); createLowPass (ratio); resetFilters(); } void ResamplingAudioSource::releaseResources() { input->releaseResources(); buffer.setSize (numChannels, 0); } void ResamplingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) { const ScopedLock sl (ratioLock); if (lastRatio != ratio) { createLowPass (ratio); lastRatio = ratio; } const int sampsNeeded = roundToInt (info.numSamples * ratio) + 2; int bufferSize = buffer.getNumSamples(); if (bufferSize < sampsNeeded + 8) { bufferPos %= bufferSize; bufferSize = sampsNeeded + 32; buffer.setSize (buffer.getNumChannels(), bufferSize, true, true); } bufferPos %= bufferSize; int endOfBufferPos = bufferPos + sampsInBuffer; const int channelsToProcess = jmin (numChannels, info.buffer->getNumChannels()); while (sampsNeeded > sampsInBuffer) { endOfBufferPos %= bufferSize; int numToDo = jmin (sampsNeeded - sampsInBuffer, bufferSize - endOfBufferPos); AudioSourceChannelInfo readInfo; readInfo.buffer = &buffer; readInfo.numSamples = numToDo; readInfo.startSample = endOfBufferPos; input->getNextAudioBlock (readInfo); if (ratio > 1.0001) { // for down-sampling, pre-apply the filter.. for (int i = channelsToProcess; --i >= 0;) applyFilter (buffer.getSampleData (i, endOfBufferPos), numToDo, filterStates[i]); } sampsInBuffer += numToDo; endOfBufferPos += numToDo; } for (int channel = 0; channel < channelsToProcess; ++channel) { destBuffers[channel] = info.buffer->getSampleData (channel, info.startSample); srcBuffers[channel] = buffer.getSampleData (channel, 0); } int nextPos = (bufferPos + 1) % bufferSize; for (int m = info.numSamples; --m >= 0;) { const float alpha = (float) subSampleOffset; const float invAlpha = 1.0f - alpha; for (int channel = 0; channel < channelsToProcess; ++channel) *destBuffers[channel]++ = srcBuffers[channel][bufferPos] * invAlpha + srcBuffers[channel][nextPos] * alpha; subSampleOffset += ratio; jassert (sampsInBuffer > 0); while (subSampleOffset >= 1.0) { if (++bufferPos >= bufferSize) bufferPos = 0; --sampsInBuffer; nextPos = (bufferPos + 1) % bufferSize; subSampleOffset -= 1.0; } } if (ratio < 0.9999) { // for up-sampling, apply the filter after transposing.. for (int i = channelsToProcess; --i >= 0;) applyFilter (info.buffer->getSampleData (i, info.startSample), info.numSamples, filterStates[i]); } else if (ratio <= 1.0001) { // if the filter's not currently being applied, keep it stoked with the last couple of samples to avoid discontinuities for (int i = channelsToProcess; --i >= 0;) { const float* const endOfBuffer = info.buffer->getSampleData (i, info.startSample + info.numSamples - 1); FilterState& fs = filterStates[i]; if (info.numSamples > 1) { fs.y2 = fs.x2 = *(endOfBuffer - 1); } else { fs.y2 = fs.y1; fs.x2 = fs.x1; } fs.y1 = fs.x1 = *endOfBuffer; } } jassert (sampsInBuffer >= 0); } void ResamplingAudioSource::createLowPass (const double frequencyRatio) { const double proportionalRate = (frequencyRatio > 1.0) ? 0.5 / frequencyRatio : 0.5 * frequencyRatio; const double n = 1.0 / tan (double_Pi * jmax (0.001, proportionalRate)); const double nSquared = n * n; const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); setFilterCoefficients (c1, c1 * 2.0f, c1, 1.0, c1 * 2.0 * (1.0 - nSquared), c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); } void ResamplingAudioSource::setFilterCoefficients (double c1, double c2, double c3, double c4, double c5, double c6) { const double a = 1.0 / c4; c1 *= a; c2 *= a; c3 *= a; c5 *= a; c6 *= a; coefficients[0] = c1; coefficients[1] = c2; coefficients[2] = c3; coefficients[3] = c4; coefficients[4] = c5; coefficients[5] = c6; } void ResamplingAudioSource::resetFilters() { zeromem (filterStates, sizeof (FilterState) * numChannels); } void ResamplingAudioSource::applyFilter (float* samples, int num, FilterState& fs) { while (--num >= 0) { const double in = *samples; double out = coefficients[0] * in + coefficients[1] * fs.x1 + coefficients[2] * fs.x2 - coefficients[4] * fs.y1 - coefficients[5] * fs.y2; #if JUCE_INTEL if (! (out < -1.0e-8 || out > 1.0e-8)) out = 0; #endif fs.x2 = fs.x1; fs.x1 = in; fs.y2 = fs.y1; fs.y1 = out; *samples++ = (float) out; } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ResamplingAudioSource.cpp ***/ /*** Start of inlined file: juce_ToneGeneratorAudioSource.cpp ***/ BEGIN_JUCE_NAMESPACE ToneGeneratorAudioSource::ToneGeneratorAudioSource() : frequency (1000.0), sampleRate (44100.0), currentPhase (0.0), phasePerSample (0.0), amplitude (0.5f) { } ToneGeneratorAudioSource::~ToneGeneratorAudioSource() { } void ToneGeneratorAudioSource::setAmplitude (const float newAmplitude) { amplitude = newAmplitude; } void ToneGeneratorAudioSource::setFrequency (const double newFrequencyHz) { frequency = newFrequencyHz; phasePerSample = 0.0; } void ToneGeneratorAudioSource::prepareToPlay (int /*samplesPerBlockExpected*/, double sampleRate_) { currentPhase = 0.0; phasePerSample = 0.0; sampleRate = sampleRate_; } void ToneGeneratorAudioSource::releaseResources() { } void ToneGeneratorAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) { if (phasePerSample == 0.0) phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); for (int i = 0; i < info.numSamples; ++i) { const float sample = amplitude * (float) std::sin (currentPhase); currentPhase += phasePerSample; for (int j = info.buffer->getNumChannels(); --j >= 0;) *info.buffer->getSampleData (j, info.startSample + i) = sample; } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ToneGeneratorAudioSource.cpp ***/ /*** Start of inlined file: juce_AudioDeviceManager.cpp ***/ BEGIN_JUCE_NAMESPACE AudioDeviceManager::AudioDeviceSetup::AudioDeviceSetup() : sampleRate (0), bufferSize (0), useDefaultInputChannels (true), useDefaultOutputChannels (true) { } bool AudioDeviceManager::AudioDeviceSetup::operator== (const AudioDeviceManager::AudioDeviceSetup& other) const { return outputDeviceName == other.outputDeviceName && inputDeviceName == other.inputDeviceName && sampleRate == other.sampleRate && bufferSize == other.bufferSize && inputChannels == other.inputChannels && useDefaultInputChannels == other.useDefaultInputChannels && outputChannels == other.outputChannels && useDefaultOutputChannels == other.useDefaultOutputChannels; } AudioDeviceManager::AudioDeviceManager() : currentAudioDevice (0), numInputChansNeeded (0), numOutputChansNeeded (2), listNeedsScanning (true), useInputNames (false), inputLevelMeasurementEnabledCount (0), inputLevel (0), tempBuffer (2, 2), defaultMidiOutput (0), cpuUsageMs (0), timeToCpuScale (0) { callbackHandler.owner = this; } AudioDeviceManager::~AudioDeviceManager() { currentAudioDevice = 0; defaultMidiOutput = 0; } void AudioDeviceManager::createDeviceTypesIfNeeded() { if (availableDeviceTypes.size() == 0) { createAudioDeviceTypes (availableDeviceTypes); while (lastDeviceTypeConfigs.size() < availableDeviceTypes.size()) lastDeviceTypeConfigs.add (new AudioDeviceSetup()); if (availableDeviceTypes.size() > 0) currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); } } const OwnedArray & AudioDeviceManager::getAvailableDeviceTypes() { scanDevicesIfNeeded(); return availableDeviceTypes; } AudioIODeviceType* juce_createAudioIODeviceType_CoreAudio(); AudioIODeviceType* juce_createAudioIODeviceType_iPhoneAudio(); AudioIODeviceType* juce_createAudioIODeviceType_WASAPI(); AudioIODeviceType* juce_createAudioIODeviceType_DirectSound(); AudioIODeviceType* juce_createAudioIODeviceType_ASIO(); AudioIODeviceType* juce_createAudioIODeviceType_ALSA(); AudioIODeviceType* juce_createAudioIODeviceType_JACK(); void AudioDeviceManager::createAudioDeviceTypes (OwnedArray & list) { (void) list; // (to avoid 'unused param' warnings) #if JUCE_WINDOWS #if JUCE_WASAPI if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista) list.add (juce_createAudioIODeviceType_WASAPI()); #endif #if JUCE_DIRECTSOUND list.add (juce_createAudioIODeviceType_DirectSound()); #endif #if JUCE_ASIO list.add (juce_createAudioIODeviceType_ASIO()); #endif #endif #if JUCE_MAC list.add (juce_createAudioIODeviceType_CoreAudio()); #endif #if JUCE_IOS list.add (juce_createAudioIODeviceType_iPhoneAudio()); #endif #if JUCE_LINUX && JUCE_ALSA list.add (juce_createAudioIODeviceType_ALSA()); #endif #if JUCE_LINUX && JUCE_JACK list.add (juce_createAudioIODeviceType_JACK()); #endif } const String AudioDeviceManager::initialise (const int numInputChannelsNeeded, const int numOutputChannelsNeeded, const XmlElement* const e, const bool selectDefaultDeviceOnFailure, const String& preferredDefaultDeviceName, const AudioDeviceSetup* preferredSetupOptions) { scanDevicesIfNeeded(); numInputChansNeeded = numInputChannelsNeeded; numOutputChansNeeded = numOutputChannelsNeeded; if (e != 0 && e->hasTagName ("DEVICESETUP")) { lastExplicitSettings = new XmlElement (*e); String error; AudioDeviceSetup setup; if (preferredSetupOptions != 0) setup = *preferredSetupOptions; if (e->getStringAttribute ("audioDeviceName").isNotEmpty()) { setup.inputDeviceName = setup.outputDeviceName = e->getStringAttribute ("audioDeviceName"); } else { setup.inputDeviceName = e->getStringAttribute ("audioInputDeviceName"); setup.outputDeviceName = e->getStringAttribute ("audioOutputDeviceName"); } currentDeviceType = e->getStringAttribute ("deviceType"); if (currentDeviceType.isEmpty()) { AudioIODeviceType* const type = findType (setup.inputDeviceName, setup.outputDeviceName); if (type != 0) currentDeviceType = type->getTypeName(); else if (availableDeviceTypes.size() > 0) currentDeviceType = availableDeviceTypes[0]->getTypeName(); } setup.bufferSize = e->getIntAttribute ("audioDeviceBufferSize"); setup.sampleRate = e->getDoubleAttribute ("audioDeviceRate"); setup.inputChannels.parseString (e->getStringAttribute ("audioDeviceInChans", "11"), 2); setup.outputChannels.parseString (e->getStringAttribute ("audioDeviceOutChans", "11"), 2); setup.useDefaultInputChannels = ! e->hasAttribute ("audioDeviceInChans"); setup.useDefaultOutputChannels = ! e->hasAttribute ("audioDeviceOutChans"); error = setAudioDeviceSetup (setup, true); midiInsFromXml.clear(); forEachXmlChildElementWithTagName (*e, c, "MIDIINPUT") midiInsFromXml.add (c->getStringAttribute ("name")); const StringArray allMidiIns (MidiInput::getDevices()); for (int i = allMidiIns.size(); --i >= 0;) setMidiInputEnabled (allMidiIns[i], midiInsFromXml.contains (allMidiIns[i])); if (error.isNotEmpty() && selectDefaultDeviceOnFailure) error = initialise (numInputChannelsNeeded, numOutputChannelsNeeded, 0, false, preferredDefaultDeviceName); setDefaultMidiOutput (e->getStringAttribute ("defaultMidiOutput")); return error; } else { AudioDeviceSetup setup; if (preferredSetupOptions != 0) { setup = *preferredSetupOptions; } else if (preferredDefaultDeviceName.isNotEmpty()) { for (int j = availableDeviceTypes.size(); --j >= 0;) { AudioIODeviceType* const type = availableDeviceTypes.getUnchecked(j); StringArray outs (type->getDeviceNames (false)); int i; for (i = 0; i < outs.size(); ++i) { if (outs[i].matchesWildcard (preferredDefaultDeviceName, true)) { setup.outputDeviceName = outs[i]; break; } } StringArray ins (type->getDeviceNames (true)); for (i = 0; i < ins.size(); ++i) { if (ins[i].matchesWildcard (preferredDefaultDeviceName, true)) { setup.inputDeviceName = ins[i]; break; } } } } insertDefaultDeviceNames (setup); return setAudioDeviceSetup (setup, false); } } void AudioDeviceManager::insertDefaultDeviceNames (AudioDeviceSetup& setup) const { AudioIODeviceType* type = getCurrentDeviceTypeObject(); if (type != 0) { if (setup.outputDeviceName.isEmpty()) setup.outputDeviceName = type->getDeviceNames (false) [type->getDefaultDeviceIndex (false)]; if (setup.inputDeviceName.isEmpty()) setup.inputDeviceName = type->getDeviceNames (true) [type->getDefaultDeviceIndex (true)]; } } XmlElement* AudioDeviceManager::createStateXml() const { return lastExplicitSettings != 0 ? new XmlElement (*lastExplicitSettings) : 0; } void AudioDeviceManager::scanDevicesIfNeeded() { if (listNeedsScanning) { listNeedsScanning = false; createDeviceTypesIfNeeded(); for (int i = availableDeviceTypes.size(); --i >= 0;) availableDeviceTypes.getUnchecked(i)->scanForDevices(); } } AudioIODeviceType* AudioDeviceManager::findType (const String& inputName, const String& outputName) { scanDevicesIfNeeded(); for (int i = availableDeviceTypes.size(); --i >= 0;) { AudioIODeviceType* const type = availableDeviceTypes.getUnchecked(i); if ((inputName.isNotEmpty() && type->getDeviceNames (true).contains (inputName, true)) || (outputName.isNotEmpty() && type->getDeviceNames (false).contains (outputName, true))) { return type; } } return 0; } void AudioDeviceManager::getAudioDeviceSetup (AudioDeviceSetup& setup) { setup = currentSetup; } void AudioDeviceManager::deleteCurrentDevice() { currentAudioDevice = 0; currentSetup.inputDeviceName = String::empty; currentSetup.outputDeviceName = String::empty; } void AudioDeviceManager::setCurrentAudioDeviceType (const String& type, const bool treatAsChosenDevice) { for (int i = 0; i < availableDeviceTypes.size(); ++i) { if (availableDeviceTypes.getUnchecked(i)->getTypeName() == type && currentDeviceType != type) { currentDeviceType = type; AudioDeviceSetup s (*lastDeviceTypeConfigs.getUnchecked(i)); insertDefaultDeviceNames (s); setAudioDeviceSetup (s, treatAsChosenDevice); sendChangeMessage (this); break; } } } AudioIODeviceType* AudioDeviceManager::getCurrentDeviceTypeObject() const { for (int i = 0; i < availableDeviceTypes.size(); ++i) if (availableDeviceTypes[i]->getTypeName() == currentDeviceType) return availableDeviceTypes[i]; return availableDeviceTypes[0]; } const String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& newSetup, const bool treatAsChosenDevice) { jassert (&newSetup != ¤tSetup); // this will have no effect if (newSetup == currentSetup && currentAudioDevice != 0) return String::empty; if (! (newSetup == currentSetup)) sendChangeMessage (this); stopDevice(); const String newInputDeviceName (numInputChansNeeded == 0 ? String::empty : newSetup.inputDeviceName); const String newOutputDeviceName (numOutputChansNeeded == 0 ? String::empty : newSetup.outputDeviceName); String error; AudioIODeviceType* type = getCurrentDeviceTypeObject(); if (type == 0 || (newInputDeviceName.isEmpty() && newOutputDeviceName.isEmpty())) { deleteCurrentDevice(); if (treatAsChosenDevice) updateXml(); return String::empty; } if (currentSetup.inputDeviceName != newInputDeviceName || currentSetup.outputDeviceName != newOutputDeviceName || currentAudioDevice == 0) { deleteCurrentDevice(); scanDevicesIfNeeded(); if (newOutputDeviceName.isNotEmpty() && ! type->getDeviceNames (false).contains (newOutputDeviceName)) { return "No such device: " + newOutputDeviceName; } if (newInputDeviceName.isNotEmpty() && ! type->getDeviceNames (true).contains (newInputDeviceName)) { return "No such device: " + newInputDeviceName; } currentAudioDevice = type->createDevice (newOutputDeviceName, newInputDeviceName); if (currentAudioDevice == 0) error = "Can't open the audio device!\n\nThis may be because another application is currently using the same device - if so, you should close any other applications and try again!"; else error = currentAudioDevice->getLastError(); if (error.isNotEmpty()) { deleteCurrentDevice(); return error; } if (newSetup.useDefaultInputChannels) { inputChannels.clear(); inputChannels.setRange (0, numInputChansNeeded, true); } if (newSetup.useDefaultOutputChannels) { outputChannels.clear(); outputChannels.setRange (0, numOutputChansNeeded, true); } if (newInputDeviceName.isEmpty()) inputChannels.clear(); if (newOutputDeviceName.isEmpty()) outputChannels.clear(); } if (! newSetup.useDefaultInputChannels) inputChannels = newSetup.inputChannels; if (! newSetup.useDefaultOutputChannels) outputChannels = newSetup.outputChannels; currentSetup = newSetup; currentSetup.sampleRate = chooseBestSampleRate (newSetup.sampleRate); error = currentAudioDevice->open (inputChannels, outputChannels, currentSetup.sampleRate, currentSetup.bufferSize); if (error.isEmpty()) { currentDeviceType = currentAudioDevice->getTypeName(); currentAudioDevice->start (&callbackHandler); currentSetup.sampleRate = currentAudioDevice->getCurrentSampleRate(); currentSetup.bufferSize = currentAudioDevice->getCurrentBufferSizeSamples(); currentSetup.inputChannels = currentAudioDevice->getActiveInputChannels(); currentSetup.outputChannels = currentAudioDevice->getActiveOutputChannels(); for (int i = 0; i < availableDeviceTypes.size(); ++i) if (availableDeviceTypes.getUnchecked (i)->getTypeName() == currentDeviceType) *(lastDeviceTypeConfigs.getUnchecked (i)) = currentSetup; if (treatAsChosenDevice) updateXml(); } else { deleteCurrentDevice(); } return error; } double AudioDeviceManager::chooseBestSampleRate (double rate) const { jassert (currentAudioDevice != 0); if (rate > 0) { bool ok = false; for (int i = currentAudioDevice->getNumSampleRates(); --i >= 0;) { const double sr = currentAudioDevice->getSampleRate (i); if (sr == rate) ok = true; } if (! ok) rate = 0; } if (rate == 0) { double lowestAbove44 = 0.0; for (int i = currentAudioDevice->getNumSampleRates(); --i >= 0;) { const double sr = currentAudioDevice->getSampleRate (i); if (sr >= 44100.0 && (lowestAbove44 == 0 || sr < lowestAbove44)) lowestAbove44 = sr; } if (lowestAbove44 == 0.0) rate = currentAudioDevice->getSampleRate (0); else rate = lowestAbove44; } return rate; } void AudioDeviceManager::stopDevice() { if (currentAudioDevice != 0) currentAudioDevice->stop(); testSound = 0; } void AudioDeviceManager::closeAudioDevice() { stopDevice(); currentAudioDevice = 0; } void AudioDeviceManager::restartLastAudioDevice() { if (currentAudioDevice == 0) { if (currentSetup.inputDeviceName.isEmpty() && currentSetup.outputDeviceName.isEmpty()) { // This method will only reload the last device that was running // before closeAudioDevice() was called - you need to actually open // one first, with setAudioDevice(). jassertfalse; return; } AudioDeviceSetup s (currentSetup); setAudioDeviceSetup (s, false); } } void AudioDeviceManager::updateXml() { lastExplicitSettings = new XmlElement ("DEVICESETUP"); lastExplicitSettings->setAttribute ("deviceType", currentDeviceType); lastExplicitSettings->setAttribute ("audioOutputDeviceName", currentSetup.outputDeviceName); lastExplicitSettings->setAttribute ("audioInputDeviceName", currentSetup.inputDeviceName); if (currentAudioDevice != 0) { lastExplicitSettings->setAttribute ("audioDeviceRate", currentAudioDevice->getCurrentSampleRate()); if (currentAudioDevice->getDefaultBufferSize() != currentAudioDevice->getCurrentBufferSizeSamples()) lastExplicitSettings->setAttribute ("audioDeviceBufferSize", currentAudioDevice->getCurrentBufferSizeSamples()); if (! currentSetup.useDefaultInputChannels) lastExplicitSettings->setAttribute ("audioDeviceInChans", currentSetup.inputChannels.toString (2)); if (! currentSetup.useDefaultOutputChannels) lastExplicitSettings->setAttribute ("audioDeviceOutChans", currentSetup.outputChannels.toString (2)); } for (int i = 0; i < enabledMidiInputs.size(); ++i) { XmlElement* const m = lastExplicitSettings->createNewChildElement ("MIDIINPUT"); m->setAttribute ("name", enabledMidiInputs[i]->getName()); } if (midiInsFromXml.size() > 0) { // Add any midi devices that have been enabled before, but which aren't currently // open because the device has been disconnected. const StringArray availableMidiDevices (MidiInput::getDevices()); for (int i = 0; i < midiInsFromXml.size(); ++i) { if (! availableMidiDevices.contains (midiInsFromXml[i], true)) { XmlElement* const m = lastExplicitSettings->createNewChildElement ("MIDIINPUT"); m->setAttribute ("name", midiInsFromXml[i]); } } } if (defaultMidiOutputName.isNotEmpty()) lastExplicitSettings->setAttribute ("defaultMidiOutput", defaultMidiOutputName); } void AudioDeviceManager::addAudioCallback (AudioIODeviceCallback* newCallback) { { const ScopedLock sl (audioCallbackLock); if (callbacks.contains (newCallback)) return; } if (currentAudioDevice != 0 && newCallback != 0) newCallback->audioDeviceAboutToStart (currentAudioDevice); const ScopedLock sl (audioCallbackLock); callbacks.add (newCallback); } void AudioDeviceManager::removeAudioCallback (AudioIODeviceCallback* callback) { if (callback != 0) { bool needsDeinitialising = currentAudioDevice != 0; { const ScopedLock sl (audioCallbackLock); needsDeinitialising = needsDeinitialising && callbacks.contains (callback); callbacks.removeValue (callback); } if (needsDeinitialising) callback->audioDeviceStopped(); } } void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelData, int numInputChannels, float** outputChannelData, int numOutputChannels, int numSamples) { const ScopedLock sl (audioCallbackLock); if (inputLevelMeasurementEnabledCount > 0) { for (int j = 0; j < numSamples; ++j) { float s = 0; for (int i = 0; i < numInputChannels; ++i) s += std::abs (inputChannelData[i][j]); s /= numInputChannels; const double decayFactor = 0.99992; if (s > inputLevel) inputLevel = s; else if (inputLevel > 0.001f) inputLevel *= decayFactor; else inputLevel = 0; } } if (callbacks.size() > 0) { const double callbackStartTime = Time::getMillisecondCounterHiRes(); tempBuffer.setSize (jmax (1, numOutputChannels), jmax (1, numSamples), false, false, true); callbacks.getUnchecked(0)->audioDeviceIOCallback (inputChannelData, numInputChannels, outputChannelData, numOutputChannels, numSamples); float** const tempChans = tempBuffer.getArrayOfChannels(); for (int i = callbacks.size(); --i > 0;) { callbacks.getUnchecked(i)->audioDeviceIOCallback (inputChannelData, numInputChannels, tempChans, numOutputChannels, numSamples); for (int chan = 0; chan < numOutputChannels; ++chan) { const float* const src = tempChans [chan]; float* const dst = outputChannelData [chan]; if (src != 0 && dst != 0) for (int j = 0; j < numSamples; ++j) dst[j] += src[j]; } } const double msTaken = Time::getMillisecondCounterHiRes() - callbackStartTime; const double filterAmount = 0.2; cpuUsageMs += filterAmount * (msTaken - cpuUsageMs); } else { for (int i = 0; i < numOutputChannels; ++i) zeromem (outputChannelData[i], sizeof (float) * numSamples); } if (testSound != 0) { const int numSamps = jmin (numSamples, testSound->getNumSamples() - testSoundPosition); const float* const src = testSound->getSampleData (0, testSoundPosition); for (int i = 0; i < numOutputChannels; ++i) for (int j = 0; j < numSamps; ++j) outputChannelData [i][j] += src[j]; testSoundPosition += numSamps; if (testSoundPosition >= testSound->getNumSamples()) testSound = 0; } } void AudioDeviceManager::audioDeviceAboutToStartInt (AudioIODevice* const device) { cpuUsageMs = 0; const double sampleRate = device->getCurrentSampleRate(); const int blockSize = device->getCurrentBufferSizeSamples(); if (sampleRate > 0.0 && blockSize > 0) { const double msPerBlock = 1000.0 * blockSize / sampleRate; timeToCpuScale = (msPerBlock > 0.0) ? (1.0 / msPerBlock) : 0.0; } { const ScopedLock sl (audioCallbackLock); for (int i = callbacks.size(); --i >= 0;) callbacks.getUnchecked(i)->audioDeviceAboutToStart (device); } sendChangeMessage (this); } void AudioDeviceManager::audioDeviceStoppedInt() { cpuUsageMs = 0; timeToCpuScale = 0; sendChangeMessage (this); const ScopedLock sl (audioCallbackLock); for (int i = callbacks.size(); --i >= 0;) callbacks.getUnchecked(i)->audioDeviceStopped(); } double AudioDeviceManager::getCpuUsage() const { return jlimit (0.0, 1.0, timeToCpuScale * cpuUsageMs); } void AudioDeviceManager::setMidiInputEnabled (const String& name, const bool enabled) { if (enabled != isMidiInputEnabled (name)) { if (enabled) { const int index = MidiInput::getDevices().indexOf (name); if (index >= 0) { MidiInput* const min = MidiInput::openDevice (index, &callbackHandler); if (min != 0) { enabledMidiInputs.add (min); min->start(); } } } else { for (int i = enabledMidiInputs.size(); --i >= 0;) if (enabledMidiInputs[i]->getName() == name) enabledMidiInputs.remove (i); } updateXml(); sendChangeMessage (this); } } bool AudioDeviceManager::isMidiInputEnabled (const String& name) const { for (int i = enabledMidiInputs.size(); --i >= 0;) if (enabledMidiInputs[i]->getName() == name) return true; return false; } void AudioDeviceManager::addMidiInputCallback (const String& name, MidiInputCallback* callback) { removeMidiInputCallback (name, callback); if (name.isEmpty()) { midiCallbacks.add (callback); midiCallbackDevices.add (0); } else { for (int i = enabledMidiInputs.size(); --i >= 0;) { if (enabledMidiInputs[i]->getName() == name) { const ScopedLock sl (midiCallbackLock); midiCallbacks.add (callback); midiCallbackDevices.add (enabledMidiInputs[i]); break; } } } } void AudioDeviceManager::removeMidiInputCallback (const String& name, MidiInputCallback* /*callback*/) { const ScopedLock sl (midiCallbackLock); for (int i = midiCallbacks.size(); --i >= 0;) { String devName; if (midiCallbackDevices.getUnchecked(i) != 0) devName = midiCallbackDevices.getUnchecked(i)->getName(); if (devName == name) { midiCallbacks.remove (i); midiCallbackDevices.remove (i); } } } void AudioDeviceManager::handleIncomingMidiMessageInt (MidiInput* source, const MidiMessage& message) { if (! message.isActiveSense()) { const bool isDefaultSource = (source == 0 || source == enabledMidiInputs.getFirst()); const ScopedLock sl (midiCallbackLock); for (int i = midiCallbackDevices.size(); --i >= 0;) { MidiInput* const md = midiCallbackDevices.getUnchecked(i); if (md == source || (md == 0 && isDefaultSource)) midiCallbacks.getUnchecked(i)->handleIncomingMidiMessage (source, message); } } } void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) { if (defaultMidiOutputName != deviceName) { SortedSet oldCallbacks; { const ScopedLock sl (audioCallbackLock); oldCallbacks = callbacks; callbacks.clear(); } if (currentAudioDevice != 0) for (int i = oldCallbacks.size(); --i >= 0;) oldCallbacks.getUnchecked(i)->audioDeviceStopped(); defaultMidiOutput = 0; defaultMidiOutputName = deviceName; if (deviceName.isNotEmpty()) defaultMidiOutput = MidiOutput::openDevice (MidiOutput::getDevices().indexOf (deviceName)); if (currentAudioDevice != 0) for (int i = oldCallbacks.size(); --i >= 0;) oldCallbacks.getUnchecked(i)->audioDeviceAboutToStart (currentAudioDevice); { const ScopedLock sl (audioCallbackLock); callbacks = oldCallbacks; } updateXml(); sendChangeMessage (this); } } void AudioDeviceManager::CallbackHandler::audioDeviceIOCallback (const float** inputChannelData, int numInputChannels, float** outputChannelData, int numOutputChannels, int numSamples) { owner->audioDeviceIOCallbackInt (inputChannelData, numInputChannels, outputChannelData, numOutputChannels, numSamples); } void AudioDeviceManager::CallbackHandler::audioDeviceAboutToStart (AudioIODevice* device) { owner->audioDeviceAboutToStartInt (device); } void AudioDeviceManager::CallbackHandler::audioDeviceStopped() { owner->audioDeviceStoppedInt(); } void AudioDeviceManager::CallbackHandler::handleIncomingMidiMessage (MidiInput* source, const MidiMessage& message) { owner->handleIncomingMidiMessageInt (source, message); } void AudioDeviceManager::playTestSound() { { // cunningly nested to swap, unlock and delete in that order. ScopedPointer oldSound; { const ScopedLock sl (audioCallbackLock); oldSound = testSound; } } testSoundPosition = 0; if (currentAudioDevice != 0) { const double sampleRate = currentAudioDevice->getCurrentSampleRate(); const int soundLength = (int) sampleRate; AudioSampleBuffer* const newSound = new AudioSampleBuffer (1, soundLength); float* samples = newSound->getSampleData (0); const double frequency = MidiMessage::getMidiNoteInHertz (80); const float amplitude = 0.5f; const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); for (int i = 0; i < soundLength; ++i) samples[i] = amplitude * (float) std::sin (i * phasePerSample); newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); const ScopedLock sl (audioCallbackLock); testSound = newSound; } } void AudioDeviceManager::enableInputLevelMeasurement (const bool enableMeasurement) { const ScopedLock sl (audioCallbackLock); if (enableMeasurement) ++inputLevelMeasurementEnabledCount; else --inputLevelMeasurementEnabledCount; inputLevel = 0; } double AudioDeviceManager::getCurrentInputLevel() const { jassert (inputLevelMeasurementEnabledCount > 0); // you need to call enableInputLevelMeasurement() before using this! return inputLevel; } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioDeviceManager.cpp ***/ /*** Start of inlined file: juce_AudioIODevice.cpp ***/ BEGIN_JUCE_NAMESPACE AudioIODevice::AudioIODevice (const String& deviceName, const String& typeName_) : name (deviceName), typeName (typeName_) { } AudioIODevice::~AudioIODevice() { } bool AudioIODevice::hasControlPanel() const { return false; } bool AudioIODevice::showControlPanel() { jassertfalse; // this should only be called for devices which return true from // their hasControlPanel() method. return false; } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioIODevice.cpp ***/ /*** Start of inlined file: juce_AudioIODeviceType.cpp ***/ BEGIN_JUCE_NAMESPACE AudioIODeviceType::AudioIODeviceType (const String& name) : typeName (name) { } AudioIODeviceType::~AudioIODeviceType() { } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioIODeviceType.cpp ***/ /*** Start of inlined file: juce_MidiOutput.cpp ***/ BEGIN_JUCE_NAMESPACE MidiOutput::MidiOutput() : Thread ("midi out"), internal (0), firstMessage (0) { } MidiOutput::PendingMessage::PendingMessage (const uint8* const data, const int len, const double sampleNumber) : message (data, len, sampleNumber) { } void MidiOutput::sendBlockOfMessages (const MidiBuffer& buffer, const double millisecondCounterToStartAt, double samplesPerSecondForBuffer) { // You've got to call startBackgroundThread() for this to actually work.. jassert (isThreadRunning()); // this needs to be a value in the future - RTFM for this method! jassert (millisecondCounterToStartAt > 0); const double timeScaleFactor = 1000.0 / samplesPerSecondForBuffer; MidiBuffer::Iterator i (buffer); const uint8* data; int len, time; while (i.getNextEvent (data, len, time)) { const double eventTime = millisecondCounterToStartAt + timeScaleFactor * time; PendingMessage* const m = new PendingMessage (data, len, eventTime); const ScopedLock sl (lock); if (firstMessage == 0 || firstMessage->message.getTimeStamp() > eventTime) { m->next = firstMessage; firstMessage = m; } else { PendingMessage* mm = firstMessage; while (mm->next != 0 && mm->next->message.getTimeStamp() <= eventTime) mm = mm->next; m->next = mm->next; mm->next = m; } } notify(); } void MidiOutput::clearAllPendingMessages() { const ScopedLock sl (lock); while (firstMessage != 0) { PendingMessage* const m = firstMessage; firstMessage = firstMessage->next; delete m; } } void MidiOutput::startBackgroundThread() { startThread (9); } void MidiOutput::stopBackgroundThread() { stopThread (5000); } void MidiOutput::run() { while (! threadShouldExit()) { uint32 now = Time::getMillisecondCounter(); uint32 eventTime = 0; uint32 timeToWait = 500; PendingMessage* message; { const ScopedLock sl (lock); message = firstMessage; if (message != 0) { eventTime = roundToInt (message->message.getTimeStamp()); if (eventTime > now + 20) { timeToWait = eventTime - (now + 20); message = 0; } else { firstMessage = message->next; } } } if (message != 0) { if (eventTime > now) { Time::waitForMillisecondCounter (eventTime); if (threadShouldExit()) break; } if (eventTime > now - 200) sendMessageNow (message->message); delete message; } else { jassert (timeToWait < 1000 * 30); wait (timeToWait); } } clearAllPendingMessages(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_MidiOutput.cpp ***/ /*** Start of inlined file: juce_AudioDataConverters.cpp ***/ BEGIN_JUCE_NAMESPACE void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { const double maxVal = (double) 0x7fff; char* intData = static_cast (dest); if (dest != (void*) source || destBytesPerSample <= 4) { for (int i = 0; i < numSamples; ++i) { *(uint16*) intData = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); intData += destBytesPerSample; } } else { intData += destBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; *(uint16*) intData = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); } } } void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { const double maxVal = (double) 0x7fff; char* intData = static_cast (dest); if (dest != (void*) source || destBytesPerSample <= 4) { for (int i = 0; i < numSamples; ++i) { *(uint16*) intData = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); intData += destBytesPerSample; } } else { intData += destBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; *(uint16*) intData = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); } } } void AudioDataConverters::convertFloatToInt24LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { const double maxVal = (double) 0x7fffff; char* intData = static_cast (dest); if (dest != (void*) source || destBytesPerSample <= 4) { for (int i = 0; i < numSamples; ++i) { ByteOrder::littleEndian24BitToChars ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); intData += destBytesPerSample; } } else { intData += destBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; ByteOrder::littleEndian24BitToChars ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); } } } void AudioDataConverters::convertFloatToInt24BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { const double maxVal = (double) 0x7fffff; char* intData = static_cast (dest); if (dest != (void*) source || destBytesPerSample <= 4) { for (int i = 0; i < numSamples; ++i) { ByteOrder::bigEndian24BitToChars ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); intData += destBytesPerSample; } } else { intData += destBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; ByteOrder::bigEndian24BitToChars ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); } } } void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { const double maxVal = (double) 0x7fffffff; char* intData = static_cast (dest); if (dest != (void*) source || destBytesPerSample <= 4) { for (int i = 0; i < numSamples; ++i) { *(uint32*)intData = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); intData += destBytesPerSample; } } else { intData += destBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; *(uint32*)intData = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); } } } void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { const double maxVal = (double) 0x7fffffff; char* intData = static_cast (dest); if (dest != (void*) source || destBytesPerSample <= 4) { for (int i = 0; i < numSamples; ++i) { *(uint32*)intData = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); intData += destBytesPerSample; } } else { intData += destBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; *(uint32*)intData = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); } } } void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! char* d = static_cast (dest); for (int i = 0; i < numSamples; ++i) { *(float*) d = source[i]; #if JUCE_BIG_ENDIAN *(uint32*) d = ByteOrder::swap (*(uint32*) d); #endif d += destBytesPerSample; } } void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! char* d = static_cast (dest); for (int i = 0; i < numSamples; ++i) { *(float*) d = source[i]; #if JUCE_LITTLE_ENDIAN *(uint32*) d = ByteOrder::swap (*(uint32*) d); #endif d += destBytesPerSample; } } void AudioDataConverters::convertInt16LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const float scale = 1.0f / 0x7fff; const char* intData = static_cast (source); if (source != (void*) dest || srcBytesPerSample >= 4) { for (int i = 0; i < numSamples; ++i) { dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*(uint16*)intData); intData += srcBytesPerSample; } } else { intData += srcBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*(uint16*)intData); } } } void AudioDataConverters::convertInt16BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const float scale = 1.0f / 0x7fff; const char* intData = static_cast (source); if (source != (void*) dest || srcBytesPerSample >= 4) { for (int i = 0; i < numSamples; ++i) { dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*(uint16*)intData); intData += srcBytesPerSample; } } else { intData += srcBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*(uint16*)intData); } } } void AudioDataConverters::convertInt24LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const float scale = 1.0f / 0x7fffff; const char* intData = static_cast (source); if (source != (void*) dest || srcBytesPerSample >= 4) { for (int i = 0; i < numSamples; ++i) { dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData); intData += srcBytesPerSample; } } else { intData += srcBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData); } } } void AudioDataConverters::convertInt24BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const float scale = 1.0f / 0x7fffff; const char* intData = static_cast (source); if (source != (void*) dest || srcBytesPerSample >= 4) { for (int i = 0; i < numSamples; ++i) { dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData); intData += srcBytesPerSample; } } else { intData += srcBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData); } } } void AudioDataConverters::convertInt32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const float scale = 1.0f / 0x7fffffff; const char* intData = static_cast (source); if (source != (void*) dest || srcBytesPerSample >= 4) { for (int i = 0; i < numSamples; ++i) { dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*(uint32*) intData); intData += srcBytesPerSample; } } else { intData += srcBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*(uint32*) intData); } } } void AudioDataConverters::convertInt32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const float scale = 1.0f / 0x7fffffff; const char* intData = static_cast (source); if (source != (void*) dest || srcBytesPerSample >= 4) { for (int i = 0; i < numSamples; ++i) { dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*(uint32*) intData); intData += srcBytesPerSample; } } else { intData += srcBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*(uint32*) intData); } } } void AudioDataConverters::convertFloat32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const char* s = static_cast (source); for (int i = 0; i < numSamples; ++i) { dest[i] = *(float*)s; #if JUCE_BIG_ENDIAN uint32* const d = (uint32*) (dest + i); *d = ByteOrder::swap (*d); #endif s += srcBytesPerSample; } } void AudioDataConverters::convertFloat32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const char* s = static_cast (source); for (int i = 0; i < numSamples; ++i) { dest[i] = *(float*)s; #if JUCE_LITTLE_ENDIAN uint32* const d = (uint32*) (dest + i); *d = ByteOrder::swap (*d); #endif s += srcBytesPerSample; } } void AudioDataConverters::convertFloatToFormat (const DataFormat destFormat, const float* const source, void* const dest, const int numSamples) { switch (destFormat) { case int16LE: convertFloatToInt16LE (source, dest, numSamples); break; case int16BE: convertFloatToInt16BE (source, dest, numSamples); break; case int24LE: convertFloatToInt24LE (source, dest, numSamples); break; case int24BE: convertFloatToInt24BE (source, dest, numSamples); break; case int32LE: convertFloatToInt32LE (source, dest, numSamples); break; case int32BE: convertFloatToInt32BE (source, dest, numSamples); break; case float32LE: convertFloatToFloat32LE (source, dest, numSamples); break; case float32BE: convertFloatToFloat32BE (source, dest, numSamples); break; default: jassertfalse; break; } } void AudioDataConverters::convertFormatToFloat (const DataFormat sourceFormat, const void* const source, float* const dest, const int numSamples) { switch (sourceFormat) { case int16LE: convertInt16LEToFloat (source, dest, numSamples); break; case int16BE: convertInt16BEToFloat (source, dest, numSamples); break; case int24LE: convertInt24LEToFloat (source, dest, numSamples); break; case int24BE: convertInt24BEToFloat (source, dest, numSamples); break; case int32LE: convertInt32LEToFloat (source, dest, numSamples); break; case int32BE: convertInt32BEToFloat (source, dest, numSamples); break; case float32LE: convertFloat32LEToFloat (source, dest, numSamples); break; case float32BE: convertFloat32BEToFloat (source, dest, numSamples); break; default: jassertfalse; break; } } void AudioDataConverters::interleaveSamples (const float** const source, float* const dest, const int numSamples, const int numChannels) { for (int chan = 0; chan < numChannels; ++chan) { int i = chan; const float* src = source [chan]; for (int j = 0; j < numSamples; ++j) { dest [i] = src [j]; i += numChannels; } } } void AudioDataConverters::deinterleaveSamples (const float* const source, float** const dest, const int numSamples, const int numChannels) { for (int chan = 0; chan < numChannels; ++chan) { int i = chan; float* dst = dest [chan]; for (int j = 0; j < numSamples; ++j) { dst [j] = source [i]; i += numChannels; } } } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioDataConverters.cpp ***/ /*** Start of inlined file: juce_AudioSampleBuffer.cpp ***/ BEGIN_JUCE_NAMESPACE AudioSampleBuffer::AudioSampleBuffer (const int numChannels_, const int numSamples) throw() : numChannels (numChannels_), size (numSamples) { jassert (numSamples >= 0); jassert (numChannels_ > 0); allocateData(); } AudioSampleBuffer::AudioSampleBuffer (const AudioSampleBuffer& other) throw() : numChannels (other.numChannels), size (other.size) { allocateData(); const size_t numBytes = size * sizeof (float); for (int i = 0; i < numChannels; ++i) memcpy (channels[i], other.channels[i], numBytes); } void AudioSampleBuffer::allocateData() { const size_t channelListSize = (numChannels + 1) * sizeof (float*); allocatedBytes = (int) (numChannels * size * sizeof (float) + channelListSize + 32); allocatedData.malloc (allocatedBytes); channels = reinterpret_cast (allocatedData.getData()); float* chan = (float*) (allocatedData + channelListSize); for (int i = 0; i < numChannels; ++i) { channels[i] = chan; chan += size; } channels [numChannels] = 0; } AudioSampleBuffer::AudioSampleBuffer (float** dataToReferTo, const int numChannels_, const int numSamples) throw() : numChannels (numChannels_), size (numSamples), allocatedBytes (0) { jassert (numChannels_ > 0); allocateChannels (dataToReferTo); } void AudioSampleBuffer::setDataToReferTo (float** dataToReferTo, const int newNumChannels, const int newNumSamples) throw() { jassert (newNumChannels > 0); allocatedBytes = 0; allocatedData.free(); numChannels = newNumChannels; size = newNumSamples; allocateChannels (dataToReferTo); } void AudioSampleBuffer::allocateChannels (float** const dataToReferTo) { // (try to avoid doing a malloc here, as that'll blow up things like Pro-Tools) if (numChannels < numElementsInArray (preallocatedChannelSpace)) { channels = static_cast (preallocatedChannelSpace); } else { allocatedData.malloc (numChannels + 1, sizeof (float*)); channels = reinterpret_cast (allocatedData.getData()); } for (int i = 0; i < numChannels; ++i) { // you have to pass in the same number of valid pointers as numChannels jassert (dataToReferTo[i] != 0); channels[i] = dataToReferTo[i]; } channels [numChannels] = 0; } AudioSampleBuffer& AudioSampleBuffer::operator= (const AudioSampleBuffer& other) throw() { if (this != &other) { setSize (other.getNumChannels(), other.getNumSamples(), false, false, false); const size_t numBytes = size * sizeof (float); for (int i = 0; i < numChannels; ++i) memcpy (channels[i], other.channels[i], numBytes); } return *this; } AudioSampleBuffer::~AudioSampleBuffer() throw() { } void AudioSampleBuffer::setSize (const int newNumChannels, const int newNumSamples, const bool keepExistingContent, const bool clearExtraSpace, const bool avoidReallocating) throw() { jassert (newNumChannels > 0); if (newNumSamples != size || newNumChannels != numChannels) { const size_t channelListSize = (newNumChannels + 1) * sizeof (float*); const size_t newTotalBytes = (newNumChannels * newNumSamples * sizeof (float)) + channelListSize + 32; if (keepExistingContent) { HeapBlock newData; newData.allocate (newTotalBytes, clearExtraSpace); const int numChansToCopy = jmin (numChannels, newNumChannels); const size_t numBytesToCopy = sizeof (float) * jmin (newNumSamples, size); float** const newChannels = reinterpret_cast (newData.getData()); float* newChan = reinterpret_cast (newData + channelListSize); for (int i = 0; i < numChansToCopy; ++i) { memcpy (newChan, channels[i], numBytesToCopy); newChannels[i] = newChan; newChan += newNumSamples; } allocatedData.swapWith (newData); allocatedBytes = (int) newTotalBytes; channels = newChannels; } else { if (avoidReallocating && allocatedBytes >= newTotalBytes) { if (clearExtraSpace) zeromem (allocatedData, newTotalBytes); } else { allocatedBytes = newTotalBytes; allocatedData.allocate (newTotalBytes, clearExtraSpace); channels = reinterpret_cast (allocatedData.getData()); } float* chan = reinterpret_cast (allocatedData + channelListSize); for (int i = 0; i < newNumChannels; ++i) { channels[i] = chan; chan += newNumSamples; } } channels [newNumChannels] = 0; size = newNumSamples; numChannels = newNumChannels; } } void AudioSampleBuffer::clear() throw() { for (int i = 0; i < numChannels; ++i) zeromem (channels[i], size * sizeof (float)); } void AudioSampleBuffer::clear (const int startSample, const int numSamples) throw() { jassert (startSample >= 0 && startSample + numSamples <= size); for (int i = 0; i < numChannels; ++i) zeromem (channels [i] + startSample, numSamples * sizeof (float)); } void AudioSampleBuffer::clear (const int channel, const int startSample, const int numSamples) throw() { jassert (((unsigned int) channel) < (unsigned int) numChannels); jassert (startSample >= 0 && startSample + numSamples <= size); zeromem (channels [channel] + startSample, numSamples * sizeof (float)); } void AudioSampleBuffer::applyGain (const int channel, const int startSample, int numSamples, const float gain) throw() { jassert (((unsigned int) channel) < (unsigned int) numChannels); jassert (startSample >= 0 && startSample + numSamples <= size); if (gain != 1.0f) { float* d = channels [channel] + startSample; if (gain == 0.0f) { zeromem (d, sizeof (float) * numSamples); } else { while (--numSamples >= 0) *d++ *= gain; } } } void AudioSampleBuffer::applyGainRamp (const int channel, const int startSample, int numSamples, float startGain, float endGain) throw() { if (startGain == endGain) { applyGain (channel, startSample, numSamples, startGain); } else { jassert (((unsigned int) channel) < (unsigned int) numChannels); jassert (startSample >= 0 && startSample + numSamples <= size); const float increment = (endGain - startGain) / numSamples; float* d = channels [channel] + startSample; while (--numSamples >= 0) { *d++ *= startGain; startGain += increment; } } } void AudioSampleBuffer::applyGain (const int startSample, const int numSamples, const float gain) throw() { for (int i = 0; i < numChannels; ++i) applyGain (i, startSample, numSamples, gain); } void AudioSampleBuffer::addFrom (const int destChannel, const int destStartSample, const AudioSampleBuffer& source, const int sourceChannel, const int sourceStartSample, int numSamples, const float gain) throw() { jassert (&source != this || sourceChannel != destChannel); jassert (((unsigned int) destChannel) < (unsigned int) numChannels); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (((unsigned int) sourceChannel) < (unsigned int) source.numChannels); jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); if (gain != 0.0f && numSamples > 0) { float* d = channels [destChannel] + destStartSample; const float* s = source.channels [sourceChannel] + sourceStartSample; if (gain != 1.0f) { while (--numSamples >= 0) *d++ += gain * *s++; } else { while (--numSamples >= 0) *d++ += *s++; } } } void AudioSampleBuffer::addFrom (const int destChannel, const int destStartSample, const float* source, int numSamples, const float gain) throw() { jassert (((unsigned int) destChannel) < (unsigned int) numChannels); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); if (gain != 0.0f && numSamples > 0) { float* d = channels [destChannel] + destStartSample; if (gain != 1.0f) { while (--numSamples >= 0) *d++ += gain * *source++; } else { while (--numSamples >= 0) *d++ += *source++; } } } void AudioSampleBuffer::addFromWithRamp (const int destChannel, const int destStartSample, const float* source, int numSamples, float startGain, const float endGain) throw() { jassert (((unsigned int) destChannel) < (unsigned int) numChannels); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); if (startGain == endGain) { addFrom (destChannel, destStartSample, source, numSamples, startGain); } else { if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f)) { const float increment = (endGain - startGain) / numSamples; float* d = channels [destChannel] + destStartSample; while (--numSamples >= 0) { *d++ += startGain * *source++; startGain += increment; } } } } void AudioSampleBuffer::copyFrom (const int destChannel, const int destStartSample, const AudioSampleBuffer& source, const int sourceChannel, const int sourceStartSample, int numSamples) throw() { jassert (&source != this || sourceChannel != destChannel); jassert (((unsigned int) destChannel) < (unsigned int) numChannels); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (((unsigned int) sourceChannel) < (unsigned int) source.numChannels); jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); if (numSamples > 0) { memcpy (channels [destChannel] + destStartSample, source.channels [sourceChannel] + sourceStartSample, sizeof (float) * numSamples); } } void AudioSampleBuffer::copyFrom (const int destChannel, const int destStartSample, const float* source, int numSamples) throw() { jassert (((unsigned int) destChannel) < (unsigned int) numChannels); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); if (numSamples > 0) { memcpy (channels [destChannel] + destStartSample, source, sizeof (float) * numSamples); } } void AudioSampleBuffer::copyFrom (const int destChannel, const int destStartSample, const float* source, int numSamples, const float gain) throw() { jassert (((unsigned int) destChannel) < (unsigned int) numChannels); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); if (numSamples > 0) { float* d = channels [destChannel] + destStartSample; if (gain != 1.0f) { if (gain == 0) { zeromem (d, sizeof (float) * numSamples); } else { while (--numSamples >= 0) *d++ = gain * *source++; } } else { memcpy (d, source, sizeof (float) * numSamples); } } } void AudioSampleBuffer::copyFromWithRamp (const int destChannel, const int destStartSample, const float* source, int numSamples, float startGain, float endGain) throw() { jassert (((unsigned int) destChannel) < (unsigned int) numChannels); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); if (startGain == endGain) { copyFrom (destChannel, destStartSample, source, numSamples, startGain); } else { if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f)) { const float increment = (endGain - startGain) / numSamples; float* d = channels [destChannel] + destStartSample; while (--numSamples >= 0) { *d++ = startGain * *source++; startGain += increment; } } } } void AudioSampleBuffer::findMinMax (const int channel, const int startSample, int numSamples, float& minVal, float& maxVal) const throw() { jassert (((unsigned int) channel) < (unsigned int) numChannels); jassert (startSample >= 0 && startSample + numSamples <= size); if (numSamples <= 0) { minVal = 0.0f; maxVal = 0.0f; } else { const float* d = channels [channel] + startSample; float mn = *d++; float mx = mn; while (--numSamples > 0) // (> 0 rather than >= 0 because we've already taken the first sample) { const float samp = *d++; if (samp > mx) mx = samp; if (samp < mn) mn = samp; } maxVal = mx; minVal = mn; } } float AudioSampleBuffer::getMagnitude (const int channel, const int startSample, const int numSamples) const throw() { jassert (((unsigned int) channel) < (unsigned int) numChannels); jassert (startSample >= 0 && startSample + numSamples <= size); float mn, mx; findMinMax (channel, startSample, numSamples, mn, mx); return jmax (mn, -mn, mx, -mx); } float AudioSampleBuffer::getMagnitude (const int startSample, const int numSamples) const throw() { float mag = 0.0f; for (int i = 0; i < numChannels; ++i) mag = jmax (mag, getMagnitude (i, startSample, numSamples)); return mag; } float AudioSampleBuffer::getRMSLevel (const int channel, const int startSample, const int numSamples) const throw() { jassert (((unsigned int) channel) < (unsigned int) numChannels); jassert (startSample >= 0 && startSample + numSamples <= size); if (numSamples <= 0 || channel < 0 || channel >= numChannels) return 0.0f; const float* const data = channels [channel] + startSample; double sum = 0.0; for (int i = 0; i < numSamples; ++i) { const float sample = data [i]; sum += sample * sample; } return (float) std::sqrt (sum / numSamples); } void AudioSampleBuffer::readFromAudioReader (AudioFormatReader* reader, const int startSample, const int numSamples, const int readerStartSample, const bool useLeftChan, const bool useRightChan) { jassert (reader != 0); jassert (startSample >= 0 && startSample + numSamples <= size); if (numSamples > 0) { int* chans[3]; if (useLeftChan == useRightChan) { chans[0] = reinterpret_cast (getSampleData (0, startSample)); chans[1] = (reader->numChannels > 1 && getNumChannels() > 1) ? reinterpret_cast (getSampleData (1, startSample)) : 0; } else if (useLeftChan || (reader->numChannels == 1)) { chans[0] = reinterpret_cast (getSampleData (0, startSample)); chans[1] = 0; } else if (useRightChan) { chans[0] = 0; chans[1] = reinterpret_cast (getSampleData (0, startSample)); } chans[2] = 0; reader->read (chans, 2, readerStartSample, numSamples, true); if (! reader->usesFloatingPointData) { for (int j = 0; j < 2; ++j) { float* const d = reinterpret_cast (chans[j]); if (d != 0) { const float multiplier = 1.0f / 0x7fffffff; for (int i = 0; i < numSamples; ++i) d[i] = *reinterpret_cast (d + i) * multiplier; } } } if (numChannels > 1 && (chans[0] == 0 || chans[1] == 0)) { // if this is a stereo buffer and the source was mono, dupe the first channel.. memcpy (getSampleData (1, startSample), getSampleData (0, startSample), sizeof (float) * numSamples); } } } void AudioSampleBuffer::writeToAudioWriter (AudioFormatWriter* writer, const int startSample, const int numSamples) const { jassert (startSample >= 0 && startSample + numSamples <= size && numChannels > 0); if (numSamples > 0) { HeapBlock tempBuffer; HeapBlock chans (numChannels + 1); chans [numChannels] = 0; if (writer->isFloatingPoint()) { for (int i = numChannels; --i >= 0;) chans[i] = reinterpret_cast (channels[i] + startSample); } else { tempBuffer.malloc (numSamples * numChannels); for (int j = 0; j < numChannels; ++j) { int* const dest = tempBuffer + j * numSamples; const float* const src = channels[j] + startSample; chans[j] = dest; for (int i = 0; i < numSamples; ++i) { const double samp = src[i]; if (samp <= -1.0) dest[i] = std::numeric_limits::min(); else if (samp >= 1.0) dest[i] = std::numeric_limits::max(); else dest[i] = roundToInt (std::numeric_limits::max() * samp); } } } writer->write ((const int**) chans.getData(), numSamples); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioSampleBuffer.cpp ***/ /*** Start of inlined file: juce_IIRFilter.cpp ***/ BEGIN_JUCE_NAMESPACE IIRFilter::IIRFilter() : active (false) { reset(); } IIRFilter::IIRFilter (const IIRFilter& other) : active (other.active) { const ScopedLock sl (other.processLock); memcpy (coefficients, other.coefficients, sizeof (coefficients)); reset(); } IIRFilter::~IIRFilter() { } void IIRFilter::reset() throw() { const ScopedLock sl (processLock); x1 = 0; x2 = 0; y1 = 0; y2 = 0; } float IIRFilter::processSingleSampleRaw (const float in) throw() { float out = coefficients[0] * in + coefficients[1] * x1 + coefficients[2] * x2 - coefficients[4] * y1 - coefficients[5] * y2; #if JUCE_INTEL if (! (out < -1.0e-8 || out > 1.0e-8)) out = 0; #endif x2 = x1; x1 = in; y2 = y1; y1 = out; return out; } void IIRFilter::processSamples (float* const samples, const int numSamples) throw() { const ScopedLock sl (processLock); if (active) { for (int i = 0; i < numSamples; ++i) { const float in = samples[i]; float out = coefficients[0] * in + coefficients[1] * x1 + coefficients[2] * x2 - coefficients[4] * y1 - coefficients[5] * y2; #if JUCE_INTEL if (! (out < -1.0e-8 || out > 1.0e-8)) out = 0; #endif x2 = x1; x1 = in; y2 = y1; y1 = out; samples[i] = out; } } } void IIRFilter::makeLowPass (const double sampleRate, const double frequency) throw() { jassert (sampleRate > 0); const double n = 1.0 / tan (double_Pi * frequency / sampleRate); const double nSquared = n * n; const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); setCoefficients (c1, c1 * 2.0f, c1, 1.0, c1 * 2.0 * (1.0 - nSquared), c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); } void IIRFilter::makeHighPass (const double sampleRate, const double frequency) throw() { const double n = tan (double_Pi * frequency / sampleRate); const double nSquared = n * n; const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); setCoefficients (c1, c1 * -2.0f, c1, 1.0, c1 * 2.0 * (nSquared - 1.0), c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); } void IIRFilter::makeLowShelf (const double sampleRate, const double cutOffFrequency, const double Q, const float gainFactor) throw() { jassert (sampleRate > 0); jassert (Q > 0); const double A = jmax (0.0f, gainFactor); const double aminus1 = A - 1.0; const double aplus1 = A + 1.0; const double omega = (double_Pi * 2.0 * jmax (cutOffFrequency, 2.0)) / sampleRate; const double coso = std::cos (omega); const double beta = std::sin (omega) * std::sqrt (A) / Q; const double aminus1TimesCoso = aminus1 * coso; setCoefficients (A * (aplus1 - aminus1TimesCoso + beta), A * 2.0 * (aminus1 - aplus1 * coso), A * (aplus1 - aminus1TimesCoso - beta), aplus1 + aminus1TimesCoso + beta, -2.0 * (aminus1 + aplus1 * coso), aplus1 + aminus1TimesCoso - beta); } void IIRFilter::makeHighShelf (const double sampleRate, const double cutOffFrequency, const double Q, const float gainFactor) throw() { jassert (sampleRate > 0); jassert (Q > 0); const double A = jmax (0.0f, gainFactor); const double aminus1 = A - 1.0; const double aplus1 = A + 1.0; const double omega = (double_Pi * 2.0 * jmax (cutOffFrequency, 2.0)) / sampleRate; const double coso = std::cos (omega); const double beta = std::sin (omega) * std::sqrt (A) / Q; const double aminus1TimesCoso = aminus1 * coso; setCoefficients (A * (aplus1 + aminus1TimesCoso + beta), A * -2.0 * (aminus1 + aplus1 * coso), A * (aplus1 + aminus1TimesCoso - beta), aplus1 - aminus1TimesCoso + beta, 2.0 * (aminus1 - aplus1 * coso), aplus1 - aminus1TimesCoso - beta); } void IIRFilter::makeBandPass (const double sampleRate, const double centreFrequency, const double Q, const float gainFactor) throw() { jassert (sampleRate > 0); jassert (Q > 0); const double A = jmax (0.0f, gainFactor); const double omega = (double_Pi * 2.0 * jmax (centreFrequency, 2.0)) / sampleRate; const double alpha = 0.5 * std::sin (omega) / Q; const double c2 = -2.0 * std::cos (omega); const double alphaTimesA = alpha * A; const double alphaOverA = alpha / A; setCoefficients (1.0 + alphaTimesA, c2, 1.0 - alphaTimesA, 1.0 + alphaOverA, c2, 1.0 - alphaOverA); } void IIRFilter::makeInactive() throw() { const ScopedLock sl (processLock); active = false; } void IIRFilter::copyCoefficientsFrom (const IIRFilter& other) throw() { const ScopedLock sl (processLock); memcpy (coefficients, other.coefficients, sizeof (coefficients)); active = other.active; } void IIRFilter::setCoefficients (double c1, double c2, double c3, double c4, double c5, double c6) throw() { const double a = 1.0 / c4; c1 *= a; c2 *= a; c3 *= a; c5 *= a; c6 *= a; const ScopedLock sl (processLock); coefficients[0] = (float) c1; coefficients[1] = (float) c2; coefficients[2] = (float) c3; coefficients[3] = (float) c4; coefficients[4] = (float) c5; coefficients[5] = (float) c6; active = true; } END_JUCE_NAMESPACE /*** End of inlined file: juce_IIRFilter.cpp ***/ /*** Start of inlined file: juce_MidiBuffer.cpp ***/ BEGIN_JUCE_NAMESPACE MidiBuffer::MidiBuffer() throw() : bytesUsed (0) { } MidiBuffer::MidiBuffer (const MidiMessage& message) throw() : bytesUsed (0) { addEvent (message, 0); } MidiBuffer::MidiBuffer (const MidiBuffer& other) throw() : data (other.data), bytesUsed (other.bytesUsed) { } MidiBuffer& MidiBuffer::operator= (const MidiBuffer& other) throw() { bytesUsed = other.bytesUsed; data = other.data; return *this; } void MidiBuffer::swapWith (MidiBuffer& other) throw() { data.swapWith (other.data); swapVariables (bytesUsed, other.bytesUsed); } MidiBuffer::~MidiBuffer() { } inline uint8* MidiBuffer::getData() const throw() { return static_cast (data.getData()); } inline int MidiBuffer::getEventTime (const void* const d) throw() { return *static_cast (d); } inline uint16 MidiBuffer::getEventDataSize (const void* const d) throw() { return *reinterpret_cast (static_cast (d) + sizeof (int)); } inline uint16 MidiBuffer::getEventTotalSize (const void* const d) throw() { return getEventDataSize (d) + sizeof (int) + sizeof (uint16); } void MidiBuffer::clear() throw() { bytesUsed = 0; } void MidiBuffer::clear (const int startSample, const int numSamples) { uint8* const start = findEventAfter (getData(), startSample - 1); uint8* const end = findEventAfter (start, startSample + numSamples - 1); if (end > start) { const int bytesToMove = bytesUsed - (int) (end - getData()); if (bytesToMove > 0) memmove (start, end, bytesToMove); bytesUsed -= (int) (end - start); } } void MidiBuffer::addEvent (const MidiMessage& m, const int sampleNumber) { addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber); } static int findActualEventLength (const uint8* const data, const int maxBytes) throw() { unsigned int byte = (unsigned int) *data; int size = 0; if (byte == 0xf0 || byte == 0xf7) { const uint8* d = data + 1; while (d < data + maxBytes) if (*d++ == 0xf7) break; size = (int) (d - data); } else if (byte == 0xff) { int n; const int bytesLeft = MidiMessage::readVariableLengthVal (data + 1, n); size = jmin (maxBytes, n + 2 + bytesLeft); } else if (byte >= 0x80) { size = jmin (maxBytes, MidiMessage::getMessageLengthFromFirstByte ((uint8) byte)); } return size; } void MidiBuffer::addEvent (const void* const newData, const int maxBytes, const int sampleNumber) { const int numBytes = findActualEventLength (static_cast (newData), maxBytes); if (numBytes > 0) { int spaceNeeded = bytesUsed + numBytes + sizeof (int) + sizeof (uint16); data.ensureSize ((spaceNeeded + spaceNeeded / 2 + 8) & ~7); uint8* d = findEventAfter (getData(), sampleNumber); const int bytesToMove = bytesUsed - (int) (d - getData()); if (bytesToMove > 0) memmove (d + numBytes + sizeof (int) + sizeof (uint16), d, bytesToMove); *reinterpret_cast (d) = sampleNumber; d += sizeof (int); *reinterpret_cast (d) = (uint16) numBytes; d += sizeof (uint16); memcpy (d, newData, numBytes); bytesUsed += numBytes + sizeof (int) + sizeof (uint16); } } void MidiBuffer::addEvents (const MidiBuffer& otherBuffer, const int startSample, const int numSamples, const int sampleDeltaToAdd) { Iterator i (otherBuffer); i.setNextSamplePosition (startSample); const uint8* eventData; int eventSize, position; while (i.getNextEvent (eventData, eventSize, position) && (position < startSample + numSamples || numSamples < 0)) { addEvent (eventData, eventSize, position + sampleDeltaToAdd); } } void MidiBuffer::ensureSize (size_t minimumNumBytes) { data.ensureSize (minimumNumBytes); } bool MidiBuffer::isEmpty() const throw() { return bytesUsed == 0; } int MidiBuffer::getNumEvents() const throw() { int n = 0; const uint8* d = getData(); const uint8* const end = d + bytesUsed; while (d < end) { d += getEventTotalSize (d); ++n; } return n; } int MidiBuffer::getFirstEventTime() const throw() { return bytesUsed > 0 ? getEventTime (data.getData()) : 0; } int MidiBuffer::getLastEventTime() const throw() { if (bytesUsed == 0) return 0; const uint8* d = getData(); const uint8* const endData = d + bytesUsed; for (;;) { const uint8* const nextOne = d + getEventTotalSize (d); if (nextOne >= endData) return getEventTime (d); d = nextOne; } } uint8* MidiBuffer::findEventAfter (uint8* d, const int samplePosition) const throw() { const uint8* const endData = getData() + bytesUsed; while (d < endData && getEventTime (d) <= samplePosition) d += getEventTotalSize (d); return d; } MidiBuffer::Iterator::Iterator (const MidiBuffer& buffer_) throw() : buffer (buffer_), data (buffer_.getData()) { } MidiBuffer::Iterator::~Iterator() throw() { } void MidiBuffer::Iterator::setNextSamplePosition (const int samplePosition) throw() { data = buffer.getData(); const uint8* dataEnd = data + buffer.bytesUsed; while (data < dataEnd && getEventTime (data) < samplePosition) data += getEventTotalSize (data); } bool MidiBuffer::Iterator::getNextEvent (const uint8* &midiData, int& numBytes, int& samplePosition) throw() { if (data >= buffer.getData() + buffer.bytesUsed) return false; samplePosition = getEventTime (data); numBytes = getEventDataSize (data); data += sizeof (int) + sizeof (uint16); midiData = data; data += numBytes; return true; } bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePosition) throw() { if (data >= buffer.getData() + buffer.bytesUsed) return false; samplePosition = getEventTime (data); const int numBytes = getEventDataSize (data); data += sizeof (int) + sizeof (uint16); result = MidiMessage (data, numBytes, samplePosition); data += numBytes; return true; } END_JUCE_NAMESPACE /*** End of inlined file: juce_MidiBuffer.cpp ***/ /*** Start of inlined file: juce_MidiFile.cpp ***/ BEGIN_JUCE_NAMESPACE namespace MidiFileHelpers { static void writeVariableLengthInt (OutputStream& out, unsigned int v) { unsigned int buffer = v & 0x7F; while ((v >>= 7) != 0) { buffer <<= 8; buffer |= ((v & 0x7F) | 0x80); } for (;;) { out.writeByte ((char) buffer); if (buffer & 0x80) buffer >>= 8; else break; } } static bool parseMidiHeader (const uint8* &data, short& timeFormat, short& fileType, short& numberOfTracks) throw() { unsigned int ch = (int) ByteOrder::bigEndianInt (data); data += 4; if (ch != ByteOrder::bigEndianInt ("MThd")) { bool ok = false; if (ch == ByteOrder::bigEndianInt ("RIFF")) { for (int i = 0; i < 8; ++i) { ch = ByteOrder::bigEndianInt (data); data += 4; if (ch == ByteOrder::bigEndianInt ("MThd")) { ok = true; break; } } } if (! ok) return false; } unsigned int bytesRemaining = ByteOrder::bigEndianInt (data); data += 4; fileType = (short) ByteOrder::bigEndianShort (data); data += 2; numberOfTracks = (short) ByteOrder::bigEndianShort (data); data += 2; timeFormat = (short) ByteOrder::bigEndianShort (data); data += 2; bytesRemaining -= 6; data += bytesRemaining; return true; } static double convertTicksToSeconds (const double time, const MidiMessageSequence& tempoEvents, const int timeFormat) { if (timeFormat > 0) { int numer = 4, denom = 4; double tempoTime = 0.0, correctedTempoTime = 0.0; const double tickLen = 1.0 / (timeFormat & 0x7fff); double secsPerTick = 0.5 * tickLen; const int numEvents = tempoEvents.getNumEvents(); for (int i = 0; i < numEvents; ++i) { const MidiMessage& m = tempoEvents.getEventPointer(i)->message; if (time <= m.getTimeStamp()) break; if (timeFormat > 0) { correctedTempoTime = correctedTempoTime + (m.getTimeStamp() - tempoTime) * secsPerTick; } else { correctedTempoTime = tickLen * m.getTimeStamp() / (((timeFormat & 0x7fff) >> 8) * (timeFormat & 0xff)); } tempoTime = m.getTimeStamp(); if (m.isTempoMetaEvent()) secsPerTick = tickLen * m.getTempoSecondsPerQuarterNote(); else if (m.isTimeSignatureMetaEvent()) m.getTimeSignatureInfo (numer, denom); while (i + 1 < numEvents) { const MidiMessage& m2 = tempoEvents.getEventPointer(i + 1)->message; if (m2.getTimeStamp() == tempoTime) { ++i; if (m2.isTempoMetaEvent()) secsPerTick = tickLen * m2.getTempoSecondsPerQuarterNote(); else if (m2.isTimeSignatureMetaEvent()) m2.getTimeSignatureInfo (numer, denom); } else { break; } } } return correctedTempoTime + (time - tempoTime) * secsPerTick; } else { return time / (((timeFormat & 0x7fff) >> 8) * (timeFormat & 0xff)); } } // a comparator that puts all the note-offs before note-ons that have the same time struct Sorter { static int compareElements (const MidiMessageSequence::MidiEventHolder* const first, const MidiMessageSequence::MidiEventHolder* const second) throw() { const double diff = (first->message.getTimeStamp() - second->message.getTimeStamp()); if (diff == 0) { if (first->message.isNoteOff() && second->message.isNoteOn()) return -1; else if (first->message.isNoteOn() && second->message.isNoteOff()) return 1; else return 0; } else { return (diff > 0) ? 1 : -1; } } }; } MidiFile::MidiFile() : timeFormat ((short) (unsigned short) 0xe728) { } MidiFile::~MidiFile() { clear(); } void MidiFile::clear() { tracks.clear(); } int MidiFile::getNumTracks() const throw() { return tracks.size(); } const MidiMessageSequence* MidiFile::getTrack (const int index) const throw() { return tracks [index]; } void MidiFile::addTrack (const MidiMessageSequence& trackSequence) { tracks.add (new MidiMessageSequence (trackSequence)); } short MidiFile::getTimeFormat() const throw() { return timeFormat; } void MidiFile::setTicksPerQuarterNote (const int ticks) throw() { timeFormat = (short) ticks; } void MidiFile::setSmpteTimeFormat (const int framesPerSecond, const int subframeResolution) throw() { timeFormat = (short) (((-framesPerSecond) << 8) | subframeResolution); } void MidiFile::findAllTempoEvents (MidiMessageSequence& tempoChangeEvents) const { for (int i = tracks.size(); --i >= 0;) { const int numEvents = tracks.getUnchecked(i)->getNumEvents(); for (int j = 0; j < numEvents; ++j) { const MidiMessage& m = tracks.getUnchecked(i)->getEventPointer (j)->message; if (m.isTempoMetaEvent()) tempoChangeEvents.addEvent (m); } } } void MidiFile::findAllTimeSigEvents (MidiMessageSequence& timeSigEvents) const { for (int i = tracks.size(); --i >= 0;) { const int numEvents = tracks.getUnchecked(i)->getNumEvents(); for (int j = 0; j < numEvents; ++j) { const MidiMessage& m = tracks.getUnchecked(i)->getEventPointer (j)->message; if (m.isTimeSignatureMetaEvent()) timeSigEvents.addEvent (m); } } } double MidiFile::getLastTimestamp() const { double t = 0.0; for (int i = tracks.size(); --i >= 0;) t = jmax (t, tracks.getUnchecked(i)->getEndTime()); return t; } bool MidiFile::readFrom (InputStream& sourceStream) { clear(); MemoryBlock data; const int maxSensibleMidiFileSize = 2 * 1024 * 1024; // (put a sanity-check on the file size, as midi files are generally small) if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) { size_t size = data.getSize(); const uint8* d = static_cast (data.getData()); short fileType, expectedTracks; if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks)) { size -= (int) (d - static_cast (data.getData())); int track = 0; while (size > 0 && track < expectedTracks) { const int chunkType = (int) ByteOrder::bigEndianInt (d); d += 4; const int chunkSize = (int) ByteOrder::bigEndianInt (d); d += 4; if (chunkSize <= 0) break; if (size < 0) return false; if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk")) { readNextTrack (d, chunkSize); } size -= chunkSize + 8; d += chunkSize; ++track; } return true; } } return false; } void MidiFile::readNextTrack (const uint8* data, int size) { double time = 0; char lastStatusByte = 0; MidiMessageSequence result; while (size > 0) { int bytesUsed; const int delay = MidiMessage::readVariableLengthVal (data, bytesUsed); data += bytesUsed; size -= bytesUsed; time += delay; int messSize = 0; const MidiMessage mm (data, size, messSize, lastStatusByte, time); if (messSize <= 0) break; size -= messSize; data += messSize; result.addEvent (mm); const char firstByte = *(mm.getRawData()); if ((firstByte & 0xf0) != 0xf0) lastStatusByte = firstByte; } // use a sort that puts all the note-offs before note-ons that have the same time MidiFileHelpers::Sorter sorter; result.list.sort (sorter, true); result.updateMatchedPairs(); addTrack (result); } void MidiFile::convertTimestampTicksToSeconds() { MidiMessageSequence tempoEvents; findAllTempoEvents (tempoEvents); findAllTimeSigEvents (tempoEvents); for (int i = 0; i < tracks.size(); ++i) { MidiMessageSequence& ms = *tracks.getUnchecked(i); for (int j = ms.getNumEvents(); --j >= 0;) { MidiMessage& m = ms.getEventPointer(j)->message; m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(), tempoEvents, timeFormat)); } } } bool MidiFile::writeTo (OutputStream& out) { out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd")); out.writeIntBigEndian (6); out.writeShortBigEndian (1); // type out.writeShortBigEndian ((short) tracks.size()); out.writeShortBigEndian (timeFormat); for (int i = 0; i < tracks.size(); ++i) writeTrack (out, i); out.flush(); return true; } void MidiFile::writeTrack (OutputStream& mainOut, const int trackNum) { MemoryOutputStream out; const MidiMessageSequence& ms = *tracks[trackNum]; int lastTick = 0; char lastStatusByte = 0; for (int i = 0; i < ms.getNumEvents(); ++i) { const MidiMessage& mm = ms.getEventPointer(i)->message; const int tick = roundToInt (mm.getTimeStamp()); const int delta = jmax (0, tick - lastTick); MidiFileHelpers::writeVariableLengthInt (out, delta); lastTick = tick; const char statusByte = *(mm.getRawData()); if ((statusByte == lastStatusByte) && ((statusByte & 0xf0) != 0xf0) && i > 0 && mm.getRawDataSize() > 1) { out.write (mm.getRawData() + 1, mm.getRawDataSize() - 1); } else { out.write (mm.getRawData(), mm.getRawDataSize()); } lastStatusByte = statusByte; } out.writeByte (0); const MidiMessage m (MidiMessage::endOfTrack()); out.write (m.getRawData(), m.getRawDataSize()); mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk")); mainOut.writeIntBigEndian ((int) out.getDataSize()); mainOut.write (out.getData(), (int) out.getDataSize()); } END_JUCE_NAMESPACE /*** End of inlined file: juce_MidiFile.cpp ***/ /*** Start of inlined file: juce_MidiKeyboardState.cpp ***/ BEGIN_JUCE_NAMESPACE MidiKeyboardState::MidiKeyboardState() { zerostruct (noteStates); } MidiKeyboardState::~MidiKeyboardState() { } void MidiKeyboardState::reset() { const ScopedLock sl (lock); zerostruct (noteStates); eventsToAdd.clear(); } bool MidiKeyboardState::isNoteOn (const int midiChannel, const int n) const throw() { jassert (midiChannel >= 0 && midiChannel <= 16); return ((unsigned int) n) < 128 && (noteStates[n] & (1 << (midiChannel - 1))) != 0; } bool MidiKeyboardState::isNoteOnForChannels (const int midiChannelMask, const int n) const throw() { return ((unsigned int) n) < 128 && (noteStates[n] & midiChannelMask) != 0; } void MidiKeyboardState::noteOn (const int midiChannel, const int midiNoteNumber, const float velocity) { jassert (midiChannel >= 0 && midiChannel <= 16); jassert (((unsigned int) midiNoteNumber) < 128); const ScopedLock sl (lock); if (((unsigned int) midiNoteNumber) < 128) { const int timeNow = (int) Time::getMillisecondCounter(); eventsToAdd.addEvent (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity), timeNow); eventsToAdd.clear (0, timeNow - 500); noteOnInternal (midiChannel, midiNoteNumber, velocity); } } void MidiKeyboardState::noteOnInternal (const int midiChannel, const int midiNoteNumber, const float velocity) { if (((unsigned int) midiNoteNumber) < 128) { noteStates [midiNoteNumber] |= (1 << (midiChannel - 1)); for (int i = listeners.size(); --i >= 0;) listeners.getUnchecked(i)->handleNoteOn (this, midiChannel, midiNoteNumber, velocity); } } void MidiKeyboardState::noteOff (const int midiChannel, const int midiNoteNumber) { const ScopedLock sl (lock); if (isNoteOn (midiChannel, midiNoteNumber)) { const int timeNow = (int) Time::getMillisecondCounter(); eventsToAdd.addEvent (MidiMessage::noteOff (midiChannel, midiNoteNumber), timeNow); eventsToAdd.clear (0, timeNow - 500); noteOffInternal (midiChannel, midiNoteNumber); } } void MidiKeyboardState::noteOffInternal (const int midiChannel, const int midiNoteNumber) { if (isNoteOn (midiChannel, midiNoteNumber)) { noteStates [midiNoteNumber] &= ~(1 << (midiChannel - 1)); for (int i = listeners.size(); --i >= 0;) listeners.getUnchecked(i)->handleNoteOff (this, midiChannel, midiNoteNumber); } } void MidiKeyboardState::allNotesOff (const int midiChannel) { const ScopedLock sl (lock); if (midiChannel <= 0) { for (int i = 1; i <= 16; ++i) allNotesOff (i); } else { for (int i = 0; i < 128; ++i) noteOff (midiChannel, i); } } void MidiKeyboardState::processNextMidiEvent (const MidiMessage& message) { if (message.isNoteOn()) { noteOnInternal (message.getChannel(), message.getNoteNumber(), message.getFloatVelocity()); } else if (message.isNoteOff()) { noteOffInternal (message.getChannel(), message.getNoteNumber()); } else if (message.isAllNotesOff()) { for (int i = 0; i < 128; ++i) noteOffInternal (message.getChannel(), i); } } void MidiKeyboardState::processNextMidiBuffer (MidiBuffer& buffer, const int startSample, const int numSamples, const bool injectIndirectEvents) { MidiBuffer::Iterator i (buffer); MidiMessage message (0xf4, 0.0); int time; const ScopedLock sl (lock); while (i.getNextEvent (message, time)) processNextMidiEvent (message); if (injectIndirectEvents) { MidiBuffer::Iterator i2 (eventsToAdd); const int firstEventToAdd = eventsToAdd.getFirstEventTime(); const double scaleFactor = numSamples / (double) (eventsToAdd.getLastEventTime() + 1 - firstEventToAdd); while (i2.getNextEvent (message, time)) { const int pos = jlimit (0, numSamples - 1, roundToInt ((time - firstEventToAdd) * scaleFactor)); buffer.addEvent (message, startSample + pos); } } eventsToAdd.clear(); } void MidiKeyboardState::addListener (MidiKeyboardStateListener* const listener) { const ScopedLock sl (lock); listeners.addIfNotAlreadyThere (listener); } void MidiKeyboardState::removeListener (MidiKeyboardStateListener* const listener) { const ScopedLock sl (lock); listeners.removeValue (listener); } END_JUCE_NAMESPACE /*** End of inlined file: juce_MidiKeyboardState.cpp ***/ /*** Start of inlined file: juce_MidiMessage.cpp ***/ BEGIN_JUCE_NAMESPACE int MidiMessage::readVariableLengthVal (const uint8* data, int& numBytesUsed) throw() { numBytesUsed = 0; int v = 0; int i; do { i = (int) *data++; if (++numBytesUsed > 6) break; v = (v << 7) + (i & 0x7f); } while (i & 0x80); return v; } int MidiMessage::getMessageLengthFromFirstByte (const uint8 firstByte) throw() { // this method only works for valid starting bytes of a short midi message jassert (firstByte >= 0x80 && firstByte != 0xf0 && firstByte != 0xf7); static const char messageLengths[] = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; return messageLengths [firstByte & 0x7f]; } MidiMessage::MidiMessage (const void* const d, const int dataSize, const double t) : timeStamp (t), size (dataSize) { jassert (dataSize > 0); if (dataSize <= 4) data = static_cast (preallocatedData.asBytes); else data = new uint8 [dataSize]; memcpy (data, d, dataSize); // check that the length matches the data.. jassert (size > 3 || data[0] >= 0xf0 || getMessageLengthFromFirstByte (data[0]) == size); } MidiMessage::MidiMessage (const int byte1, const double t) throw() : timeStamp (t), data (static_cast (preallocatedData.asBytes)), size (1) { data[0] = (uint8) byte1; // check that the length matches the data.. jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 1); } MidiMessage::MidiMessage (const int byte1, const int byte2, const double t) throw() : timeStamp (t), data (static_cast (preallocatedData.asBytes)), size (2) { data[0] = (uint8) byte1; data[1] = (uint8) byte2; // check that the length matches the data.. jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 2); } MidiMessage::MidiMessage (const int byte1, const int byte2, const int byte3, const double t) throw() : timeStamp (t), data (static_cast (preallocatedData.asBytes)), size (3) { data[0] = (uint8) byte1; data[1] = (uint8) byte2; data[2] = (uint8) byte3; // check that the length matches the data.. jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 3); } MidiMessage::MidiMessage (const MidiMessage& other) : timeStamp (other.timeStamp), size (other.size) { if (other.data != static_cast (other.preallocatedData.asBytes)) { data = new uint8 [size]; memcpy (data, other.data, size); } else { data = static_cast (preallocatedData.asBytes); preallocatedData.asInt32 = other.preallocatedData.asInt32; } } MidiMessage::MidiMessage (const MidiMessage& other, const double newTimeStamp) : timeStamp (newTimeStamp), size (other.size) { if (other.data != static_cast (other.preallocatedData.asBytes)) { data = new uint8 [size]; memcpy (data, other.data, size); } else { data = static_cast (preallocatedData.asBytes); preallocatedData.asInt32 = other.preallocatedData.asInt32; } } MidiMessage::MidiMessage (const void* src_, int sz, int& numBytesUsed, const uint8 lastStatusByte, double t) : timeStamp (t), data (static_cast (preallocatedData.asBytes)) { const uint8* src = static_cast (src_); unsigned int byte = (unsigned int) *src; if (byte < 0x80) { byte = (unsigned int) (uint8) lastStatusByte; numBytesUsed = -1; } else { numBytesUsed = 0; --sz; ++src; } if (byte >= 0x80) { if (byte == 0xf0) { const uint8* d = src; while (d < src + sz) { if (*d >= 0x80) // stop if we hit a status byte, and don't include it in this message { if (*d == 0xf7) // include an 0xf7 if we hit one ++d; break; } ++d; } size = 1 + (int) (d - src); data = new uint8 [size]; *data = (uint8) byte; memcpy (data + 1, src, size - 1); } else if (byte == 0xff) { int n; const int bytesLeft = readVariableLengthVal (src + 1, n); size = jmin (sz + 1, n + 2 + bytesLeft); data = new uint8 [size]; *data = (uint8) byte; memcpy (data + 1, src, size - 1); } else { preallocatedData.asInt32 = 0; size = getMessageLengthFromFirstByte ((uint8) byte); data[0] = (uint8) byte; if (size > 1) { data[1] = src[0]; if (size > 2) data[2] = src[1]; } } numBytesUsed += size; } else { preallocatedData.asInt32 = 0; size = 0; } } MidiMessage& MidiMessage::operator= (const MidiMessage& other) { if (this != &other) { timeStamp = other.timeStamp; size = other.size; if (data != static_cast (preallocatedData.asBytes)) delete[] data; if (other.data != static_cast (other.preallocatedData.asBytes)) { data = new uint8 [size]; memcpy (data, other.data, size); } else { data = static_cast (preallocatedData.asBytes); preallocatedData.asInt32 = other.preallocatedData.asInt32; } } return *this; } MidiMessage::~MidiMessage() { if (data != static_cast (preallocatedData.asBytes)) delete[] data; } int MidiMessage::getChannel() const throw() { if ((data[0] & 0xf0) != 0xf0) return (data[0] & 0xf) + 1; else return 0; } bool MidiMessage::isForChannel (const int channel) const throw() { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 return ((data[0] & 0xf) == channel - 1) && ((data[0] & 0xf0) != 0xf0); } void MidiMessage::setChannel (const int channel) throw() { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 if ((data[0] & 0xf0) != (uint8) 0xf0) data[0] = (uint8) ((data[0] & (uint8)0xf0) | (uint8)(channel - 1)); } bool MidiMessage::isNoteOn (const bool returnTrueForVelocity0) const throw() { return ((data[0] & 0xf0) == 0x90) && (returnTrueForVelocity0 || data[2] != 0); } bool MidiMessage::isNoteOff (const bool returnTrueForNoteOnVelocity0) const throw() { return ((data[0] & 0xf0) == 0x80) || (returnTrueForNoteOnVelocity0 && (data[2] == 0) && ((data[0] & 0xf0) == 0x90)); } bool MidiMessage::isNoteOnOrOff() const throw() { const int d = data[0] & 0xf0; return (d == 0x90) || (d == 0x80); } int MidiMessage::getNoteNumber() const throw() { return data[1]; } void MidiMessage::setNoteNumber (const int newNoteNumber) throw() { if (isNoteOnOrOff()) data[1] = (uint8) jlimit (0, 127, newNoteNumber); } uint8 MidiMessage::getVelocity() const throw() { if (isNoteOnOrOff()) return data[2]; else return 0; } float MidiMessage::getFloatVelocity() const throw() { return getVelocity() * (1.0f / 127.0f); } void MidiMessage::setVelocity (const float newVelocity) throw() { if (isNoteOnOrOff()) data[2] = (uint8) jlimit (0, 0x7f, roundToInt (newVelocity * 127.0f)); } void MidiMessage::multiplyVelocity (const float scaleFactor) throw() { if (isNoteOnOrOff()) data[2] = (uint8) jlimit (0, 0x7f, roundToInt (scaleFactor * data[2])); } bool MidiMessage::isAftertouch() const throw() { return (data[0] & 0xf0) == 0xa0; } int MidiMessage::getAfterTouchValue() const throw() { return data[2]; } const MidiMessage MidiMessage::aftertouchChange (const int channel, const int noteNum, const int aftertouchValue) throw() { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 jassert (((unsigned int) noteNum) <= 127); jassert (((unsigned int) aftertouchValue) <= 127); return MidiMessage (0xa0 | jlimit (0, 15, channel - 1), noteNum & 0x7f, aftertouchValue & 0x7f); } bool MidiMessage::isChannelPressure() const throw() { return (data[0] & 0xf0) == 0xd0; } int MidiMessage::getChannelPressureValue() const throw() { jassert (isChannelPressure()); return data[1]; } const MidiMessage MidiMessage::channelPressureChange (const int channel, const int pressure) throw() { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 jassert (((unsigned int) pressure) <= 127); return MidiMessage (0xd0 | jlimit (0, 15, channel - 1), pressure & 0x7f); } bool MidiMessage::isProgramChange() const throw() { return (data[0] & 0xf0) == 0xc0; } int MidiMessage::getProgramChangeNumber() const throw() { return data[1]; } const MidiMessage MidiMessage::programChange (const int channel, const int programNumber) throw() { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 return MidiMessage (0xc0 | jlimit (0, 15, channel - 1), programNumber & 0x7f); } bool MidiMessage::isPitchWheel() const throw() { return (data[0] & 0xf0) == 0xe0; } int MidiMessage::getPitchWheelValue() const throw() { return data[1] | (data[2] << 7); } const MidiMessage MidiMessage::pitchWheel (const int channel, const int position) throw() { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 jassert (((unsigned int) position) <= 0x3fff); return MidiMessage (0xe0 | jlimit (0, 15, channel - 1), position & 127, (position >> 7) & 127); } bool MidiMessage::isController() const throw() { return (data[0] & 0xf0) == 0xb0; } int MidiMessage::getControllerNumber() const throw() { jassert (isController()); return data[1]; } int MidiMessage::getControllerValue() const throw() { jassert (isController()); return data[2]; } const MidiMessage MidiMessage::controllerEvent (const int channel, const int controllerType, const int value) throw() { // the channel must be between 1 and 16 inclusive jassert (channel > 0 && channel <= 16); return MidiMessage (0xb0 | jlimit (0, 15, channel - 1), controllerType & 127, value & 127); } const MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const float velocity) throw() { return noteOn (channel, noteNumber, (uint8)(velocity * 127.0f)); } const MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const uint8 velocity) throw() { jassert (channel > 0 && channel <= 16); jassert (((unsigned int) noteNumber) <= 127); return MidiMessage (0x90 | jlimit (0, 15, channel - 1), noteNumber & 127, jlimit (0, 127, roundToInt (velocity))); } const MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber) throw() { jassert (channel > 0 && channel <= 16); jassert (((unsigned int) noteNumber) <= 127); return MidiMessage (0x80 | jlimit (0, 15, channel - 1), noteNumber & 127, 0); } const MidiMessage MidiMessage::allNotesOff (const int channel) throw() { return controllerEvent (channel, 123, 0); } bool MidiMessage::isAllNotesOff() const throw() { return (data[0] & 0xf0) == 0xb0 && data[1] == 123; } const MidiMessage MidiMessage::allSoundOff (const int channel) throw() { return controllerEvent (channel, 120, 0); } bool MidiMessage::isAllSoundOff() const throw() { return (data[0] & 0xf0) == 0xb0 && data[1] == 120; } const MidiMessage MidiMessage::allControllersOff (const int channel) throw() { return controllerEvent (channel, 121, 0); } const MidiMessage MidiMessage::masterVolume (const float volume) { const int vol = jlimit (0, 0x3fff, roundToInt (volume * 0x4000)); uint8 buf[8]; buf[0] = 0xf0; buf[1] = 0x7f; buf[2] = 0x7f; buf[3] = 0x04; buf[4] = 0x01; buf[5] = (uint8) (vol & 0x7f); buf[6] = (uint8) (vol >> 7); buf[7] = 0xf7; return MidiMessage (buf, 8); } bool MidiMessage::isSysEx() const throw() { return *data == 0xf0; } const MidiMessage MidiMessage::createSysExMessage (const uint8* sysexData, const int dataSize) { MemoryBlock mm (dataSize + 2); uint8* const m = static_cast (mm.getData()); m[0] = 0xf0; memcpy (m + 1, sysexData, dataSize); m[dataSize + 1] = 0xf7; return MidiMessage (m, dataSize + 2); } const uint8* MidiMessage::getSysExData() const throw() { return (isSysEx()) ? getRawData() + 1 : 0; } int MidiMessage::getSysExDataSize() const throw() { return (isSysEx()) ? size - 2 : 0; } bool MidiMessage::isMetaEvent() const throw() { return *data == 0xff; } bool MidiMessage::isActiveSense() const throw() { return *data == 0xfe; } int MidiMessage::getMetaEventType() const throw() { if (*data != 0xff) return -1; else return data[1]; } int MidiMessage::getMetaEventLength() const throw() { if (*data == 0xff) { int n; return jmin (size - 2, readVariableLengthVal (data + 2, n)); } return 0; } const uint8* MidiMessage::getMetaEventData() const throw() { int n; const uint8* d = data + 2; readVariableLengthVal (d, n); return d + n; } bool MidiMessage::isTrackMetaEvent() const throw() { return getMetaEventType() == 0; } bool MidiMessage::isEndOfTrackMetaEvent() const throw() { return getMetaEventType() == 47; } bool MidiMessage::isTextMetaEvent() const throw() { const int t = getMetaEventType(); return t > 0 && t < 16; } const String MidiMessage::getTextFromTextMetaEvent() const { return String (reinterpret_cast (getMetaEventData()), getMetaEventLength()); } bool MidiMessage::isTrackNameEvent() const throw() { return (data[1] == 3) && (*data == 0xff); } bool MidiMessage::isTempoMetaEvent() const throw() { return (data[1] == 81) && (*data == 0xff); } bool MidiMessage::isMidiChannelMetaEvent() const throw() { return (data[1] == 0x20) && (*data == 0xff) && (data[2] == 1); } int MidiMessage::getMidiChannelMetaEventChannel() const throw() { return data[3] + 1; } double MidiMessage::getTempoSecondsPerQuarterNote() const throw() { if (! isTempoMetaEvent()) return 0.0; const uint8* const d = getMetaEventData(); return (((unsigned int) d[0] << 16) | ((unsigned int) d[1] << 8) | d[2]) / 1000000.0; } double MidiMessage::getTempoMetaEventTickLength (const short timeFormat) const throw() { if (timeFormat > 0) { if (! isTempoMetaEvent()) return 0.5 / timeFormat; return getTempoSecondsPerQuarterNote() / timeFormat; } else { const int frameCode = (-timeFormat) >> 8; double framesPerSecond; switch (frameCode) { case 24: framesPerSecond = 24.0; break; case 25: framesPerSecond = 25.0; break; case 29: framesPerSecond = 29.97; break; case 30: framesPerSecond = 30.0; break; default: framesPerSecond = 30.0; break; } return (1.0 / framesPerSecond) / (timeFormat & 0xff); } } const MidiMessage MidiMessage::tempoMetaEvent (int microsecondsPerQuarterNote) throw() { uint8 d[8]; d[0] = 0xff; d[1] = 81; d[2] = 3; d[3] = (uint8) (microsecondsPerQuarterNote >> 16); d[4] = (uint8) ((microsecondsPerQuarterNote >> 8) & 0xff); d[5] = (uint8) (microsecondsPerQuarterNote & 0xff); return MidiMessage (d, 6, 0.0); } bool MidiMessage::isTimeSignatureMetaEvent() const throw() { return (data[1] == 0x58) && (*data == (uint8) 0xff); } void MidiMessage::getTimeSignatureInfo (int& numerator, int& denominator) const throw() { if (isTimeSignatureMetaEvent()) { const uint8* const d = getMetaEventData(); numerator = d[0]; denominator = 1 << d[1]; } else { numerator = 4; denominator = 4; } } const MidiMessage MidiMessage::timeSignatureMetaEvent (const int numerator, const int denominator) { uint8 d[8]; d[0] = 0xff; d[1] = 0x58; d[2] = 0x04; d[3] = (uint8) numerator; int n = 1; int powerOfTwo = 0; while (n < denominator) { n <<= 1; ++powerOfTwo; } d[4] = (uint8) powerOfTwo; d[5] = 0x01; d[6] = 96; return MidiMessage (d, 7, 0.0); } const MidiMessage MidiMessage::midiChannelMetaEvent (const int channel) throw() { uint8 d[8]; d[0] = 0xff; d[1] = 0x20; d[2] = 0x01; d[3] = (uint8) jlimit (0, 0xff, channel - 1); return MidiMessage (d, 4, 0.0); } bool MidiMessage::isKeySignatureMetaEvent() const throw() { return getMetaEventType() == 89; } int MidiMessage::getKeySignatureNumberOfSharpsOrFlats() const throw() { return (int) *getMetaEventData(); } const MidiMessage MidiMessage::endOfTrack() throw() { return MidiMessage (0xff, 0x2f, 0, 0.0); } bool MidiMessage::isSongPositionPointer() const throw() { return *data == 0xf2; } int MidiMessage::getSongPositionPointerMidiBeat() const throw() { return data[1] | (data[2] << 7); } const MidiMessage MidiMessage::songPositionPointer (const int positionInMidiBeats) throw() { return MidiMessage (0xf2, positionInMidiBeats & 127, (positionInMidiBeats >> 7) & 127); } bool MidiMessage::isMidiStart() const throw() { return *data == 0xfa; } const MidiMessage MidiMessage::midiStart() throw() { return MidiMessage (0xfa); } bool MidiMessage::isMidiContinue() const throw() { return *data == 0xfb; } const MidiMessage MidiMessage::midiContinue() throw() { return MidiMessage (0xfb); } bool MidiMessage::isMidiStop() const throw() { return *data == 0xfc; } const MidiMessage MidiMessage::midiStop() throw() { return MidiMessage (0xfc); } bool MidiMessage::isMidiClock() const throw() { return *data == 0xf8; } const MidiMessage MidiMessage::midiClock() throw() { return MidiMessage (0xf8); } bool MidiMessage::isQuarterFrame() const throw() { return *data == 0xf1; } int MidiMessage::getQuarterFrameSequenceNumber() const throw() { return ((int) data[1]) >> 4; } int MidiMessage::getQuarterFrameValue() const throw() { return ((int) data[1]) & 0x0f; } const MidiMessage MidiMessage::quarterFrame (const int sequenceNumber, const int value) throw() { return MidiMessage (0xf1, (sequenceNumber << 4) | value); } bool MidiMessage::isFullFrame() const throw() { return data[0] == 0xf0 && data[1] == 0x7f && size >= 10 && data[3] == 0x01 && data[4] == 0x01; } void MidiMessage::getFullFrameParameters (int& hours, int& minutes, int& seconds, int& frames, MidiMessage::SmpteTimecodeType& timecodeType) const throw() { jassert (isFullFrame()); timecodeType = (SmpteTimecodeType) (data[5] >> 5); hours = data[5] & 0x1f; minutes = data[6]; seconds = data[7]; frames = data[8]; } const MidiMessage MidiMessage::fullFrame (const int hours, const int minutes, const int seconds, const int frames, MidiMessage::SmpteTimecodeType timecodeType) { uint8 d[10]; d[0] = 0xf0; d[1] = 0x7f; d[2] = 0x7f; d[3] = 0x01; d[4] = 0x01; d[5] = (uint8) ((hours & 0x01f) | (timecodeType << 5)); d[6] = (uint8) minutes; d[7] = (uint8) seconds; d[8] = (uint8) frames; d[9] = 0xf7; return MidiMessage (d, 10, 0.0); } bool MidiMessage::isMidiMachineControlMessage() const throw() { return data[0] == 0xf0 && data[1] == 0x7f && data[3] == 0x06 && size > 5; } MidiMessage::MidiMachineControlCommand MidiMessage::getMidiMachineControlCommand() const throw() { jassert (isMidiMachineControlMessage()); return (MidiMachineControlCommand) data[4]; } const MidiMessage MidiMessage::midiMachineControlCommand (MidiMessage::MidiMachineControlCommand command) { uint8 d[6]; d[0] = 0xf0; d[1] = 0x7f; d[2] = 0x00; d[3] = 0x06; d[4] = (uint8) command; d[5] = 0xf7; return MidiMessage (d, 6, 0.0); } bool MidiMessage::isMidiMachineControlGoto (int& hours, int& minutes, int& seconds, int& frames) const throw() { if (size >= 12 && data[0] == 0xf0 && data[1] == 0x7f && data[3] == 0x06 && data[4] == 0x44 && data[5] == 0x06 && data[6] == 0x01) { hours = data[7] % 24; // (that some machines send out hours > 24) minutes = data[8]; seconds = data[9]; frames = data[10]; return true; } return false; } const MidiMessage MidiMessage::midiMachineControlGoto (int hours, int minutes, int seconds, int frames) { uint8 d[12]; d[0] = 0xf0; d[1] = 0x7f; d[2] = 0x00; d[3] = 0x06; d[4] = 0x44; d[5] = 0x06; d[6] = 0x01; d[7] = (uint8) hours; d[8] = (uint8) minutes; d[9] = (uint8) seconds; d[10] = (uint8) frames; d[11] = 0xf7; return MidiMessage (d, 12, 0.0); } const String MidiMessage::getMidiNoteName (int note, bool useSharps, bool includeOctaveNumber, int octaveNumForMiddleC) { static const char* const sharpNoteNames[] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; static const char* const flatNoteNames[] = { "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B" }; if (((unsigned int) note) < 128) { String s (useSharps ? sharpNoteNames [note % 12] : flatNoteNames [note % 12]); if (includeOctaveNumber) s << (note / 12 + (octaveNumForMiddleC - 5)); return s; } return String::empty; } const double MidiMessage::getMidiNoteInHertz (int noteNumber) throw() { noteNumber -= 12 * 6 + 9; // now 0 = A440 return 440.0 * pow (2.0, noteNumber / 12.0); } const String MidiMessage::getGMInstrumentName (const int n) { const char *names[] = { "Acoustic Grand Piano", "Bright Acoustic Piano", "Electric Grand Piano", "Honky-tonk Piano", "Electric Piano 1", "Electric Piano 2", "Harpsichord", "Clavinet", "Celesta", "Glockenspiel", "Music Box", "Vibraphone", "Marimba", "Xylophone", "Tubular Bells", "Dulcimer", "Drawbar Organ", "Percussive Organ", "Rock Organ", "Church Organ", "Reed Organ", "Accordion", "Harmonica", "Tango Accordion", "Acoustic Guitar (nylon)", "Acoustic Guitar (steel)", "Electric Guitar (jazz)", "Electric Guitar (clean)", "Electric Guitar (mute)", "Overdriven Guitar", "Distortion Guitar", "Guitar Harmonics", "Acoustic Bass", "Electric Bass (finger)", "Electric Bass (pick)", "Fretless Bass", "Slap Bass 1", "Slap Bass 2", "Synth Bass 1", "Synth Bass 2", "Violin", "Viola", "Cello", "Contrabass", "Tremolo Strings", "Pizzicato Strings", "Orchestral Harp", "Timpani", "String Ensemble 1", "String Ensemble 2", "SynthStrings 1", "SynthStrings 2", "Choir Aahs", "Voice Oohs", "Synth Voice", "Orchestra Hit", "Trumpet", "Trombone", "Tuba", "Muted Trumpet", "French Horn", "Brass Section", "SynthBrass 1", "SynthBrass 2", "Soprano Sax", "Alto Sax", "Tenor Sax", "Baritone Sax", "Oboe", "English Horn", "Bassoon", "Clarinet", "Piccolo", "Flute", "Recorder", "Pan Flute", "Blown Bottle", "Shakuhachi", "Whistle", "Ocarina", "Lead 1 (square)", "Lead 2 (sawtooth)", "Lead 3 (calliope)", "Lead 4 (chiff)", "Lead 5 (charang)", "Lead 6 (voice)", "Lead 7 (fifths)", "Lead 8 (bass+lead)", "Pad 1 (new age)", "Pad 2 (warm)", "Pad 3 (polysynth)", "Pad 4 (choir)", "Pad 5 (bowed)", "Pad 6 (metallic)", "Pad 7 (halo)", "Pad 8 (sweep)", "FX 1 (rain)", "FX 2 (soundtrack)", "FX 3 (crystal)", "FX 4 (atmosphere)", "FX 5 (brightness)", "FX 6 (goblins)", "FX 7 (echoes)", "FX 8 (sci-fi)", "Sitar", "Banjo", "Shamisen", "Koto", "Kalimba", "Bag pipe", "Fiddle", "Shanai", "Tinkle Bell", "Agogo", "Steel Drums", "Woodblock", "Taiko Drum", "Melodic Tom", "Synth Drum", "Reverse Cymbal", "Guitar Fret Noise", "Breath Noise", "Seashore", "Bird Tweet", "Telephone Ring", "Helicopter", "Applause", "Gunshot" }; return (((unsigned int) n) < 128) ? names[n] : (const char*) 0; } const String MidiMessage::getGMInstrumentBankName (const int n) { const char* names[] = { "Piano", "Chromatic Percussion", "Organ", "Guitar", "Bass", "Strings", "Ensemble", "Brass", "Reed", "Pipe", "Synth Lead", "Synth Pad", "Synth Effects", "Ethnic", "Percussive", "Sound Effects" }; return (((unsigned int) n) <= 15) ? names[n] : (const char*) 0; } const String MidiMessage::getRhythmInstrumentName (const int n) { const char* names[] = { "Acoustic Bass Drum", "Bass Drum 1", "Side Stick", "Acoustic Snare", "Hand Clap", "Electric Snare", "Low Floor Tom", "Closed Hi-Hat", "High Floor Tom", "Pedal Hi-Hat", "Low Tom", "Open Hi-Hat", "Low-Mid Tom", "Hi-Mid Tom", "Crash Cymbal 1", "High Tom", "Ride Cymbal 1", "Chinese Cymbal", "Ride Bell", "Tambourine", "Splash Cymbal", "Cowbell", "Crash Cymbal 2", "Vibraslap", "Ride Cymbal 2", "Hi Bongo", "Low Bongo", "Mute Hi Conga", "Open Hi Conga", "Low Conga", "High Timbale", "Low Timbale", "High Agogo", "Low Agogo", "Cabasa", "Maracas", "Short Whistle", "Long Whistle", "Short Guiro", "Long Guiro", "Claves", "Hi Wood Block", "Low Wood Block", "Mute Cuica", "Open Cuica", "Mute Triangle", "Open Triangle" }; return (n >= 35 && n <= 81) ? names [n - 35] : (const char*) 0; } const String MidiMessage::getControllerName (const int n) { const char* names[] = { "Bank Select", "Modulation Wheel (coarse)", "Breath controller (coarse)", 0, "Foot Pedal (coarse)", "Portamento Time (coarse)", "Data Entry (coarse)", "Volume (coarse)", "Balance (coarse)", 0, "Pan position (coarse)", "Expression (coarse)", "Effect Control 1 (coarse)", "Effect Control 2 (coarse)", 0, 0, "General Purpose Slider 1", "General Purpose Slider 2", "General Purpose Slider 3", "General Purpose Slider 4", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Bank Select (fine)", "Modulation Wheel (fine)", "Breath controller (fine)", 0, "Foot Pedal (fine)", "Portamento Time (fine)", "Data Entry (fine)", "Volume (fine)", "Balance (fine)", 0, "Pan position (fine)", "Expression (fine)", "Effect Control 1 (fine)", "Effect Control 2 (fine)", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Hold Pedal (on/off)", "Portamento (on/off)", "Sustenuto Pedal (on/off)", "Soft Pedal (on/off)", "Legato Pedal (on/off)", "Hold 2 Pedal (on/off)", "Sound Variation", "Sound Timbre", "Sound Release Time", "Sound Attack Time", "Sound Brightness", "Sound Control 6", "Sound Control 7", "Sound Control 8", "Sound Control 9", "Sound Control 10", "General Purpose Button 1 (on/off)", "General Purpose Button 2 (on/off)", "General Purpose Button 3 (on/off)", "General Purpose Button 4 (on/off)", 0, 0, 0, 0, 0, 0, 0, "Reverb Level", "Tremolo Level", "Chorus Level", "Celeste Level", "Phaser Level", "Data Button increment", "Data Button decrement", "Non-registered Parameter (fine)", "Non-registered Parameter (coarse)", "Registered Parameter (fine)", "Registered Parameter (coarse)", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "All Sound Off", "All Controllers Off", "Local Keyboard (on/off)", "All Notes Off", "Omni Mode Off", "Omni Mode On", "Mono Operation", "Poly Operation" }; return (((unsigned int) n) < 128) ? names[n] : (const char*) 0; } END_JUCE_NAMESPACE /*** End of inlined file: juce_MidiMessage.cpp ***/ /*** Start of inlined file: juce_MidiMessageCollector.cpp ***/ BEGIN_JUCE_NAMESPACE MidiMessageCollector::MidiMessageCollector() : lastCallbackTime (0), sampleRate (44100.0001) { } MidiMessageCollector::~MidiMessageCollector() { } void MidiMessageCollector::reset (const double sampleRate_) { jassert (sampleRate_ > 0); const ScopedLock sl (midiCallbackLock); sampleRate = sampleRate_; incomingMessages.clear(); lastCallbackTime = Time::getMillisecondCounterHiRes(); } void MidiMessageCollector::addMessageToQueue (const MidiMessage& message) { // you need to call reset() to set the correct sample rate before using this object jassert (sampleRate != 44100.0001); // the messages that come in here need to be time-stamped correctly - see MidiInput // for details of what the number should be. jassert (message.getTimeStamp() != 0); const ScopedLock sl (midiCallbackLock); const int sampleNumber = (int) ((message.getTimeStamp() - 0.001 * lastCallbackTime) * sampleRate); incomingMessages.addEvent (message, sampleNumber); // if the messages don't get used for over a second, we'd better // get rid of any old ones to avoid the queue getting too big if (sampleNumber > sampleRate) incomingMessages.clear (0, sampleNumber - (int) sampleRate); } void MidiMessageCollector::removeNextBlockOfMessages (MidiBuffer& destBuffer, const int numSamples) { // you need to call reset() to set the correct sample rate before using this object jassert (sampleRate != 44100.0001); const double timeNow = Time::getMillisecondCounterHiRes(); const double msElapsed = timeNow - lastCallbackTime; const ScopedLock sl (midiCallbackLock); lastCallbackTime = timeNow; if (! incomingMessages.isEmpty()) { int numSourceSamples = jmax (1, roundToInt (msElapsed * 0.001 * sampleRate)); int startSample = 0; int scale = 1 << 16; const uint8* midiData; int numBytes, samplePosition; MidiBuffer::Iterator iter (incomingMessages); if (numSourceSamples > numSamples) { // if our list of events is longer than the buffer we're being // asked for, scale them down to squeeze them all in.. const int maxBlockLengthToUse = numSamples << 5; if (numSourceSamples > maxBlockLengthToUse) { startSample = numSourceSamples - maxBlockLengthToUse; numSourceSamples = maxBlockLengthToUse; iter.setNextSamplePosition (startSample); } scale = (numSamples << 10) / numSourceSamples; while (iter.getNextEvent (midiData, numBytes, samplePosition)) { samplePosition = ((samplePosition - startSample) * scale) >> 10; destBuffer.addEvent (midiData, numBytes, jlimit (0, numSamples - 1, samplePosition)); } } else { // if our event list is shorter than the number we need, put them // towards the end of the buffer startSample = numSamples - numSourceSamples; while (iter.getNextEvent (midiData, numBytes, samplePosition)) { destBuffer.addEvent (midiData, numBytes, jlimit (0, numSamples - 1, samplePosition + startSample)); } } incomingMessages.clear(); } } void MidiMessageCollector::handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) { MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity)); m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001); addMessageToQueue (m); } void MidiMessageCollector::handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber) { MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber)); m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001); addMessageToQueue (m); } void MidiMessageCollector::handleIncomingMidiMessage (MidiInput*, const MidiMessage& message) { addMessageToQueue (message); } END_JUCE_NAMESPACE /*** End of inlined file: juce_MidiMessageCollector.cpp ***/ /*** Start of inlined file: juce_MidiMessageSequence.cpp ***/ BEGIN_JUCE_NAMESPACE MidiMessageSequence::MidiMessageSequence() { } MidiMessageSequence::MidiMessageSequence (const MidiMessageSequence& other) { list.ensureStorageAllocated (other.list.size()); for (int i = 0; i < other.list.size(); ++i) list.add (new MidiEventHolder (other.list.getUnchecked(i)->message)); } MidiMessageSequence& MidiMessageSequence::operator= (const MidiMessageSequence& other) { MidiMessageSequence otherCopy (other); swapWith (otherCopy); return *this; } void MidiMessageSequence::swapWith (MidiMessageSequence& other) throw() { list.swapWithArray (other.list); } MidiMessageSequence::~MidiMessageSequence() { } void MidiMessageSequence::clear() { list.clear(); } int MidiMessageSequence::getNumEvents() const { return list.size(); } MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (const int index) const { return list [index]; } double MidiMessageSequence::getTimeOfMatchingKeyUp (const int index) const { const MidiEventHolder* const meh = list [index]; if (meh != 0 && meh->noteOffObject != 0) return meh->noteOffObject->message.getTimeStamp(); else return 0.0; } int MidiMessageSequence::getIndexOfMatchingKeyUp (const int index) const { const MidiEventHolder* const meh = list [index]; return (meh != 0) ? list.indexOf (meh->noteOffObject) : -1; } int MidiMessageSequence::getIndexOf (MidiEventHolder* const event) const { return list.indexOf (event); } int MidiMessageSequence::getNextIndexAtTime (const double timeStamp) const { const int numEvents = list.size(); int i; for (i = 0; i < numEvents; ++i) if (list.getUnchecked(i)->message.getTimeStamp() >= timeStamp) break; return i; } double MidiMessageSequence::getStartTime() const { if (list.size() > 0) return list.getUnchecked(0)->message.getTimeStamp(); else return 0; } double MidiMessageSequence::getEndTime() const { if (list.size() > 0) return list.getLast()->message.getTimeStamp(); else return 0; } double MidiMessageSequence::getEventTime (const int index) const { if (((unsigned int) index) < (unsigned int) list.size()) return list.getUnchecked (index)->message.getTimeStamp(); return 0.0; } void MidiMessageSequence::addEvent (const MidiMessage& newMessage, double timeAdjustment) { MidiEventHolder* const newOne = new MidiEventHolder (newMessage); timeAdjustment += newMessage.getTimeStamp(); newOne->message.setTimeStamp (timeAdjustment); int i; for (i = list.size(); --i >= 0;) if (list.getUnchecked(i)->message.getTimeStamp() <= timeAdjustment) break; list.insert (i + 1, newOne); } void MidiMessageSequence::deleteEvent (const int index, const bool deleteMatchingNoteUp) { if (((unsigned int) index) < (unsigned int) list.size()) { if (deleteMatchingNoteUp) deleteEvent (getIndexOfMatchingKeyUp (index), false); list.remove (index); } } void MidiMessageSequence::addSequence (const MidiMessageSequence& other, double timeAdjustment, double firstAllowableTime, double endOfAllowableDestTimes) { firstAllowableTime -= timeAdjustment; endOfAllowableDestTimes -= timeAdjustment; for (int i = 0; i < other.list.size(); ++i) { const MidiMessage& m = other.list.getUnchecked(i)->message; const double t = m.getTimeStamp(); if (t >= firstAllowableTime && t < endOfAllowableDestTimes) { MidiEventHolder* const newOne = new MidiEventHolder (m); newOne->message.setTimeStamp (timeAdjustment + t); list.add (newOne); } } sort(); } int MidiMessageSequence::compareElements (const MidiMessageSequence::MidiEventHolder* const first, const MidiMessageSequence::MidiEventHolder* const second) throw() { const double diff = first->message.getTimeStamp() - second->message.getTimeStamp(); return (diff > 0) - (diff < 0); } void MidiMessageSequence::sort() { list.sort (*this, true); } void MidiMessageSequence::updateMatchedPairs() { for (int i = 0; i < list.size(); ++i) { const MidiMessage& m1 = list.getUnchecked(i)->message; if (m1.isNoteOn()) { list.getUnchecked(i)->noteOffObject = 0; const int note = m1.getNoteNumber(); const int chan = m1.getChannel(); const int len = list.size(); for (int j = i + 1; j < len; ++j) { const MidiMessage& m = list.getUnchecked(j)->message; if (m.getNoteNumber() == note && m.getChannel() == chan) { if (m.isNoteOff()) { list.getUnchecked(i)->noteOffObject = list[j]; break; } else if (m.isNoteOn()) { list.insert (j, new MidiEventHolder (MidiMessage::noteOff (chan, note))); list.getUnchecked(j)->message.setTimeStamp (m.getTimeStamp()); list.getUnchecked(i)->noteOffObject = list[j]; break; } } } } } } void MidiMessageSequence::addTimeToMessages (const double delta) { for (int i = list.size(); --i >= 0;) list.getUnchecked (i)->message.setTimeStamp (list.getUnchecked (i)->message.getTimeStamp() + delta); } void MidiMessageSequence::extractMidiChannelMessages (const int channelNumberToExtract, MidiMessageSequence& destSequence, const bool alsoIncludeMetaEvents) const { for (int i = 0; i < list.size(); ++i) { const MidiMessage& mm = list.getUnchecked(i)->message; if (mm.isForChannel (channelNumberToExtract) || (alsoIncludeMetaEvents && mm.isMetaEvent())) { destSequence.addEvent (mm); } } } void MidiMessageSequence::extractSysExMessages (MidiMessageSequence& destSequence) const { for (int i = 0; i < list.size(); ++i) { const MidiMessage& mm = list.getUnchecked(i)->message; if (mm.isSysEx()) destSequence.addEvent (mm); } } void MidiMessageSequence::deleteMidiChannelMessages (const int channelNumberToRemove) { for (int i = list.size(); --i >= 0;) if (list.getUnchecked(i)->message.isForChannel (channelNumberToRemove)) list.remove(i); } void MidiMessageSequence::deleteSysExMessages() { for (int i = list.size(); --i >= 0;) if (list.getUnchecked(i)->message.isSysEx()) list.remove(i); } void MidiMessageSequence::createControllerUpdatesForTime (const int channelNumber, const double time, OwnedArray& dest) { bool doneProg = false; bool donePitchWheel = false; Array doneControllers; doneControllers.ensureStorageAllocated (32); for (int i = list.size(); --i >= 0;) { const MidiMessage& mm = list.getUnchecked(i)->message; if (mm.isForChannel (channelNumber) && mm.getTimeStamp() <= time) { if (mm.isProgramChange()) { if (! doneProg) { dest.add (new MidiMessage (mm, 0.0)); doneProg = true; } } else if (mm.isController()) { if (! doneControllers.contains (mm.getControllerNumber())) { dest.add (new MidiMessage (mm, 0.0)); doneControllers.add (mm.getControllerNumber()); } } else if (mm.isPitchWheel()) { if (! donePitchWheel) { dest.add (new MidiMessage (mm, 0.0)); donePitchWheel = true; } } } } } MidiMessageSequence::MidiEventHolder::MidiEventHolder (const MidiMessage& message_) : message (message_), noteOffObject (0) { } MidiMessageSequence::MidiEventHolder::~MidiEventHolder() { } END_JUCE_NAMESPACE /*** End of inlined file: juce_MidiMessageSequence.cpp ***/ /*** Start of inlined file: juce_AudioPluginFormat.cpp ***/ BEGIN_JUCE_NAMESPACE AudioPluginFormat::AudioPluginFormat() throw() { } AudioPluginFormat::~AudioPluginFormat() { } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioPluginFormat.cpp ***/ /*** Start of inlined file: juce_AudioPluginFormatManager.cpp ***/ BEGIN_JUCE_NAMESPACE AudioPluginFormatManager::AudioPluginFormatManager() { } AudioPluginFormatManager::~AudioPluginFormatManager() { clearSingletonInstance(); } juce_ImplementSingleton_SingleThreaded (AudioPluginFormatManager); void AudioPluginFormatManager::addDefaultFormats() { #if JUCE_DEBUG // you should only call this method once! for (int i = formats.size(); --i >= 0;) { #if JUCE_PLUGINHOST_VST && ! (JUCE_MAC && JUCE_64BIT) jassert (dynamic_cast (formats[i]) == 0); #endif #if JUCE_PLUGINHOST_AU && JUCE_MAC jassert (dynamic_cast (formats[i]) == 0); #endif #if JUCE_PLUGINHOST_DX && JUCE_WINDOWS jassert (dynamic_cast (formats[i]) == 0); #endif #if JUCE_PLUGINHOST_LADSPA && JUCE_LINUX jassert (dynamic_cast (formats[i]) == 0); #endif } #endif #if JUCE_PLUGINHOST_AU && JUCE_MAC formats.add (new AudioUnitPluginFormat()); #endif #if JUCE_PLUGINHOST_VST && ! (JUCE_MAC && JUCE_64BIT) formats.add (new VSTPluginFormat()); #endif #if JUCE_PLUGINHOST_DX && JUCE_WINDOWS formats.add (new DirectXPluginFormat()); #endif #if JUCE_PLUGINHOST_LADSPA && JUCE_LINUX formats.add (new LADSPAPluginFormat()); #endif } int AudioPluginFormatManager::getNumFormats() { return formats.size(); } AudioPluginFormat* AudioPluginFormatManager::getFormat (const int index) { return formats [index]; } void AudioPluginFormatManager::addFormat (AudioPluginFormat* const format) { formats.add (format); } AudioPluginInstance* AudioPluginFormatManager::createPluginInstance (const PluginDescription& description, String& errorMessage) const { AudioPluginInstance* result = 0; for (int i = 0; i < formats.size(); ++i) { result = formats.getUnchecked(i)->createInstanceFromDescription (description); if (result != 0) break; } if (result == 0) { if (! doesPluginStillExist (description)) errorMessage = TRANS ("This plug-in file no longer exists"); else errorMessage = TRANS ("This plug-in failed to load correctly"); } return result; } bool AudioPluginFormatManager::doesPluginStillExist (const PluginDescription& description) const { for (int i = 0; i < formats.size(); ++i) if (formats.getUnchecked(i)->getName() == description.pluginFormatName) return formats.getUnchecked(i)->doesPluginStillExist (description); return false; } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioPluginFormatManager.cpp ***/ /*** Start of inlined file: juce_AudioPluginInstance.cpp ***/ #define JUCE_PLUGIN_HOST 1 BEGIN_JUCE_NAMESPACE AudioPluginInstance::AudioPluginInstance() { } AudioPluginInstance::~AudioPluginInstance() { } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioPluginInstance.cpp ***/ /*** Start of inlined file: juce_KnownPluginList.cpp ***/ BEGIN_JUCE_NAMESPACE KnownPluginList::KnownPluginList() { } KnownPluginList::~KnownPluginList() { } void KnownPluginList::clear() { if (types.size() > 0) { types.clear(); sendChangeMessage (this); } } PluginDescription* KnownPluginList::getTypeForFile (const String& fileOrIdentifier) const { for (int i = 0; i < types.size(); ++i) if (types.getUnchecked(i)->fileOrIdentifier == fileOrIdentifier) return types.getUnchecked(i); return 0; } PluginDescription* KnownPluginList::getTypeForIdentifierString (const String& identifierString) const { for (int i = 0; i < types.size(); ++i) if (types.getUnchecked(i)->createIdentifierString() == identifierString) return types.getUnchecked(i); return 0; } bool KnownPluginList::addType (const PluginDescription& type) { for (int i = types.size(); --i >= 0;) { if (types.getUnchecked(i)->isDuplicateOf (type)) { // strange - found a duplicate plugin with different info.. jassert (types.getUnchecked(i)->name == type.name); jassert (types.getUnchecked(i)->isInstrument == type.isInstrument); *types.getUnchecked(i) = type; return false; } } types.add (new PluginDescription (type)); sendChangeMessage (this); return true; } void KnownPluginList::removeType (const int index) { types.remove (index); sendChangeMessage (this); } static const Time getPluginFileModTime (const String& fileOrIdentifier) { if (fileOrIdentifier.startsWithChar ('/') || fileOrIdentifier[1] == ':') return File (fileOrIdentifier).getLastModificationTime(); return Time (0); } static bool timesAreDifferent (const Time& t1, const Time& t2) throw() { return t1 != t2 || t1 == Time (0); } bool KnownPluginList::isListingUpToDate (const String& fileOrIdentifier) const { if (getTypeForFile (fileOrIdentifier) == 0) return false; for (int i = types.size(); --i >= 0;) { const PluginDescription* const d = types.getUnchecked(i); if (d->fileOrIdentifier == fileOrIdentifier && timesAreDifferent (d->lastFileModTime, getPluginFileModTime (fileOrIdentifier))) { return false; } } return true; } bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, const bool dontRescanIfAlreadyInList, OwnedArray & typesFound, AudioPluginFormat& format) { bool addedOne = false; if (dontRescanIfAlreadyInList && getTypeForFile (fileOrIdentifier) != 0) { bool needsRescanning = false; for (int i = types.size(); --i >= 0;) { const PluginDescription* const d = types.getUnchecked(i); if (d->fileOrIdentifier == fileOrIdentifier) { if (timesAreDifferent (d->lastFileModTime, getPluginFileModTime (fileOrIdentifier))) needsRescanning = true; else typesFound.add (new PluginDescription (*d)); } } if (! needsRescanning) return false; } OwnedArray found; format.findAllTypesForFile (found, fileOrIdentifier); for (int i = 0; i < found.size(); ++i) { PluginDescription* const desc = found.getUnchecked(i); jassert (desc != 0); if (addType (*desc)) addedOne = true; typesFound.add (new PluginDescription (*desc)); } return addedOne; } void KnownPluginList::scanAndAddDragAndDroppedFiles (const StringArray& files, OwnedArray & typesFound) { for (int i = 0; i < files.size(); ++i) { bool loaded = false; for (int j = 0; j < AudioPluginFormatManager::getInstance()->getNumFormats(); ++j) { AudioPluginFormat* const format = AudioPluginFormatManager::getInstance()->getFormat (j); if (scanAndAddFile (files[i], true, typesFound, *format)) loaded = true; } if (! loaded) { const File f (files[i]); if (f.isDirectory()) { StringArray s; { Array subFiles; f.findChildFiles (subFiles, File::findFilesAndDirectories, false); for (int j = 0; j < subFiles.size(); ++j) s.add (subFiles.getReference(j).getFullPathName()); } scanAndAddDragAndDroppedFiles (s, typesFound); } } } } class PluginSorter { public: KnownPluginList::SortMethod method; PluginSorter() throw() {} int compareElements (const PluginDescription* const first, const PluginDescription* const second) const { int diff = 0; if (method == KnownPluginList::sortByCategory) diff = first->category.compareLexicographically (second->category); else if (method == KnownPluginList::sortByManufacturer) diff = first->manufacturerName.compareLexicographically (second->manufacturerName); else if (method == KnownPluginList::sortByFileSystemLocation) diff = first->fileOrIdentifier.replaceCharacter ('\\', '/') .upToLastOccurrenceOf ("/", false, false) .compare (second->fileOrIdentifier.replaceCharacter ('\\', '/') .upToLastOccurrenceOf ("/", false, false)); if (diff == 0) diff = first->name.compareLexicographically (second->name); return diff; } }; void KnownPluginList::sort (const SortMethod method) { if (method != defaultOrder) { PluginSorter sorter; sorter.method = method; types.sort (sorter, true); sendChangeMessage (this); } } XmlElement* KnownPluginList::createXml() const { XmlElement* const e = new XmlElement ("KNOWNPLUGINS"); for (int i = 0; i < types.size(); ++i) e->addChildElement (types.getUnchecked(i)->createXml()); return e; } void KnownPluginList::recreateFromXml (const XmlElement& xml) { clear(); if (xml.hasTagName ("KNOWNPLUGINS")) { forEachXmlChildElement (xml, e) { PluginDescription info; if (info.loadFromXml (*e)) addType (info); } } } const int menuIdBase = 0x324503f4; // This is used to turn a bunch of paths into a nested menu structure. struct PluginFilesystemTree { private: String folder; OwnedArray subFolders; Array plugins; void addPlugin (PluginDescription* const pd, const String& path) { if (path.isEmpty()) { plugins.add (pd); } else { const String firstSubFolder (path.upToFirstOccurrenceOf ("/", false, false)); const String remainingPath (path.fromFirstOccurrenceOf ("/", false, false)); for (int i = subFolders.size(); --i >= 0;) { if (subFolders.getUnchecked(i)->folder.equalsIgnoreCase (firstSubFolder)) { subFolders.getUnchecked(i)->addPlugin (pd, remainingPath); return; } } PluginFilesystemTree* const newFolder = new PluginFilesystemTree(); newFolder->folder = firstSubFolder; subFolders.add (newFolder); newFolder->addPlugin (pd, remainingPath); } } // removes any deeply nested folders that don't contain any actual plugins void optimise() { for (int i = subFolders.size(); --i >= 0;) { PluginFilesystemTree* const sub = subFolders.getUnchecked(i); sub->optimise(); if (sub->plugins.size() == 0) { for (int j = 0; j < sub->subFolders.size(); ++j) subFolders.add (sub->subFolders.getUnchecked(j)); sub->subFolders.clear (false); subFolders.remove (i); } } } public: void buildTree (const Array & allPlugins) { for (int i = 0; i < allPlugins.size(); ++i) { String path (allPlugins.getUnchecked(i) ->fileOrIdentifier.replaceCharacter ('\\', '/') .upToLastOccurrenceOf ("/", false, false)); if (path.substring (1, 2) == ":") path = path.substring (2); addPlugin (allPlugins.getUnchecked(i), path); } optimise(); } void addToMenu (PopupMenu& m, const OwnedArray & allPlugins) const { int i; for (i = 0; i < subFolders.size(); ++i) { const PluginFilesystemTree* const sub = subFolders.getUnchecked(i); PopupMenu subMenu; sub->addToMenu (subMenu, allPlugins); #if JUCE_MAC // avoid the special AU formatting nonsense on Mac.. m.addSubMenu (sub->folder.fromFirstOccurrenceOf (":", false, false), subMenu); #else m.addSubMenu (sub->folder, subMenu); #endif } for (i = 0; i < plugins.size(); ++i) { PluginDescription* const plugin = plugins.getUnchecked(i); m.addItem (allPlugins.indexOf (plugin) + menuIdBase, plugin->name, true, false); } } }; void KnownPluginList::addToMenu (PopupMenu& menu, const SortMethod sortMethod) const { Array sorted; { PluginSorter sorter; sorter.method = sortMethod; for (int i = 0; i < types.size(); ++i) sorted.addSorted (sorter, types.getUnchecked(i)); } if (sortMethod == sortByCategory || sortMethod == sortByManufacturer) { String lastSubMenuName; PopupMenu sub; for (int i = 0; i < sorted.size(); ++i) { const PluginDescription* const pd = sorted.getUnchecked(i); String thisSubMenuName (sortMethod == sortByCategory ? pd->category : pd->manufacturerName); if (! thisSubMenuName.containsNonWhitespaceChars()) thisSubMenuName = "Other"; if (thisSubMenuName != lastSubMenuName) { if (sub.getNumItems() > 0) { menu.addSubMenu (lastSubMenuName, sub); sub.clear(); } lastSubMenuName = thisSubMenuName; } sub.addItem (types.indexOf (pd) + menuIdBase, pd->name, true, false); } if (sub.getNumItems() > 0) menu.addSubMenu (lastSubMenuName, sub); } else if (sortMethod == sortByFileSystemLocation) { PluginFilesystemTree root; root.buildTree (sorted); root.addToMenu (menu, types); } else { for (int i = 0; i < sorted.size(); ++i) { const PluginDescription* const pd = sorted.getUnchecked(i); menu.addItem (types.indexOf (pd) + menuIdBase, pd->name, true, false); } } } int KnownPluginList::getIndexChosenByMenu (const int menuResultCode) const { const int i = menuResultCode - menuIdBase; return (((unsigned int) i) < (unsigned int) types.size()) ? i : -1; } END_JUCE_NAMESPACE /*** End of inlined file: juce_KnownPluginList.cpp ***/ /*** Start of inlined file: juce_PluginDescription.cpp ***/ BEGIN_JUCE_NAMESPACE PluginDescription::PluginDescription() : uid (0), isInstrument (false), numInputChannels (0), numOutputChannels (0) { } PluginDescription::~PluginDescription() { } PluginDescription::PluginDescription (const PluginDescription& other) : name (other.name), pluginFormatName (other.pluginFormatName), category (other.category), manufacturerName (other.manufacturerName), version (other.version), fileOrIdentifier (other.fileOrIdentifier), lastFileModTime (other.lastFileModTime), uid (other.uid), isInstrument (other.isInstrument), numInputChannels (other.numInputChannels), numOutputChannels (other.numOutputChannels) { } PluginDescription& PluginDescription::operator= (const PluginDescription& other) { name = other.name; pluginFormatName = other.pluginFormatName; category = other.category; manufacturerName = other.manufacturerName; version = other.version; fileOrIdentifier = other.fileOrIdentifier; uid = other.uid; isInstrument = other.isInstrument; lastFileModTime = other.lastFileModTime; numInputChannels = other.numInputChannels; numOutputChannels = other.numOutputChannels; return *this; } bool PluginDescription::isDuplicateOf (const PluginDescription& other) const { return fileOrIdentifier == other.fileOrIdentifier && uid == other.uid; } const String PluginDescription::createIdentifierString() const { return pluginFormatName + "-" + name + "-" + String::toHexString (fileOrIdentifier.hashCode()) + "-" + String::toHexString (uid); } XmlElement* PluginDescription::createXml() const { XmlElement* const e = new XmlElement ("PLUGIN"); e->setAttribute ("name", name); e->setAttribute ("format", pluginFormatName); e->setAttribute ("category", category); e->setAttribute ("manufacturer", manufacturerName); e->setAttribute ("version", version); e->setAttribute ("file", fileOrIdentifier); e->setAttribute ("uid", String::toHexString (uid)); e->setAttribute ("isInstrument", isInstrument); e->setAttribute ("fileTime", String::toHexString (lastFileModTime.toMilliseconds())); e->setAttribute ("numInputs", numInputChannels); e->setAttribute ("numOutputs", numOutputChannels); return e; } bool PluginDescription::loadFromXml (const XmlElement& xml) { if (xml.hasTagName ("PLUGIN")) { name = xml.getStringAttribute ("name"); pluginFormatName = xml.getStringAttribute ("format"); category = xml.getStringAttribute ("category"); manufacturerName = xml.getStringAttribute ("manufacturer"); version = xml.getStringAttribute ("version"); fileOrIdentifier = xml.getStringAttribute ("file"); uid = xml.getStringAttribute ("uid").getHexValue32(); isInstrument = xml.getBoolAttribute ("isInstrument", false); lastFileModTime = Time (xml.getStringAttribute ("fileTime").getHexValue64()); numInputChannels = xml.getIntAttribute ("numInputs"); numOutputChannels = xml.getIntAttribute ("numOutputs"); return true; } return false; } END_JUCE_NAMESPACE /*** End of inlined file: juce_PluginDescription.cpp ***/ /*** Start of inlined file: juce_PluginDirectoryScanner.cpp ***/ BEGIN_JUCE_NAMESPACE PluginDirectoryScanner::PluginDirectoryScanner (KnownPluginList& listToAddTo, AudioPluginFormat& formatToLookFor, FileSearchPath directoriesToSearch, const bool recursive, const File& deadMansPedalFile_) : list (listToAddTo), format (formatToLookFor), deadMansPedalFile (deadMansPedalFile_), nextIndex (0), progress (0) { directoriesToSearch.removeRedundantPaths(); filesOrIdentifiersToScan = format.searchPathsForPlugins (directoriesToSearch, recursive); // If any plugins have crashed recently when being loaded, move them to the // end of the list to give the others a chance to load correctly.. const StringArray crashedPlugins (getDeadMansPedalFile()); for (int i = 0; i < crashedPlugins.size(); ++i) { const String f = crashedPlugins[i]; for (int j = filesOrIdentifiersToScan.size(); --j >= 0;) if (f == filesOrIdentifiersToScan[j]) filesOrIdentifiersToScan.move (j, -1); } } PluginDirectoryScanner::~PluginDirectoryScanner() { } const String PluginDirectoryScanner::getNextPluginFileThatWillBeScanned() const { return format.getNameOfPluginFromIdentifier (filesOrIdentifiersToScan [nextIndex]); } bool PluginDirectoryScanner::scanNextFile (const bool dontRescanIfAlreadyInList) { String file (filesOrIdentifiersToScan [nextIndex]); if (file.isNotEmpty()) { if (! list.isListingUpToDate (file)) { OwnedArray typesFound; // Add this plugin to the end of the dead-man's pedal list in case it crashes... StringArray crashedPlugins (getDeadMansPedalFile()); crashedPlugins.removeString (file); crashedPlugins.add (file); setDeadMansPedalFile (crashedPlugins); list.scanAndAddFile (file, dontRescanIfAlreadyInList, typesFound, format); // Managed to load without crashing, so remove it from the dead-man's-pedal.. crashedPlugins.removeString (file); setDeadMansPedalFile (crashedPlugins); if (typesFound.size() == 0) failedFiles.add (file); } ++nextIndex; progress = nextIndex / (float) filesOrIdentifiersToScan.size(); } return nextIndex < filesOrIdentifiersToScan.size(); } const StringArray PluginDirectoryScanner::getDeadMansPedalFile() { StringArray lines; if (deadMansPedalFile != File::nonexistent) { lines.addLines (deadMansPedalFile.loadFileAsString()); lines.removeEmptyStrings(); } return lines; } void PluginDirectoryScanner::setDeadMansPedalFile (const StringArray& newContents) { if (deadMansPedalFile != File::nonexistent) deadMansPedalFile.replaceWithText (newContents.joinIntoString ("\n"), true, true); } END_JUCE_NAMESPACE /*** End of inlined file: juce_PluginDirectoryScanner.cpp ***/ /*** Start of inlined file: juce_PluginListComponent.cpp ***/ BEGIN_JUCE_NAMESPACE PluginListComponent::PluginListComponent (KnownPluginList& listToEdit, const File& deadMansPedalFile_, PropertiesFile* const propertiesToUse_) : list (listToEdit), deadMansPedalFile (deadMansPedalFile_), propertiesToUse (propertiesToUse_) { addAndMakeVisible (listBox = new ListBox (String::empty, this)); addAndMakeVisible (optionsButton = new TextButton ("Options...")); optionsButton->addButtonListener (this); optionsButton->setTriggeredOnMouseDown (true); setSize (400, 600); list.addChangeListener (this); changeListenerCallback (0); } PluginListComponent::~PluginListComponent() { list.removeChangeListener (this); deleteAllChildren(); } void PluginListComponent::resized() { listBox->setBounds (0, 0, getWidth(), getHeight() - 30); optionsButton->changeWidthToFitText (24); optionsButton->setTopLeftPosition (8, getHeight() - 28); } void PluginListComponent::changeListenerCallback (void*) { listBox->updateContent(); listBox->repaint(); } int PluginListComponent::getNumRows() { return list.getNumTypes(); } void PluginListComponent::paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) { if (rowIsSelected) g.fillAll (findColour (TextEditor::highlightColourId)); const PluginDescription* const pd = list.getType (row); if (pd != 0) { GlyphArrangement ga; ga.addCurtailedLineOfText (Font (height * 0.7f, Font::bold), pd->name, 8.0f, height * 0.8f, width - 10.0f, true); g.setColour (Colours::black); ga.draw (g); const Rectangle bb (ga.getBoundingBox (0, -1, false)); String desc; desc << pd->pluginFormatName << (pd->isInstrument ? " instrument" : " effect") << " - " << pd->numInputChannels << (pd->numInputChannels == 1 ? " in" : " ins") << " / " << pd->numOutputChannels << (pd->numOutputChannels == 1 ? " out" : " outs"); if (pd->manufacturerName.isNotEmpty()) desc << " - " << pd->manufacturerName; if (pd->version.isNotEmpty()) desc << " - " << pd->version; if (pd->category.isNotEmpty()) desc << " - category: '" << pd->category << '\''; g.setColour (Colours::grey); ga.clear(); ga.addCurtailedLineOfText (Font (height * 0.6f), desc, bb.getRight() + 10.0f, height * 0.8f, width - bb.getRight() - 12.0f, true); ga.draw (g); } } void PluginListComponent::deleteKeyPressed (int lastRowSelected) { list.removeType (lastRowSelected); } void PluginListComponent::buttonClicked (Button* b) { if (optionsButton == b) { PopupMenu menu; menu.addItem (1, TRANS("Clear list")); menu.addItem (5, TRANS("Remove selected plugin from list"), listBox->getNumSelectedRows() > 0); menu.addItem (6, TRANS("Show folder containing selected plugin"), listBox->getNumSelectedRows() > 0); menu.addItem (7, TRANS("Remove any plugins whose files no longer exist")); menu.addSeparator(); menu.addItem (2, TRANS("Sort alphabetically")); menu.addItem (3, TRANS("Sort by category")); menu.addItem (4, TRANS("Sort by manufacturer")); menu.addSeparator(); for (int i = 0; i < AudioPluginFormatManager::getInstance()->getNumFormats(); ++i) { AudioPluginFormat* const format = AudioPluginFormatManager::getInstance()->getFormat (i); if (format->getDefaultLocationsToSearch().getNumPaths() > 0) menu.addItem (10 + i, "Scan for new or updated " + format->getName() + " plugins..."); } const int r = menu.showAt (optionsButton); if (r == 1) { list.clear(); } else if (r == 2) { list.sort (KnownPluginList::sortAlphabetically); } else if (r == 3) { list.sort (KnownPluginList::sortByCategory); } else if (r == 4) { list.sort (KnownPluginList::sortByManufacturer); } else if (r == 5) { const SparseSet selected (listBox->getSelectedRows()); for (int i = list.getNumTypes(); --i >= 0;) if (selected.contains (i)) list.removeType (i); } else if (r == 6) { const PluginDescription* const desc = list.getType (listBox->getSelectedRow()); if (desc != 0) { if (File (desc->fileOrIdentifier).existsAsFile()) File (desc->fileOrIdentifier).getParentDirectory().startAsProcess(); } } else if (r == 7) { for (int i = list.getNumTypes(); --i >= 0;) { if (! AudioPluginFormatManager::getInstance()->doesPluginStillExist (*list.getType (i))) { list.removeType (i); } } } else if (r != 0) { typeToScan = r - 10; startTimer (1); } } } void PluginListComponent::timerCallback() { stopTimer(); scanFor (AudioPluginFormatManager::getInstance()->getFormat (typeToScan)); } bool PluginListComponent::isInterestedInFileDrag (const StringArray& /*files*/) { return true; } void PluginListComponent::filesDropped (const StringArray& files, int, int) { OwnedArray typesFound; list.scanAndAddDragAndDroppedFiles (files, typesFound); } void PluginListComponent::scanFor (AudioPluginFormat* format) { if (format == 0) return; FileSearchPath path (format->getDefaultLocationsToSearch()); if (propertiesToUse != 0) path = propertiesToUse->getValue ("lastPluginScanPath_" + format->getName(), path.toString()); { AlertWindow aw (TRANS("Select folders to scan..."), String::empty, AlertWindow::NoIcon); FileSearchPathListComponent pathList; pathList.setSize (500, 300); pathList.setPath (path); aw.addCustomComponent (&pathList); aw.addButton (TRANS("Scan"), 1, KeyPress::returnKey); aw.addButton (TRANS("Cancel"), 0, KeyPress (KeyPress::escapeKey)); if (aw.runModalLoop() == 0) return; path = pathList.getPath(); } if (propertiesToUse != 0) { propertiesToUse->setValue ("lastPluginScanPath_" + format->getName(), path.toString()); propertiesToUse->saveIfNeeded(); } double progress = 0.0; AlertWindow aw (TRANS("Scanning for plugins..."), TRANS("Searching for all possible plugin files..."), AlertWindow::NoIcon); aw.addButton (TRANS("Cancel"), 0, KeyPress (KeyPress::escapeKey)); aw.addProgressBarComponent (progress); aw.enterModalState(); MessageManager::getInstance()->runDispatchLoopUntil (300); PluginDirectoryScanner scanner (list, *format, path, true, deadMansPedalFile); for (;;) { aw.setMessage (TRANS("Testing:\n\n") + scanner.getNextPluginFileThatWillBeScanned()); MessageManager::getInstance()->runDispatchLoopUntil (20); if (! scanner.scanNextFile (true)) break; if (! aw.isCurrentlyModal()) break; progress = scanner.getProgress(); } if (scanner.getFailedFiles().size() > 0) { StringArray shortNames; for (int i = 0; i < scanner.getFailedFiles().size(); ++i) shortNames.add (File (scanner.getFailedFiles()[i]).getFileName()); AlertWindow::showMessageBox (AlertWindow::InfoIcon, TRANS("Scan complete"), TRANS("Note that the following files appeared to be plugin files, but failed to load correctly:\n\n") + shortNames.joinIntoString (", ")); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_PluginListComponent.cpp ***/ /*** Start of inlined file: juce_AudioUnitPluginFormat.mm ***/ #if JUCE_PLUGINHOST_AU && ! (JUCE_LINUX || JUCE_WINDOWS) #include #include #include #if JUCE_SUPPORT_CARBON #include #include #endif BEGIN_JUCE_NAMESPACE #if JUCE_MAC && JUCE_SUPPORT_CARBON #endif #if JUCE_MAC // Change this to disable logging of various activities #ifndef AU_LOGGING #define AU_LOGGING 1 #endif #if AU_LOGGING #define log(a) Logger::writeToLog(a); #else #define log(a) #endif namespace AudioUnitFormatHelpers { static int insideCallback = 0; static const String osTypeToString (OSType type) { char s[4]; s[0] = (char) (((uint32) type) >> 24); s[1] = (char) (((uint32) type) >> 16); s[2] = (char) (((uint32) type) >> 8); s[3] = (char) ((uint32) type); return String (s, 4); } static OSType stringToOSType (const String& s1) { const String s (s1 + " "); return (((OSType) (unsigned char) s[0]) << 24) | (((OSType) (unsigned char) s[1]) << 16) | (((OSType) (unsigned char) s[2]) << 8) | ((OSType) (unsigned char) s[3]); } static const char* auIdentifierPrefix = "AudioUnit:"; static const String createAUPluginIdentifier (const ComponentDescription& desc) { jassert (osTypeToString ('abcd') == "abcd"); // agh, must have got the endianness wrong.. jassert (stringToOSType ("abcd") == (OSType) 'abcd'); // ditto String s (auIdentifierPrefix); if (desc.componentType == kAudioUnitType_MusicDevice) s << "Synths/"; else if (desc.componentType == kAudioUnitType_MusicEffect || desc.componentType == kAudioUnitType_Effect) s << "Effects/"; else if (desc.componentType == kAudioUnitType_Generator) s << "Generators/"; else if (desc.componentType == kAudioUnitType_Panner) s << "Panners/"; s << osTypeToString (desc.componentType) << "," << osTypeToString (desc.componentSubType) << "," << osTypeToString (desc.componentManufacturer); return s; } static void getAUDetails (ComponentRecord* comp, String& name, String& manufacturer) { Handle componentNameHandle = NewHandle (sizeof (void*)); Handle componentInfoHandle = NewHandle (sizeof (void*)); if (componentNameHandle != 0 && componentInfoHandle != 0) { ComponentDescription desc; if (GetComponentInfo (comp, &desc, componentNameHandle, componentInfoHandle, 0) == noErr) { ConstStr255Param nameString = (ConstStr255Param) (*componentNameHandle); ConstStr255Param infoString = (ConstStr255Param) (*componentInfoHandle); if (nameString != 0 && nameString[0] != 0) { const String all ((const char*) nameString + 1, nameString[0]); DBG ("name: "+ all); manufacturer = all.upToFirstOccurrenceOf (":", false, false).trim(); name = all.fromFirstOccurrenceOf (":", false, false).trim(); } if (infoString != 0 && infoString[0] != 0) { DBG ("info: " + String ((const char*) infoString + 1, infoString[0])); } if (name.isEmpty()) name = ""; } DisposeHandle (componentNameHandle); DisposeHandle (componentInfoHandle); } } static bool getComponentDescFromIdentifier (const String& fileOrIdentifier, ComponentDescription& desc, String& name, String& version, String& manufacturer) { zerostruct (desc); if (fileOrIdentifier.startsWithIgnoreCase (auIdentifierPrefix)) { String s (fileOrIdentifier.substring (jmax (fileOrIdentifier.lastIndexOfChar (':'), fileOrIdentifier.lastIndexOfChar ('/')) + 1)); StringArray tokens; tokens.addTokens (s, ",", String::empty); tokens.trim(); tokens.removeEmptyStrings(); if (tokens.size() == 3) { desc.componentType = stringToOSType (tokens[0]); desc.componentSubType = stringToOSType (tokens[1]); desc.componentManufacturer = stringToOSType (tokens[2]); ComponentRecord* comp = FindNextComponent (0, &desc); if (comp != 0) { getAUDetails (comp, name, manufacturer); return true; } } } return false; } } class AudioUnitPluginWindowCarbon; class AudioUnitPluginWindowCocoa; class AudioUnitPluginInstance : public AudioPluginInstance { public: ~AudioUnitPluginInstance(); // AudioPluginInstance methods: void fillInPluginDescription (PluginDescription& desc) const { desc.name = pluginName; desc.fileOrIdentifier = AudioUnitFormatHelpers::createAUPluginIdentifier (componentDesc); desc.uid = ((int) componentDesc.componentType) ^ ((int) componentDesc.componentSubType) ^ ((int) componentDesc.componentManufacturer); desc.lastFileModTime = 0; desc.pluginFormatName = "AudioUnit"; desc.category = getCategory(); desc.manufacturerName = manufacturer; desc.version = version; desc.numInputChannels = getNumInputChannels(); desc.numOutputChannels = getNumOutputChannels(); desc.isInstrument = (componentDesc.componentType == kAudioUnitType_MusicDevice); } const String getName() const { return pluginName; } bool acceptsMidi() const { return wantsMidiMessages; } bool producesMidi() const { return false; } // AudioProcessor methods: void prepareToPlay (double sampleRate, int estimatedSamplesPerBlock); void releaseResources(); void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages); AudioProcessorEditor* createEditor(); const String getInputChannelName (int index) const; bool isInputChannelStereoPair (int index) const; const String getOutputChannelName (int index) const; bool isOutputChannelStereoPair (int index) const; int getNumParameters(); float getParameter (int index); void setParameter (int index, float newValue); const String getParameterName (int index); const String getParameterText (int index); bool isParameterAutomatable (int index) const; int getNumPrograms(); int getCurrentProgram(); void setCurrentProgram (int index); const String getProgramName (int index); void changeProgramName (int index, const String& newName); void getStateInformation (MemoryBlock& destData); void getCurrentProgramStateInformation (MemoryBlock& destData); void setStateInformation (const void* data, int sizeInBytes); void setCurrentProgramStateInformation (const void* data, int sizeInBytes); juce_UseDebuggingNewOperator private: friend class AudioUnitPluginWindowCarbon; friend class AudioUnitPluginWindowCocoa; friend class AudioUnitPluginFormat; ComponentDescription componentDesc; String pluginName, manufacturer, version; String fileOrIdentifier; CriticalSection lock; bool initialised, wantsMidiMessages, wasPlaying; HeapBlock outputBufferList; AudioTimeStamp timeStamp; AudioSampleBuffer* currentBuffer; AudioUnit audioUnit; Array parameterIds; bool getComponentDescFromFile (const String& fileOrIdentifier); void initialise(); OSStatus renderGetInput (AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) const; static OSStatus renderGetInputCallback (void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) { return ((AudioUnitPluginInstance*) inRefCon) ->renderGetInput (ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); } OSStatus getBeatAndTempo (Float64* outCurrentBeat, Float64* outCurrentTempo) const; OSStatus getMusicalTimeLocation (UInt32* outDeltaSampleOffsetToNextBeat, Float32* outTimeSig_Numerator, UInt32* outTimeSig_Denominator, Float64* outCurrentMeasureDownBeat) const; OSStatus getTransportState (Boolean* outIsPlaying, Boolean* outTransportStateChanged, Float64* outCurrentSampleInTimeLine, Boolean* outIsCycling, Float64* outCycleStartBeat, Float64* outCycleEndBeat); static OSStatus getBeatAndTempoCallback (void* inHostUserData, Float64* outCurrentBeat, Float64* outCurrentTempo) { return ((AudioUnitPluginInstance*) inHostUserData)->getBeatAndTempo (outCurrentBeat, outCurrentTempo); } static OSStatus getMusicalTimeLocationCallback (void* inHostUserData, UInt32* outDeltaSampleOffsetToNextBeat, Float32* outTimeSig_Numerator, UInt32* outTimeSig_Denominator, Float64* outCurrentMeasureDownBeat) { return ((AudioUnitPluginInstance*) inHostUserData) ->getMusicalTimeLocation (outDeltaSampleOffsetToNextBeat, outTimeSig_Numerator, outTimeSig_Denominator, outCurrentMeasureDownBeat); } static OSStatus getTransportStateCallback (void* inHostUserData, Boolean* outIsPlaying, Boolean* outTransportStateChanged, Float64* outCurrentSampleInTimeLine, Boolean* outIsCycling, Float64* outCycleStartBeat, Float64* outCycleEndBeat) { return ((AudioUnitPluginInstance*) inHostUserData) ->getTransportState (outIsPlaying, outTransportStateChanged, outCurrentSampleInTimeLine, outIsCycling, outCycleStartBeat, outCycleEndBeat); } void getNumChannels (int& numIns, int& numOuts) { numIns = 0; numOuts = 0; AUChannelInfo supportedChannels [128]; UInt32 supportedChannelsSize = sizeof (supportedChannels); if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, supportedChannels, &supportedChannelsSize) == noErr && supportedChannelsSize > 0) { for (int i = 0; i < supportedChannelsSize / sizeof (AUChannelInfo); ++i) { numIns = jmax (numIns, (int) supportedChannels[i].inChannels); numOuts = jmax (numOuts, (int) supportedChannels[i].outChannels); } } else { // (this really means the plugin will take any number of ins/outs as long // as they are the same) numIns = numOuts = 2; } } const String getCategory() const; AudioUnitPluginInstance (const String& fileOrIdentifier); }; AudioUnitPluginInstance::AudioUnitPluginInstance (const String& fileOrIdentifier) : fileOrIdentifier (fileOrIdentifier), initialised (false), wantsMidiMessages (false), audioUnit (0), currentBuffer (0) { using namespace AudioUnitFormatHelpers; try { ++insideCallback; log ("Opening AU: " + fileOrIdentifier); if (getComponentDescFromFile (fileOrIdentifier)) { ComponentRecord* const comp = FindNextComponent (0, &componentDesc); if (comp != 0) { audioUnit = (AudioUnit) OpenComponent (comp); wantsMidiMessages = componentDesc.componentType == kAudioUnitType_MusicDevice || componentDesc.componentType == kAudioUnitType_MusicEffect; } } --insideCallback; } catch (...) { --insideCallback; } } AudioUnitPluginInstance::~AudioUnitPluginInstance() { const ScopedLock sl (lock); jassert (AudioUnitFormatHelpers::insideCallback == 0); if (audioUnit != 0) { AudioUnitUninitialize (audioUnit); CloseComponent (audioUnit); audioUnit = 0; } } bool AudioUnitPluginInstance::getComponentDescFromFile (const String& fileOrIdentifier) { zerostruct (componentDesc); if (AudioUnitFormatHelpers::getComponentDescFromIdentifier (fileOrIdentifier, componentDesc, pluginName, version, manufacturer)) return true; const File file (fileOrIdentifier); if (! file.hasFileExtension (".component")) return false; const char* const utf8 = fileOrIdentifier.toUTF8(); CFURLRef url = CFURLCreateFromFileSystemRepresentation (0, (const UInt8*) utf8, strlen (utf8), file.isDirectory()); if (url != 0) { CFBundleRef bundleRef = CFBundleCreate (kCFAllocatorDefault, url); CFRelease (url); if (bundleRef != 0) { CFTypeRef name = CFBundleGetValueForInfoDictionaryKey (bundleRef, CFSTR("CFBundleName")); if (name != 0 && CFGetTypeID (name) == CFStringGetTypeID()) pluginName = PlatformUtilities::cfStringToJuceString ((CFStringRef) name); if (pluginName.isEmpty()) pluginName = file.getFileNameWithoutExtension(); CFTypeRef versionString = CFBundleGetValueForInfoDictionaryKey (bundleRef, CFSTR("CFBundleVersion")); if (versionString != 0 && CFGetTypeID (versionString) == CFStringGetTypeID()) version = PlatformUtilities::cfStringToJuceString ((CFStringRef) versionString); CFTypeRef manuString = CFBundleGetValueForInfoDictionaryKey (bundleRef, CFSTR("CFBundleGetInfoString")); if (manuString != 0 && CFGetTypeID (manuString) == CFStringGetTypeID()) manufacturer = PlatformUtilities::cfStringToJuceString ((CFStringRef) manuString); short resFileId = CFBundleOpenBundleResourceMap (bundleRef); UseResFile (resFileId); for (int i = 1; i <= Count1Resources ('thng'); ++i) { Handle h = Get1IndResource ('thng', i); if (h != 0) { HLock (h); const uint32* const types = (const uint32*) *h; if (types[0] == kAudioUnitType_MusicDevice || types[0] == kAudioUnitType_MusicEffect || types[0] == kAudioUnitType_Effect || types[0] == kAudioUnitType_Generator || types[0] == kAudioUnitType_Panner) { componentDesc.componentType = types[0]; componentDesc.componentSubType = types[1]; componentDesc.componentManufacturer = types[2]; break; } HUnlock (h); ReleaseResource (h); } } CFBundleCloseBundleResourceMap (bundleRef, resFileId); CFRelease (bundleRef); } } return componentDesc.componentType != 0 && componentDesc.componentSubType != 0; } void AudioUnitPluginInstance::initialise() { if (initialised || audioUnit == 0) return; log ("Initialising AU: " + pluginName); parameterIds.clear(); { UInt32 paramListSize = 0; AudioUnitGetProperty (audioUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, 0, ¶mListSize); if (paramListSize > 0) { parameterIds.insertMultiple (0, 0, paramListSize / sizeof (int)); AudioUnitGetProperty (audioUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, ¶meterIds.getReference(0), ¶mListSize); } } { AURenderCallbackStruct info; zerostruct (info); info.inputProcRefCon = this; info.inputProc = renderGetInputCallback; AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &info, sizeof (info)); } { HostCallbackInfo info; zerostruct (info); info.hostUserData = this; info.beatAndTempoProc = getBeatAndTempoCallback; info.musicalTimeLocationProc = getMusicalTimeLocationCallback; info.transportStateProc = getTransportStateCallback; AudioUnitSetProperty (audioUnit, kAudioUnitProperty_HostCallbacks, kAudioUnitScope_Global, 0, &info, sizeof (info)); } int numIns, numOuts; getNumChannels (numIns, numOuts); setPlayConfigDetails (numIns, numOuts, 0, 0); initialised = AudioUnitInitialize (audioUnit) == noErr; setLatencySamples (0); } void AudioUnitPluginInstance::prepareToPlay (double sampleRate_, int samplesPerBlockExpected) { if (audioUnit != 0) { Float64 sampleRateIn = 0, sampleRateOut = 0; UInt32 sampleRateSize = sizeof (sampleRateIn); AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Input, 0, &sampleRateIn, &sampleRateSize); AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, 0, &sampleRateOut, &sampleRateSize); if (sampleRateIn != sampleRate_ || sampleRateOut != sampleRate_) { if (initialised) { AudioUnitUninitialize (audioUnit); initialised = false; } Float64 sr = sampleRate_; AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Input, 0, &sr, sizeof (Float64)); AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, 0, &sr, sizeof (Float64)); } } initialise(); if (initialised) { int numIns, numOuts; getNumChannels (numIns, numOuts); setPlayConfigDetails (numIns, numOuts, sampleRate_, samplesPerBlockExpected); Float64 latencySecs = 0.0; UInt32 latencySize = sizeof (latencySecs); AudioUnitGetProperty (audioUnit, kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0, &latencySecs, &latencySize); setLatencySamples (roundToInt (latencySecs * sampleRate_)); AudioUnitReset (audioUnit, kAudioUnitScope_Input, 0); AudioUnitReset (audioUnit, kAudioUnitScope_Output, 0); AudioUnitReset (audioUnit, kAudioUnitScope_Global, 0); AudioStreamBasicDescription stream; zerostruct (stream); stream.mSampleRate = sampleRate_; stream.mFormatID = kAudioFormatLinearPCM; stream.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved; stream.mFramesPerPacket = 1; stream.mBytesPerPacket = 4; stream.mBytesPerFrame = 4; stream.mBitsPerChannel = 32; stream.mChannelsPerFrame = numIns; OSStatus err = AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &stream, sizeof (stream)); stream.mChannelsPerFrame = numOuts; err = AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &stream, sizeof (stream)); outputBufferList.calloc (sizeof (AudioBufferList) + sizeof (AudioBuffer) * (numOuts + 1), 1); outputBufferList->mNumberBuffers = numOuts; for (int i = numOuts; --i >= 0;) outputBufferList->mBuffers[i].mNumberChannels = 1; zerostruct (timeStamp); timeStamp.mSampleTime = 0; timeStamp.mHostTime = AudioGetCurrentHostTime(); timeStamp.mFlags = kAudioTimeStampSampleTimeValid | kAudioTimeStampHostTimeValid; currentBuffer = 0; wasPlaying = false; } } void AudioUnitPluginInstance::releaseResources() { if (initialised) { AudioUnitReset (audioUnit, kAudioUnitScope_Input, 0); AudioUnitReset (audioUnit, kAudioUnitScope_Output, 0); AudioUnitReset (audioUnit, kAudioUnitScope_Global, 0); outputBufferList.free(); currentBuffer = 0; } } OSStatus AudioUnitPluginInstance::renderGetInput (AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) const { if (inBusNumber == 0 && currentBuffer != 0) { jassert (inNumberFrames == currentBuffer->getNumSamples()); // if this ever happens, might need to add extra handling for (int i = 0; i < ioData->mNumberBuffers; ++i) { if (i < currentBuffer->getNumChannels()) { memcpy (ioData->mBuffers[i].mData, currentBuffer->getSampleData (i, 0), sizeof (float) * inNumberFrames); } else { zeromem (ioData->mBuffers[i].mData, sizeof (float) * inNumberFrames); } } } return noErr; } void AudioUnitPluginInstance::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { const int numSamples = buffer.getNumSamples(); if (initialised) { AudioUnitRenderActionFlags flags = 0; timeStamp.mHostTime = AudioGetCurrentHostTime(); for (int i = getNumOutputChannels(); --i >= 0;) { outputBufferList->mBuffers[i].mDataByteSize = sizeof (float) * numSamples; outputBufferList->mBuffers[i].mData = buffer.getSampleData (i, 0); } currentBuffer = &buffer; if (wantsMidiMessages) { const uint8* midiEventData; int midiEventSize, midiEventPosition; MidiBuffer::Iterator i (midiMessages); while (i.getNextEvent (midiEventData, midiEventSize, midiEventPosition)) { if (midiEventSize <= 3) MusicDeviceMIDIEvent (audioUnit, midiEventData[0], midiEventData[1], midiEventData[2], midiEventPosition); else MusicDeviceSysEx (audioUnit, midiEventData, midiEventSize); } midiMessages.clear(); } AudioUnitRender (audioUnit, &flags, &timeStamp, 0, numSamples, outputBufferList); timeStamp.mSampleTime += numSamples; } else { // Not initialised, so just bypass.. for (int i = getNumInputChannels(); i < getNumOutputChannels(); ++i) buffer.clear (i, 0, buffer.getNumSamples()); } } OSStatus AudioUnitPluginInstance::getBeatAndTempo (Float64* outCurrentBeat, Float64* outCurrentTempo) const { AudioPlayHead* const ph = getPlayHead(); AudioPlayHead::CurrentPositionInfo result; if (ph != 0 && ph->getCurrentPosition (result)) { if (outCurrentBeat != 0) *outCurrentBeat = result.ppqPosition; if (outCurrentTempo != 0) *outCurrentTempo = result.bpm; } else { if (outCurrentBeat != 0) *outCurrentBeat = 0; if (outCurrentTempo != 0) *outCurrentTempo = 120.0; } return noErr; } OSStatus AudioUnitPluginInstance::getMusicalTimeLocation (UInt32* outDeltaSampleOffsetToNextBeat, Float32* outTimeSig_Numerator, UInt32* outTimeSig_Denominator, Float64* outCurrentMeasureDownBeat) const { AudioPlayHead* const ph = getPlayHead(); AudioPlayHead::CurrentPositionInfo result; if (ph != 0 && ph->getCurrentPosition (result)) { if (outTimeSig_Numerator != 0) *outTimeSig_Numerator = result.timeSigNumerator; if (outTimeSig_Denominator != 0) *outTimeSig_Denominator = result.timeSigDenominator; if (outDeltaSampleOffsetToNextBeat != 0) *outDeltaSampleOffsetToNextBeat = 0; //xxx if (outCurrentMeasureDownBeat != 0) *outCurrentMeasureDownBeat = result.ppqPositionOfLastBarStart; //xxx wrong } else { if (outDeltaSampleOffsetToNextBeat != 0) *outDeltaSampleOffsetToNextBeat = 0; if (outTimeSig_Numerator != 0) *outTimeSig_Numerator = 4; if (outTimeSig_Denominator != 0) *outTimeSig_Denominator = 4; if (outCurrentMeasureDownBeat != 0) *outCurrentMeasureDownBeat = 0; } return noErr; } OSStatus AudioUnitPluginInstance::getTransportState (Boolean* outIsPlaying, Boolean* outTransportStateChanged, Float64* outCurrentSampleInTimeLine, Boolean* outIsCycling, Float64* outCycleStartBeat, Float64* outCycleEndBeat) { AudioPlayHead* const ph = getPlayHead(); AudioPlayHead::CurrentPositionInfo result; if (ph != 0 && ph->getCurrentPosition (result)) { if (outIsPlaying != 0) *outIsPlaying = result.isPlaying; if (outTransportStateChanged != 0) { *outTransportStateChanged = result.isPlaying != wasPlaying; wasPlaying = result.isPlaying; } if (outCurrentSampleInTimeLine != 0) *outCurrentSampleInTimeLine = roundToInt (result.timeInSeconds * getSampleRate()); if (outIsCycling != 0) *outIsCycling = false; if (outCycleStartBeat != 0) *outCycleStartBeat = 0; if (outCycleEndBeat != 0) *outCycleEndBeat = 0; } else { if (outIsPlaying != 0) *outIsPlaying = false; if (outTransportStateChanged != 0) *outTransportStateChanged = false; if (outCurrentSampleInTimeLine != 0) *outCurrentSampleInTimeLine = 0; if (outIsCycling != 0) *outIsCycling = false; if (outCycleStartBeat != 0) *outCycleStartBeat = 0; if (outCycleEndBeat != 0) *outCycleEndBeat = 0; } return noErr; } class AudioUnitPluginWindowCocoa : public AudioProcessorEditor { public: AudioUnitPluginWindowCocoa (AudioUnitPluginInstance& plugin_, const bool createGenericViewIfNeeded) : AudioProcessorEditor (&plugin_), plugin (plugin_) { addAndMakeVisible (&wrapper); setOpaque (true); setVisible (true); setSize (100, 100); createView (createGenericViewIfNeeded); } ~AudioUnitPluginWindowCocoa() { const bool wasValid = isValid(); wrapper.setView (0); if (wasValid) plugin.editorBeingDeleted (this); } bool isValid() const { return wrapper.getView() != 0; } void paint (Graphics& g) { g.fillAll (Colours::white); } void resized() { wrapper.setSize (getWidth(), getHeight()); } private: AudioUnitPluginInstance& plugin; NSViewComponent wrapper; bool createView (const bool createGenericViewIfNeeded) { NSView* pluginView = 0; UInt32 dataSize = 0; Boolean isWritable = false; if (AudioUnitGetPropertyInfo (plugin.audioUnit, kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr && dataSize != 0 && AudioUnitGetPropertyInfo (plugin.audioUnit, kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr) { HeapBlock info; info.calloc (dataSize, 1); if (AudioUnitGetProperty (plugin.audioUnit, kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, 0, info, &dataSize) == noErr) { NSString* viewClassName = (NSString*) (info->mCocoaAUViewClass[0]); NSString* path = (NSString*) CFURLCopyPath (info->mCocoaAUViewBundleLocation); NSBundle* viewBundle = [NSBundle bundleWithPath: [path autorelease]]; Class viewClass = [viewBundle classNamed: viewClassName]; if ([viewClass conformsToProtocol: @protocol (AUCocoaUIBase)] && [viewClass instancesRespondToSelector: @selector (interfaceVersion)] && [viewClass instancesRespondToSelector: @selector (uiViewForAudioUnit: withSize:)]) { id factory = [[[viewClass alloc] init] autorelease]; pluginView = [factory uiViewForAudioUnit: plugin.audioUnit withSize: NSMakeSize (getWidth(), getHeight())]; } for (int i = (dataSize - sizeof (CFURLRef)) / sizeof (CFStringRef); --i >= 0;) { CFRelease (info->mCocoaAUViewClass[i]); CFRelease (info->mCocoaAUViewBundleLocation); } } } if (createGenericViewIfNeeded && (pluginView == 0)) pluginView = [[AUGenericView alloc] initWithAudioUnit: plugin.audioUnit]; wrapper.setView (pluginView); if (pluginView != 0) setSize ([pluginView frame].size.width, [pluginView frame].size.height); return pluginView != 0; } }; #if JUCE_SUPPORT_CARBON class AudioUnitPluginWindowCarbon : public AudioProcessorEditor { public: AudioUnitPluginWindowCarbon (AudioUnitPluginInstance& plugin_) : AudioProcessorEditor (&plugin_), plugin (plugin_), viewComponent (0) { addAndMakeVisible (innerWrapper = new InnerWrapperComponent (this)); setOpaque (true); setVisible (true); setSize (400, 300); ComponentDescription viewList [16]; UInt32 viewListSize = sizeof (viewList); AudioUnitGetProperty (plugin.audioUnit, kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &viewList, &viewListSize); componentRecord = FindNextComponent (0, &viewList[0]); } ~AudioUnitPluginWindowCarbon() { innerWrapper = 0; if (isValid()) plugin.editorBeingDeleted (this); } bool isValid() const throw() { return componentRecord != 0; } void paint (Graphics& g) { g.fillAll (Colours::black); } void resized() { innerWrapper->setSize (getWidth(), getHeight()); } bool keyStateChanged (bool) { return false; } bool keyPressed (const KeyPress&) { return false; } AudioUnit getAudioUnit() const { return plugin.audioUnit; } AudioUnitCarbonView getViewComponent() { if (viewComponent == 0 && componentRecord != 0) viewComponent = (AudioUnitCarbonView) OpenComponent (componentRecord); return viewComponent; } void closeViewComponent() { if (viewComponent != 0) { log ("Closing AU GUI: " + plugin.getName()); CloseComponent (viewComponent); viewComponent = 0; } } juce_UseDebuggingNewOperator private: AudioUnitPluginInstance& plugin; ComponentRecord* componentRecord; AudioUnitCarbonView viewComponent; class InnerWrapperComponent : public CarbonViewWrapperComponent { public: InnerWrapperComponent (AudioUnitPluginWindowCarbon* const owner_) : owner (owner_) { } ~InnerWrapperComponent() { deleteWindow(); } HIViewRef attachView (WindowRef windowRef, HIViewRef rootView) { log ("Opening AU GUI: " + owner->plugin.getName()); AudioUnitCarbonView viewComponent = owner->getViewComponent(); if (viewComponent == 0) return 0; Float32Point pos = { 0, 0 }; Float32Point size = { 250, 200 }; HIViewRef pluginView = 0; AudioUnitCarbonViewCreate (viewComponent, owner->getAudioUnit(), windowRef, rootView, &pos, &size, (ControlRef*) &pluginView); return pluginView; } void removeView (HIViewRef) { owner->closeViewComponent(); } private: AudioUnitPluginWindowCarbon* const owner; }; friend class InnerWrapperComponent; ScopedPointer innerWrapper; }; #endif AudioProcessorEditor* AudioUnitPluginInstance::createEditor() { ScopedPointer w (new AudioUnitPluginWindowCocoa (*this, false)); if (! static_cast (static_cast (w))->isValid()) w = 0; #if JUCE_SUPPORT_CARBON if (w == 0) { w = new AudioUnitPluginWindowCarbon (*this); if (! static_cast (static_cast (w))->isValid()) w = 0; } #endif if (w == 0) w = new AudioUnitPluginWindowCocoa (*this, true); // use AUGenericView as a fallback return w.release(); } const String AudioUnitPluginInstance::getCategory() const { const char* result = 0; switch (componentDesc.componentType) { case kAudioUnitType_Effect: case kAudioUnitType_MusicEffect: result = "Effect"; break; case kAudioUnitType_MusicDevice: result = "Synth"; break; case kAudioUnitType_Generator: result = "Generator"; break; case kAudioUnitType_Panner: result = "Panner"; break; default: break; } return result; } int AudioUnitPluginInstance::getNumParameters() { return parameterIds.size(); } float AudioUnitPluginInstance::getParameter (int index) { const ScopedLock sl (lock); Float32 value = 0.0f; if (audioUnit != 0 && ((unsigned int) index) < (unsigned int) parameterIds.size()) { AudioUnitGetParameter (audioUnit, (UInt32) parameterIds.getUnchecked (index), kAudioUnitScope_Global, 0, &value); } return value; } void AudioUnitPluginInstance::setParameter (int index, float newValue) { const ScopedLock sl (lock); if (audioUnit != 0 && ((unsigned int) index) < (unsigned int) parameterIds.size()) { AudioUnitSetParameter (audioUnit, (UInt32) parameterIds.getUnchecked (index), kAudioUnitScope_Global, 0, newValue, 0); } } const String AudioUnitPluginInstance::getParameterName (int index) { AudioUnitParameterInfo info; zerostruct (info); UInt32 sz = sizeof (info); String name; if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, parameterIds [index], &info, &sz) == noErr) { if ((info.flags & kAudioUnitParameterFlag_HasCFNameString) != 0) name = PlatformUtilities::cfStringToJuceString (info.cfNameString); else name = String (info.name, sizeof (info.name)); } return name; } const String AudioUnitPluginInstance::getParameterText (int index) { return String (getParameter (index)); } bool AudioUnitPluginInstance::isParameterAutomatable (int index) const { AudioUnitParameterInfo info; UInt32 sz = sizeof (info); if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, parameterIds [index], &info, &sz) == noErr) { return (info.flags & kAudioUnitParameterFlag_NonRealTime) == 0; } return true; } int AudioUnitPluginInstance::getNumPrograms() { CFArrayRef presets; UInt32 sz = sizeof (CFArrayRef); int num = 0; if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_FactoryPresets, kAudioUnitScope_Global, 0, &presets, &sz) == noErr) { num = (int) CFArrayGetCount (presets); CFRelease (presets); } return num; } int AudioUnitPluginInstance::getCurrentProgram() { AUPreset current; current.presetNumber = 0; UInt32 sz = sizeof (AUPreset); AudioUnitGetProperty (audioUnit, kAudioUnitProperty_FactoryPresets, kAudioUnitScope_Global, 0, ¤t, &sz); return current.presetNumber; } void AudioUnitPluginInstance::setCurrentProgram (int newIndex) { AUPreset current; current.presetNumber = newIndex; current.presetName = 0; AudioUnitSetProperty (audioUnit, kAudioUnitProperty_FactoryPresets, kAudioUnitScope_Global, 0, ¤t, sizeof (AUPreset)); } const String AudioUnitPluginInstance::getProgramName (int index) { String s; CFArrayRef presets; UInt32 sz = sizeof (CFArrayRef); if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_FactoryPresets, kAudioUnitScope_Global, 0, &presets, &sz) == noErr) { for (CFIndex i = 0; i < CFArrayGetCount (presets); ++i) { const AUPreset* p = (const AUPreset*) CFArrayGetValueAtIndex (presets, i); if (p != 0 && p->presetNumber == index) { s = PlatformUtilities::cfStringToJuceString (p->presetName); break; } } CFRelease (presets); } return s; } void AudioUnitPluginInstance::changeProgramName (int index, const String& newName) { jassertfalse; // xxx not implemented! } const String AudioUnitPluginInstance::getInputChannelName (int index) const { if (((unsigned int) index) < (unsigned int) getNumInputChannels()) return "Input " + String (index + 1); return String::empty; } bool AudioUnitPluginInstance::isInputChannelStereoPair (int index) const { if (((unsigned int) index) >= (unsigned int) getNumInputChannels()) return false; return true; } const String AudioUnitPluginInstance::getOutputChannelName (int index) const { if (((unsigned int) index) < (unsigned int) getNumOutputChannels()) return "Output " + String (index + 1); return String::empty; } bool AudioUnitPluginInstance::isOutputChannelStereoPair (int index) const { if (((unsigned int) index) >= (unsigned int) getNumOutputChannels()) return false; return true; } void AudioUnitPluginInstance::getStateInformation (MemoryBlock& destData) { getCurrentProgramStateInformation (destData); } void AudioUnitPluginInstance::getCurrentProgramStateInformation (MemoryBlock& destData) { CFPropertyListRef propertyList = 0; UInt32 sz = sizeof (CFPropertyListRef); if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0, &propertyList, &sz) == noErr) { CFWriteStreamRef stream = CFWriteStreamCreateWithAllocatedBuffers (kCFAllocatorDefault, kCFAllocatorDefault); CFWriteStreamOpen (stream); CFIndex bytesWritten = CFPropertyListWriteToStream (propertyList, stream, kCFPropertyListBinaryFormat_v1_0, 0); CFWriteStreamClose (stream); CFDataRef data = (CFDataRef) CFWriteStreamCopyProperty (stream, kCFStreamPropertyDataWritten); destData.setSize (bytesWritten); destData.copyFrom (CFDataGetBytePtr (data), 0, destData.getSize()); CFRelease (data); CFRelease (stream); CFRelease (propertyList); } } void AudioUnitPluginInstance::setStateInformation (const void* data, int sizeInBytes) { setCurrentProgramStateInformation (data, sizeInBytes); } void AudioUnitPluginInstance::setCurrentProgramStateInformation (const void* data, int sizeInBytes) { CFReadStreamRef stream = CFReadStreamCreateWithBytesNoCopy (kCFAllocatorDefault, (const UInt8*) data, sizeInBytes, kCFAllocatorNull); CFReadStreamOpen (stream); CFPropertyListFormat format = kCFPropertyListBinaryFormat_v1_0; CFPropertyListRef propertyList = CFPropertyListCreateFromStream (kCFAllocatorDefault, stream, 0, kCFPropertyListImmutable, &format, 0); CFRelease (stream); if (propertyList != 0) AudioUnitSetProperty (audioUnit, kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0, &propertyList, sizeof (propertyList)); } AudioUnitPluginFormat::AudioUnitPluginFormat() { } AudioUnitPluginFormat::~AudioUnitPluginFormat() { } void AudioUnitPluginFormat::findAllTypesForFile (OwnedArray & results, const String& fileOrIdentifier) { if (! fileMightContainThisPluginType (fileOrIdentifier)) return; PluginDescription desc; desc.fileOrIdentifier = fileOrIdentifier; desc.uid = 0; try { ScopedPointer createdInstance (createInstanceFromDescription (desc)); AudioUnitPluginInstance* const auInstance = dynamic_cast ((AudioPluginInstance*) createdInstance); if (auInstance != 0) { auInstance->fillInPluginDescription (desc); results.add (new PluginDescription (desc)); } } catch (...) { // crashed while loading... } } AudioPluginInstance* AudioUnitPluginFormat::createInstanceFromDescription (const PluginDescription& desc) { if (fileMightContainThisPluginType (desc.fileOrIdentifier)) { ScopedPointer result (new AudioUnitPluginInstance (desc.fileOrIdentifier)); if (result->audioUnit != 0) { result->initialise(); return result.release(); } } return 0; } const StringArray AudioUnitPluginFormat::searchPathsForPlugins (const FileSearchPath& /*directoriesToSearch*/, const bool /*recursive*/) { StringArray result; ComponentRecord* comp = 0; ComponentDescription desc; zerostruct (desc); for (;;) { zerostruct (desc); comp = FindNextComponent (comp, &desc); if (comp == 0) break; GetComponentInfo (comp, &desc, 0, 0, 0); if (desc.componentType == kAudioUnitType_MusicDevice || desc.componentType == kAudioUnitType_MusicEffect || desc.componentType == kAudioUnitType_Effect || desc.componentType == kAudioUnitType_Generator || desc.componentType == kAudioUnitType_Panner) { const String s (AudioUnitFormatHelpers::createAUPluginIdentifier (desc)); DBG (s); result.add (s); } } return result; } bool AudioUnitPluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier) { ComponentDescription desc; String name, version, manufacturer; if (AudioUnitFormatHelpers::getComponentDescFromIdentifier (fileOrIdentifier, desc, name, version, manufacturer)) return FindNextComponent (0, &desc) != 0; const File f (fileOrIdentifier); return f.hasFileExtension (".component") && f.isDirectory(); } const String AudioUnitPluginFormat::getNameOfPluginFromIdentifier (const String& fileOrIdentifier) { ComponentDescription desc; String name, version, manufacturer; AudioUnitFormatHelpers::getComponentDescFromIdentifier (fileOrIdentifier, desc, name, version, manufacturer); if (name.isEmpty()) name = fileOrIdentifier; return name; } bool AudioUnitPluginFormat::doesPluginStillExist (const PluginDescription& desc) { if (desc.fileOrIdentifier.startsWithIgnoreCase (AudioUnitFormatHelpers::auIdentifierPrefix)) return fileMightContainThisPluginType (desc.fileOrIdentifier); else return File (desc.fileOrIdentifier).exists(); } const FileSearchPath AudioUnitPluginFormat::getDefaultLocationsToSearch() { return FileSearchPath ("/(Default AudioUnit locations)"); } #endif END_JUCE_NAMESPACE #undef log #endif /*** End of inlined file: juce_AudioUnitPluginFormat.mm ***/ /*** Start of inlined file: juce_VSTPluginFormat.mm ***/ // This file just wraps juce_VSTPluginFormat.cpp in an objective-C wrapper #define JUCE_MAC_VST_INCLUDED 1 /*** Start of inlined file: juce_VSTPluginFormat.cpp ***/ #if JUCE_PLUGINHOST_VST && (JUCE_MAC_VST_INCLUDED || ! JUCE_MAC) #if JUCE_WINDOWS #undef _WIN32_WINNT #define _WIN32_WINNT 0x500 #undef STRICT #define STRICT #include #include #pragma warning (disable : 4312 4355) #elif JUCE_LINUX #include #include #include #include #include #undef Font #undef KeyPress #undef Drawable #undef Time #else #include #include #endif #if ! (JUCE_MAC && JUCE_64BIT) BEGIN_JUCE_NAMESPACE #if JUCE_MAC && JUCE_SUPPORT_CARBON #endif #undef PRAGMA_ALIGN_SUPPORTED #define VST_FORCE_DEPRECATED 0 #if JUCE_MSVC #pragma warning (push) #pragma warning (disable: 4996) #endif /* Obviously you're going to need the Steinberg vstsdk2.4 folder in your include path if you want to add VST support. If you're not interested in VSTs, you can disable them by changing the JUCE_PLUGINHOST_VST flag in juce_Config.h */ #include "pluginterfaces/vst2.x/aeffectx.h" #if JUCE_MSVC #pragma warning (pop) #endif #if JUCE_LINUX #define Font JUCE_NAMESPACE::Font #define KeyPress JUCE_NAMESPACE::KeyPress #define Drawable JUCE_NAMESPACE::Drawable #define Time JUCE_NAMESPACE::Time #endif /*** Start of inlined file: juce_VSTMidiEventList.h ***/ #ifdef __aeffect__ #ifndef __JUCE_VSTMIDIEVENTLIST_JUCEHEADER__ #define __JUCE_VSTMIDIEVENTLIST_JUCEHEADER__ /** Holds a set of VSTMidiEvent objects and makes it easy to add events to the list. This is used by both the VST hosting code and the plugin wrapper. */ class VSTMidiEventList { public: VSTMidiEventList() : numEventsUsed (0), numEventsAllocated (0) { } ~VSTMidiEventList() { freeEvents(); } void clear() { numEventsUsed = 0; if (events != 0) events->numEvents = 0; } void addEvent (const void* const midiData, const int numBytes, const int frameOffset) { ensureSize (numEventsUsed + 1); VstMidiEvent* const e = (VstMidiEvent*) (events->events [numEventsUsed]); events->numEvents = ++numEventsUsed; if (numBytes <= 4) { if (e->type == kVstSysExType) { juce_free (((VstMidiSysexEvent*) e)->sysexDump); e->type = kVstMidiType; e->byteSize = sizeof (VstMidiEvent); e->noteLength = 0; e->noteOffset = 0; e->detune = 0; e->noteOffVelocity = 0; } e->deltaFrames = frameOffset; memcpy (e->midiData, midiData, numBytes); } else { VstMidiSysexEvent* const se = (VstMidiSysexEvent*) e; if (se->type == kVstSysExType) se->sysexDump = (char*) juce_realloc (se->sysexDump, numBytes); else se->sysexDump = (char*) juce_malloc (numBytes); memcpy (se->sysexDump, midiData, numBytes); se->type = kVstSysExType; se->byteSize = sizeof (VstMidiSysexEvent); se->deltaFrames = frameOffset; se->flags = 0; se->dumpBytes = numBytes; se->resvd1 = 0; se->resvd2 = 0; } } // Handy method to pull the events out of an event buffer supplied by the host // or plugin. static void addEventsToMidiBuffer (const VstEvents* events, MidiBuffer& dest) { for (int i = 0; i < events->numEvents; ++i) { const VstEvent* const e = events->events[i]; if (e != 0) { if (e->type == kVstMidiType) { dest.addEvent ((const JUCE_NAMESPACE::uint8*) ((const VstMidiEvent*) e)->midiData, 4, e->deltaFrames); } else if (e->type == kVstSysExType) { dest.addEvent ((const JUCE_NAMESPACE::uint8*) ((const VstMidiSysexEvent*) e)->sysexDump, (int) ((const VstMidiSysexEvent*) e)->dumpBytes, e->deltaFrames); } } } } void ensureSize (int numEventsNeeded) { if (numEventsNeeded > numEventsAllocated) { numEventsNeeded = (numEventsNeeded + 32) & ~31; const int size = 20 + sizeof (VstEvent*) * numEventsNeeded; if (events == 0) events.calloc (size, 1); else events.realloc (size, 1); for (int i = numEventsAllocated; i < numEventsNeeded; ++i) { VstMidiEvent* const e = (VstMidiEvent*) juce_calloc (jmax ((int) sizeof (VstMidiEvent), (int) sizeof (VstMidiSysexEvent))); e->type = kVstMidiType; e->byteSize = sizeof (VstMidiEvent); events->events[i] = (VstEvent*) e; } numEventsAllocated = numEventsNeeded; } } void freeEvents() { if (events != 0) { for (int i = numEventsAllocated; --i >= 0;) { VstMidiEvent* const e = (VstMidiEvent*) (events->events[i]); if (e->type == kVstSysExType) juce_free (((VstMidiSysexEvent*) e)->sysexDump); juce_free (e); } events.free(); numEventsUsed = 0; numEventsAllocated = 0; } } HeapBlock events; private: int numEventsUsed, numEventsAllocated; }; #endif // __JUCE_VSTMIDIEVENTLIST_JUCEHEADER__ #endif // __JUCE_VSTMIDIEVENTLIST_JUCEHEADER__ /*** End of inlined file: juce_VSTMidiEventList.h ***/ #if ! JUCE_WINDOWS static void _fpreset() {} static void _clearfp() {} #endif extern void juce_callAnyTimersSynchronously(); const int fxbVersionNum = 1; struct fxProgram { long chunkMagic; // 'CcnK' long byteSize; // of this chunk, excl. magic + byteSize long fxMagic; // 'FxCk' long version; long fxID; // fx unique id long fxVersion; long numParams; char prgName[28]; float params[1]; // variable no. of parameters }; struct fxSet { long chunkMagic; // 'CcnK' long byteSize; // of this chunk, excl. magic + byteSize long fxMagic; // 'FxBk' long version; long fxID; // fx unique id long fxVersion; long numPrograms; char future[128]; fxProgram programs[1]; // variable no. of programs }; struct fxChunkSet { long chunkMagic; // 'CcnK' long byteSize; // of this chunk, excl. magic + byteSize long fxMagic; // 'FxCh', 'FPCh', or 'FBCh' long version; long fxID; // fx unique id long fxVersion; long numPrograms; char future[128]; long chunkSize; char chunk[8]; // variable }; struct fxProgramSet { long chunkMagic; // 'CcnK' long byteSize; // of this chunk, excl. magic + byteSize long fxMagic; // 'FxCh', 'FPCh', or 'FBCh' long version; long fxID; // fx unique id long fxVersion; long numPrograms; char name[28]; long chunkSize; char chunk[8]; // variable }; static long vst_swap (const long x) throw() { #ifdef JUCE_LITTLE_ENDIAN return (long) ByteOrder::swap ((uint32) x); #else return x; #endif } static float vst_swapFloat (const float x) throw() { #ifdef JUCE_LITTLE_ENDIAN union { uint32 asInt; float asFloat; } n; n.asFloat = x; n.asInt = ByteOrder::swap (n.asInt); return n.asFloat; #else return x; #endif } static double getVSTHostTimeNanoseconds() { #if JUCE_WINDOWS return timeGetTime() * 1000000.0; #elif JUCE_LINUX timeval micro; gettimeofday (µ, 0); return micro.tv_usec * 1000.0; #elif JUCE_MAC UnsignedWide micro; Microseconds (µ); return micro.lo * 1000.0; #endif } typedef AEffect* (*MainCall) (audioMasterCallback); static VstIntPtr VSTCALLBACK audioMaster (AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt); static int shellUIDToCreate = 0; static int insideVSTCallback = 0; class VSTPluginWindow; // Change this to disable logging of various VST activities #ifndef VST_LOGGING #define VST_LOGGING 1 #endif #if VST_LOGGING #define log(a) Logger::writeToLog(a); #else #define log(a) #endif #if JUCE_MAC && JUCE_PPC static void* NewCFMFromMachO (void* const machofp) throw() { void* result = juce_malloc (8); ((void**) result)[0] = machofp; ((void**) result)[1] = result; return result; } #endif #if JUCE_LINUX extern Display* display; extern XContext windowHandleXContext; typedef void (*EventProcPtr) (XEvent* ev); static bool xErrorTriggered; static int temporaryErrorHandler (Display*, XErrorEvent*) { xErrorTriggered = true; return 0; } static int getPropertyFromXWindow (Window handle, Atom atom) { XErrorHandler oldErrorHandler = XSetErrorHandler (temporaryErrorHandler); xErrorTriggered = false; int userSize; unsigned long bytes, userCount; unsigned char* data; Atom userType; XGetWindowProperty (display, handle, atom, 0, 1, false, AnyPropertyType, &userType, &userSize, &userCount, &bytes, &data); XSetErrorHandler (oldErrorHandler); return (userCount == 1 && ! xErrorTriggered) ? *reinterpret_cast (data) : 0; } static Window getChildWindow (Window windowToCheck) { Window rootWindow, parentWindow; Window* childWindows; unsigned int numChildren; XQueryTree (display, windowToCheck, &rootWindow, &parentWindow, &childWindows, &numChildren); if (numChildren > 0) return childWindows [0]; return 0; } static void translateJuceToXButtonModifiers (const MouseEvent& e, XEvent& ev) throw() { if (e.mods.isLeftButtonDown()) { ev.xbutton.button = Button1; ev.xbutton.state |= Button1Mask; } else if (e.mods.isRightButtonDown()) { ev.xbutton.button = Button3; ev.xbutton.state |= Button3Mask; } else if (e.mods.isMiddleButtonDown()) { ev.xbutton.button = Button2; ev.xbutton.state |= Button2Mask; } } static void translateJuceToXMotionModifiers (const MouseEvent& e, XEvent& ev) throw() { if (e.mods.isLeftButtonDown()) ev.xmotion.state |= Button1Mask; else if (e.mods.isRightButtonDown()) ev.xmotion.state |= Button3Mask; else if (e.mods.isMiddleButtonDown()) ev.xmotion.state |= Button2Mask; } static void translateJuceToXCrossingModifiers (const MouseEvent& e, XEvent& ev) throw() { if (e.mods.isLeftButtonDown()) ev.xcrossing.state |= Button1Mask; else if (e.mods.isRightButtonDown()) ev.xcrossing.state |= Button3Mask; else if (e.mods.isMiddleButtonDown()) ev.xcrossing.state |= Button2Mask; } static void translateJuceToXMouseWheelModifiers (const MouseEvent& e, const float increment, XEvent& ev) throw() { if (increment < 0) { ev.xbutton.button = Button5; ev.xbutton.state |= Button5Mask; } else if (increment > 0) { ev.xbutton.button = Button4; ev.xbutton.state |= Button4Mask; } } #endif class ModuleHandle : public ReferenceCountedObject { public: File file; MainCall moduleMain; String pluginName; static Array & getActiveModules() { static Array activeModules; return activeModules; } static ModuleHandle* findOrCreateModule (const File& file) { for (int i = getActiveModules().size(); --i >= 0;) { ModuleHandle* const module = getActiveModules().getUnchecked(i); if (module->file == file) return module; } _fpreset(); // (doesn't do any harm) ++insideVSTCallback; shellUIDToCreate = 0; log ("Attempting to load VST: " + file.getFullPathName()); ScopedPointer m (new ModuleHandle (file)); if (! m->open()) m = 0; --insideVSTCallback; _fpreset(); // (doesn't do any harm) return m.release(); } ModuleHandle (const File& file_) : file (file_), moduleMain (0), #if JUCE_WINDOWS || JUCE_LINUX hModule (0) #elif JUCE_MAC fragId (0), resHandle (0), bundleRef (0), resFileId (0) #endif { getActiveModules().add (this); #if JUCE_WINDOWS || JUCE_LINUX fullParentDirectoryPathName = file_.getParentDirectory().getFullPathName(); #elif JUCE_MAC FSRef ref; PlatformUtilities::makeFSRefFromPath (&ref, file_.getParentDirectory().getFullPathName()); FSGetCatalogInfo (&ref, kFSCatInfoNone, 0, 0, &parentDirFSSpec, 0); #endif } ~ModuleHandle() { getActiveModules().removeValue (this); close(); } juce_UseDebuggingNewOperator #if JUCE_WINDOWS || JUCE_LINUX void* hModule; String fullParentDirectoryPathName; bool open() { #if JUCE_WINDOWS static bool timePeriodSet = false; if (! timePeriodSet) { timePeriodSet = true; timeBeginPeriod (2); } #endif pluginName = file.getFileNameWithoutExtension(); hModule = PlatformUtilities::loadDynamicLibrary (file.getFullPathName()); moduleMain = (MainCall) PlatformUtilities::getProcedureEntryPoint (hModule, "VSTPluginMain"); if (moduleMain == 0) moduleMain = (MainCall) PlatformUtilities::getProcedureEntryPoint (hModule, "main"); return moduleMain != 0; } void close() { _fpreset(); // (doesn't do any harm) PlatformUtilities::freeDynamicLibrary (hModule); } void closeEffect (AEffect* eff) { eff->dispatcher (eff, effClose, 0, 0, 0, 0); } #else CFragConnectionID fragId; Handle resHandle; CFBundleRef bundleRef; FSSpec parentDirFSSpec; short resFileId; bool open() { bool ok = false; const String filename (file.getFullPathName()); if (file.hasFileExtension (".vst")) { const char* const utf8 = filename.toUTF8(); CFURLRef url = CFURLCreateFromFileSystemRepresentation (0, (const UInt8*) utf8, strlen (utf8), file.isDirectory()); if (url != 0) { bundleRef = CFBundleCreate (kCFAllocatorDefault, url); CFRelease (url); if (bundleRef != 0) { if (CFBundleLoadExecutable (bundleRef)) { moduleMain = (MainCall) CFBundleGetFunctionPointerForName (bundleRef, CFSTR("main_macho")); if (moduleMain == 0) moduleMain = (MainCall) CFBundleGetFunctionPointerForName (bundleRef, CFSTR("VSTPluginMain")); if (moduleMain != 0) { CFTypeRef name = CFBundleGetValueForInfoDictionaryKey (bundleRef, CFSTR("CFBundleName")); if (name != 0) { if (CFGetTypeID (name) == CFStringGetTypeID()) { char buffer[1024]; if (CFStringGetCString ((CFStringRef) name, buffer, sizeof (buffer), CFStringGetSystemEncoding())) pluginName = buffer; } } if (pluginName.isEmpty()) pluginName = file.getFileNameWithoutExtension(); resFileId = CFBundleOpenBundleResourceMap (bundleRef); ok = true; } } if (! ok) { CFBundleUnloadExecutable (bundleRef); CFRelease (bundleRef); bundleRef = 0; } } } } #if JUCE_PPC else { FSRef fn; if (FSPathMakeRef ((UInt8*) filename.toUTF8(), &fn, 0) == noErr) { resFileId = FSOpenResFile (&fn, fsRdPerm); if (resFileId != -1) { const int numEffs = Count1Resources ('aEff'); for (int i = 0; i < numEffs; ++i) { resHandle = Get1IndResource ('aEff', i + 1); if (resHandle != 0) { OSType type; Str255 name; SInt16 id; GetResInfo (resHandle, &id, &type, name); pluginName = String ((const char*) name + 1, name[0]); DetachResource (resHandle); HLock (resHandle); Ptr ptr; Str255 errorText; OSErr err = GetMemFragment (*resHandle, GetHandleSize (resHandle), name, kPrivateCFragCopy, &fragId, &ptr, errorText); if (err == noErr) { moduleMain = (MainCall) newMachOFromCFM (ptr); ok = true; } else { HUnlock (resHandle); } break; } } if (! ok) CloseResFile (resFileId); } } } #endif return ok; } void close() { #if JUCE_PPC if (fragId != 0) { if (moduleMain != 0) disposeMachOFromCFM ((void*) moduleMain); CloseConnection (&fragId); HUnlock (resHandle); if (resFileId != 0) CloseResFile (resFileId); } else #endif if (bundleRef != 0) { CFBundleCloseBundleResourceMap (bundleRef, resFileId); if (CFGetRetainCount (bundleRef) == 1) CFBundleUnloadExecutable (bundleRef); if (CFGetRetainCount (bundleRef) > 0) CFRelease (bundleRef); } } void closeEffect (AEffect* eff) { #if JUCE_PPC if (fragId != 0) { Array thingsToDelete; thingsToDelete.add ((void*) eff->dispatcher); thingsToDelete.add ((void*) eff->process); thingsToDelete.add ((void*) eff->setParameter); thingsToDelete.add ((void*) eff->getParameter); thingsToDelete.add ((void*) eff->processReplacing); eff->dispatcher (eff, effClose, 0, 0, 0, 0); for (int i = thingsToDelete.size(); --i >= 0;) disposeMachOFromCFM (thingsToDelete[i]); } else #endif { eff->dispatcher (eff, effClose, 0, 0, 0, 0); } } #if JUCE_PPC static void* newMachOFromCFM (void* cfmfp) { if (cfmfp == 0) return 0; UInt32* const mfp = new UInt32[6]; mfp[0] = 0x3d800000 | ((UInt32) cfmfp >> 16); mfp[1] = 0x618c0000 | ((UInt32) cfmfp & 0xffff); mfp[2] = 0x800c0000; mfp[3] = 0x804c0004; mfp[4] = 0x7c0903a6; mfp[5] = 0x4e800420; MakeDataExecutable (mfp, sizeof (UInt32) * 6); return mfp; } static void disposeMachOFromCFM (void* ptr) { delete[] static_cast (ptr); } void coerceAEffectFunctionCalls (AEffect* eff) { if (fragId != 0) { eff->dispatcher = (AEffectDispatcherProc) newMachOFromCFM ((void*) eff->dispatcher); eff->process = (AEffectProcessProc) newMachOFromCFM ((void*) eff->process); eff->setParameter = (AEffectSetParameterProc) newMachOFromCFM ((void*) eff->setParameter); eff->getParameter = (AEffectGetParameterProc) newMachOFromCFM ((void*) eff->getParameter); eff->processReplacing = (AEffectProcessProc) newMachOFromCFM ((void*) eff->processReplacing); } } #endif #endif }; /** An instance of a plugin, created by a VSTPluginFormat. */ class VSTPluginInstance : public AudioPluginInstance, private Timer, private AsyncUpdater { public: ~VSTPluginInstance(); // AudioPluginInstance methods: void fillInPluginDescription (PluginDescription& desc) const { desc.name = name; desc.fileOrIdentifier = module->file.getFullPathName(); desc.uid = getUID(); desc.lastFileModTime = module->file.getLastModificationTime(); desc.pluginFormatName = "VST"; desc.category = getCategory(); { char buffer [kVstMaxVendorStrLen + 8]; zerostruct (buffer); dispatch (effGetVendorString, 0, 0, buffer, 0); desc.manufacturerName = buffer; } desc.version = getVersion(); desc.numInputChannels = getNumInputChannels(); desc.numOutputChannels = getNumOutputChannels(); desc.isInstrument = (effect != 0 && (effect->flags & effFlagsIsSynth) != 0); } const String getName() const { return name; } int getUID() const; bool acceptsMidi() const { return wantsMidiMessages; } bool producesMidi() const { return dispatch (effCanDo, 0, 0, (void*) "sendVstMidiEvent", 0) > 0; } // AudioProcessor methods: void prepareToPlay (double sampleRate, int estimatedSamplesPerBlock); void releaseResources(); void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages); AudioProcessorEditor* createEditor(); const String getInputChannelName (int index) const; bool isInputChannelStereoPair (int index) const; const String getOutputChannelName (int index) const; bool isOutputChannelStereoPair (int index) const; int getNumParameters() { return effect != 0 ? effect->numParams : 0; } float getParameter (int index); void setParameter (int index, float newValue); const String getParameterName (int index); const String getParameterText (int index); bool isParameterAutomatable (int index) const; int getNumPrograms() { return effect != 0 ? effect->numPrograms : 0; } int getCurrentProgram() { return dispatch (effGetProgram, 0, 0, 0, 0); } void setCurrentProgram (int index); const String getProgramName (int index); void changeProgramName (int index, const String& newName); void getStateInformation (MemoryBlock& destData); void getCurrentProgramStateInformation (MemoryBlock& destData); void setStateInformation (const void* data, int sizeInBytes); void setCurrentProgramStateInformation (const void* data, int sizeInBytes); void timerCallback(); void handleAsyncUpdate(); VstIntPtr handleCallback (VstInt32 opcode, VstInt32 index, VstInt32 value, void *ptr, float opt); juce_UseDebuggingNewOperator private: friend class VSTPluginWindow; friend class VSTPluginFormat; AEffect* effect; String name; CriticalSection lock; bool wantsMidiMessages, initialised, isPowerOn; mutable StringArray programNames; AudioSampleBuffer tempBuffer; CriticalSection midiInLock; MidiBuffer incomingMidi; VSTMidiEventList midiEventsToSend; VstTimeInfo vstHostTime; ReferenceCountedObjectPtr module; int dispatch (const int opcode, const int index, const int value, void* const ptr, float opt) const; bool restoreProgramSettings (const fxProgram* const prog); const String getCurrentProgramName(); void setParamsInProgramBlock (fxProgram* const prog); void updateStoredProgramNames(); void initialise(); void handleMidiFromPlugin (const VstEvents* const events); void createTempParameterStore (MemoryBlock& dest); void restoreFromTempParameterStore (const MemoryBlock& mb); const String getParameterLabel (int index) const; bool usesChunks() const throw() { return effect != 0 && (effect->flags & effFlagsProgramChunks) != 0; } void getChunkData (MemoryBlock& mb, bool isPreset, int maxSizeMB) const; void setChunkData (const char* data, int size, bool isPreset); bool loadFromFXBFile (const void* data, int numBytes); bool saveToFXBFile (MemoryBlock& dest, bool isFXB, int maxSizeMB); int getVersionNumber() const throw() { return effect != 0 ? effect->version : 0; } const String getVersion() const; const String getCategory() const; bool hasEditor() const throw() { return effect != 0 && (effect->flags & effFlagsHasEditor) != 0; } void setPower (const bool on); VSTPluginInstance (const ReferenceCountedObjectPtr & module); }; VSTPluginInstance::VSTPluginInstance (const ReferenceCountedObjectPtr & module_) : effect (0), wantsMidiMessages (false), initialised (false), isPowerOn (false), tempBuffer (1, 1), module (module_) { try { _fpreset(); ++insideVSTCallback; name = module->pluginName; log ("Creating VST instance: " + name); #if JUCE_MAC if (module->resFileId != 0) UseResFile (module->resFileId); #if JUCE_PPC if (module->fragId != 0) { static void* audioMasterCoerced = 0; if (audioMasterCoerced == 0) audioMasterCoerced = NewCFMFromMachO ((void*) &audioMaster); effect = module->moduleMain ((audioMasterCallback) audioMasterCoerced); } else #endif #endif { effect = module->moduleMain (&audioMaster); } --insideVSTCallback; if (effect != 0 && effect->magic == kEffectMagic) { #if JUCE_PPC module->coerceAEffectFunctionCalls (effect); #endif jassert (effect->resvd2 == 0); jassert (effect->object != 0); _fpreset(); // some dodgy plugs fuck around with this } else { effect = 0; } } catch (...) { --insideVSTCallback; } } VSTPluginInstance::~VSTPluginInstance() { const ScopedLock sl (lock); jassert (insideVSTCallback == 0); if (effect != 0 && effect->magic == kEffectMagic) { try { #if JUCE_MAC if (module->resFileId != 0) UseResFile (module->resFileId); #endif // Must delete any editors before deleting the plugin instance! jassert (getActiveEditor() == 0); _fpreset(); // some dodgy plugs fuck around with this module->closeEffect (effect); } catch (...) {} } module = 0; effect = 0; } void VSTPluginInstance::initialise() { if (initialised || effect == 0) return; log ("Initialising VST: " + module->pluginName); initialised = true; dispatch (effIdentify, 0, 0, 0, 0); // this code would ask the plugin for its name, but so few plugins // actually bother implementing this correctly, that it's better to // just ignore it and use the file name instead. /* { char buffer [256]; zerostruct (buffer); dispatch (effGetEffectName, 0, 0, buffer, 0); name = String (buffer).trim(); if (name.isEmpty()) name = module->pluginName; } */ if (getSampleRate() > 0) dispatch (effSetSampleRate, 0, 0, 0, (float) getSampleRate()); if (getBlockSize() > 0) dispatch (effSetBlockSize, 0, jmax (32, getBlockSize()), 0, 0); dispatch (effOpen, 0, 0, 0, 0); setPlayConfigDetails (effect->numInputs, effect->numOutputs, getSampleRate(), getBlockSize()); if (getNumPrograms() > 1) setCurrentProgram (0); else dispatch (effSetProgram, 0, 0, 0, 0); int i; for (i = effect->numInputs; --i >= 0;) dispatch (effConnectInput, i, 1, 0, 0); for (i = effect->numOutputs; --i >= 0;) dispatch (effConnectOutput, i, 1, 0, 0); updateStoredProgramNames(); wantsMidiMessages = dispatch (effCanDo, 0, 0, (void*) "receiveVstMidiEvent", 0) > 0; setLatencySamples (effect->initialDelay); } void VSTPluginInstance::prepareToPlay (double sampleRate_, int samplesPerBlockExpected) { setPlayConfigDetails (effect->numInputs, effect->numOutputs, sampleRate_, samplesPerBlockExpected); setLatencySamples (effect->initialDelay); vstHostTime.tempo = 120.0; vstHostTime.timeSigNumerator = 4; vstHostTime.timeSigDenominator = 4; vstHostTime.sampleRate = sampleRate_; vstHostTime.samplePos = 0; vstHostTime.flags = kVstNanosValid; /*| kVstTransportPlaying | kVstTempoValid | kVstTimeSigValid*/; initialise(); if (initialised) { wantsMidiMessages = wantsMidiMessages || (dispatch (effCanDo, 0, 0, (void*) "receiveVstMidiEvent", 0) > 0); if (wantsMidiMessages) midiEventsToSend.ensureSize (256); else midiEventsToSend.freeEvents(); incomingMidi.clear(); dispatch (effSetSampleRate, 0, 0, 0, (float) sampleRate_); dispatch (effSetBlockSize, 0, jmax (16, samplesPerBlockExpected), 0, 0); tempBuffer.setSize (jmax (1, effect->numOutputs), samplesPerBlockExpected); if (! isPowerOn) setPower (true); // dodgy hack to force some plugins to initialise the sample rate.. if ((! hasEditor()) && getNumParameters() > 0) { const float old = getParameter (0); setParameter (0, (old < 0.5f) ? 1.0f : 0.0f); setParameter (0, old); } dispatch (effStartProcess, 0, 0, 0, 0); } } void VSTPluginInstance::releaseResources() { if (initialised) { dispatch (effStopProcess, 0, 0, 0, 0); setPower (false); } tempBuffer.setSize (1, 1); incomingMidi.clear(); midiEventsToSend.freeEvents(); } void VSTPluginInstance::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { const int numSamples = buffer.getNumSamples(); if (initialised) { AudioPlayHead* playHead = getPlayHead(); if (playHead != 0) { AudioPlayHead::CurrentPositionInfo position; playHead->getCurrentPosition (position); vstHostTime.tempo = position.bpm; vstHostTime.timeSigNumerator = position.timeSigNumerator; vstHostTime.timeSigDenominator = position.timeSigDenominator; vstHostTime.ppqPos = position.ppqPosition; vstHostTime.barStartPos = position.ppqPositionOfLastBarStart; vstHostTime.flags |= kVstTempoValid | kVstTimeSigValid | kVstPpqPosValid | kVstBarsValid; if (position.isPlaying) vstHostTime.flags |= kVstTransportPlaying; else vstHostTime.flags &= ~kVstTransportPlaying; } vstHostTime.nanoSeconds = getVSTHostTimeNanoseconds(); if (wantsMidiMessages) { midiEventsToSend.clear(); midiEventsToSend.ensureSize (1); MidiBuffer::Iterator iter (midiMessages); const uint8* midiData; int numBytesOfMidiData, samplePosition; while (iter.getNextEvent (midiData, numBytesOfMidiData, samplePosition)) { midiEventsToSend.addEvent (midiData, numBytesOfMidiData, jlimit (0, numSamples - 1, samplePosition)); } try { effect->dispatcher (effect, effProcessEvents, 0, 0, midiEventsToSend.events, 0); } catch (...) {} } _clearfp(); if ((effect->flags & effFlagsCanReplacing) != 0) { try { effect->processReplacing (effect, buffer.getArrayOfChannels(), buffer.getArrayOfChannels(), numSamples); } catch (...) {} } else { tempBuffer.setSize (effect->numOutputs, numSamples); tempBuffer.clear(); try { effect->process (effect, buffer.getArrayOfChannels(), tempBuffer.getArrayOfChannels(), numSamples); } catch (...) {} for (int i = effect->numOutputs; --i >= 0;) buffer.copyFrom (i, 0, tempBuffer.getSampleData (i), numSamples); } } else { // Not initialised, so just bypass.. for (int i = getNumInputChannels(); i < getNumOutputChannels(); ++i) buffer.clear (i, 0, buffer.getNumSamples()); } { // copy any incoming midi.. const ScopedLock sl (midiInLock); midiMessages.swapWith (incomingMidi); incomingMidi.clear(); } } void VSTPluginInstance::handleMidiFromPlugin (const VstEvents* const events) { if (events != 0) { const ScopedLock sl (midiInLock); VSTMidiEventList::addEventsToMidiBuffer (events, incomingMidi); } } static Array activeVSTWindows; class VSTPluginWindow : public AudioProcessorEditor, #if ! JUCE_MAC public ComponentMovementWatcher, #endif public Timer { public: VSTPluginWindow (VSTPluginInstance& plugin_) : AudioProcessorEditor (&plugin_), #if ! JUCE_MAC ComponentMovementWatcher (this), #endif plugin (plugin_), isOpen (false), wasShowing (false), pluginRefusesToResize (false), pluginWantsKeys (false), alreadyInside (false), recursiveResize (false) { #if JUCE_WINDOWS sizeCheckCount = 0; pluginHWND = 0; #elif JUCE_LINUX pluginWindow = None; pluginProc = None; #else addAndMakeVisible (innerWrapper = new InnerWrapperComponent (this)); #endif activeVSTWindows.add (this); setSize (1, 1); setOpaque (true); setVisible (true); } ~VSTPluginWindow() { #if JUCE_MAC innerWrapper = 0; #else closePluginWindow(); #endif activeVSTWindows.removeValue (this); plugin.editorBeingDeleted (this); } #if ! JUCE_MAC void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) { if (recursiveResize) return; Component* const topComp = getTopLevelComponent(); if (topComp->getPeer() != 0) { const Point pos (relativePositionToOtherComponent (topComp, Point())); recursiveResize = true; #if JUCE_WINDOWS if (pluginHWND != 0) MoveWindow (pluginHWND, pos.getX(), pos.getY(), getWidth(), getHeight(), TRUE); #elif JUCE_LINUX if (pluginWindow != 0) { XResizeWindow (display, pluginWindow, getWidth(), getHeight()); XMoveWindow (display, pluginWindow, pos.getX(), pos.getY()); XMapRaised (display, pluginWindow); } #endif recursiveResize = false; } } void componentVisibilityChanged (Component&) { const bool isShowingNow = isShowing(); if (wasShowing != isShowingNow) { wasShowing = isShowingNow; if (isShowingNow) openPluginWindow(); else closePluginWindow(); } componentMovedOrResized (true, true); } void componentPeerChanged() { closePluginWindow(); openPluginWindow(); } #endif bool keyStateChanged (bool) { return pluginWantsKeys; } bool keyPressed (const KeyPress&) { return pluginWantsKeys; } #if JUCE_MAC void paint (Graphics& g) { g.fillAll (Colours::black); } #else void paint (Graphics& g) { if (isOpen) { ComponentPeer* const peer = getPeer(); if (peer != 0) { const Point pos (getScreenPosition() - peer->getScreenPosition()); peer->addMaskedRegion (pos.getX(), pos.getY(), getWidth(), getHeight()); #if JUCE_LINUX if (pluginWindow != 0) { const Rectangle clip (g.getClipBounds()); XEvent ev; zerostruct (ev); ev.xexpose.type = Expose; ev.xexpose.display = display; ev.xexpose.window = pluginWindow; ev.xexpose.x = clip.getX(); ev.xexpose.y = clip.getY(); ev.xexpose.width = clip.getWidth(); ev.xexpose.height = clip.getHeight(); sendEventToChild (&ev); } #endif } } else { g.fillAll (Colours::black); } } #endif void timerCallback() { #if JUCE_WINDOWS if (--sizeCheckCount <= 0) { sizeCheckCount = 10; checkPluginWindowSize(); } #endif try { static bool reentrant = false; if (! reentrant) { reentrant = true; plugin.dispatch (effEditIdle, 0, 0, 0, 0); reentrant = false; } } catch (...) {} } void mouseDown (const MouseEvent& e) { #if JUCE_LINUX if (pluginWindow == 0) return; toFront (true); XEvent ev; zerostruct (ev); ev.xbutton.display = display; ev.xbutton.type = ButtonPress; ev.xbutton.window = pluginWindow; ev.xbutton.root = RootWindow (display, DefaultScreen (display)); ev.xbutton.time = CurrentTime; ev.xbutton.x = e.x; ev.xbutton.y = e.y; ev.xbutton.x_root = e.getScreenX(); ev.xbutton.y_root = e.getScreenY(); translateJuceToXButtonModifiers (e, ev); sendEventToChild (&ev); #elif JUCE_WINDOWS (void) e; toFront (true); #endif } void broughtToFront() { activeVSTWindows.removeValue (this); activeVSTWindows.add (this); #if JUCE_MAC dispatch (effEditTop, 0, 0, 0, 0); #endif } juce_UseDebuggingNewOperator private: VSTPluginInstance& plugin; bool isOpen, wasShowing, recursiveResize; bool pluginWantsKeys, pluginRefusesToResize, alreadyInside; #if JUCE_WINDOWS HWND pluginHWND; void* originalWndProc; int sizeCheckCount; #elif JUCE_LINUX Window pluginWindow; EventProcPtr pluginProc; #endif #if JUCE_MAC void openPluginWindow (WindowRef parentWindow) { if (isOpen || parentWindow == 0) return; isOpen = true; ERect* rect = 0; dispatch (effEditGetRect, 0, 0, &rect, 0); dispatch (effEditOpen, 0, 0, parentWindow, 0); // do this before and after like in the steinberg example dispatch (effEditGetRect, 0, 0, &rect, 0); dispatch (effGetProgram, 0, 0, 0, 0); // also in steinberg code // Install keyboard hooks pluginWantsKeys = (dispatch (effKeysRequired, 0, 0, 0, 0) == 0); // double-check it's not too tiny int w = 250, h = 150; if (rect != 0) { w = rect->right - rect->left; h = rect->bottom - rect->top; if (w == 0 || h == 0) { w = 250; h = 150; } } w = jmax (w, 32); h = jmax (h, 32); setSize (w, h); startTimer (18 + JUCE_NAMESPACE::Random::getSystemRandom().nextInt (5)); repaint(); } #else void openPluginWindow() { if (isOpen || getWindowHandle() == 0) return; log ("Opening VST UI: " + plugin.name); isOpen = true; ERect* rect = 0; dispatch (effEditGetRect, 0, 0, &rect, 0); dispatch (effEditOpen, 0, 0, getWindowHandle(), 0); // do this before and after like in the steinberg example dispatch (effEditGetRect, 0, 0, &rect, 0); dispatch (effGetProgram, 0, 0, 0, 0); // also in steinberg code // Install keyboard hooks pluginWantsKeys = (dispatch (effKeysRequired, 0, 0, 0, 0) == 0); #if JUCE_WINDOWS originalWndProc = 0; pluginHWND = GetWindow ((HWND) getWindowHandle(), GW_CHILD); if (pluginHWND == 0) { isOpen = false; setSize (300, 150); return; } #pragma warning (push) #pragma warning (disable: 4244) originalWndProc = (void*) GetWindowLongPtr (pluginHWND, GWL_WNDPROC); if (! pluginWantsKeys) SetWindowLongPtr (pluginHWND, GWLP_WNDPROC, (LONG_PTR) vstHookWndProc); #pragma warning (pop) int w, h; RECT r; GetWindowRect (pluginHWND, &r); w = r.right - r.left; h = r.bottom - r.top; if (rect != 0) { const int rw = rect->right - rect->left; const int rh = rect->bottom - rect->top; if ((rw > 50 && rh > 50 && rw < 2000 && rh < 2000 && rw != w && rh != h) || ((w == 0 && rw > 0) || (h == 0 && rh > 0))) { // very dodgy logic to decide which size is right. if (abs (rw - w) > 350 || abs (rh - h) > 350) { SetWindowPos (pluginHWND, 0, 0, 0, rw, rh, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); GetWindowRect (pluginHWND, &r); w = r.right - r.left; h = r.bottom - r.top; pluginRefusesToResize = (w != rw) || (h != rh); w = rw; h = rh; } } } #elif JUCE_LINUX pluginWindow = getChildWindow ((Window) getWindowHandle()); if (pluginWindow != 0) pluginProc = (EventProcPtr) getPropertyFromXWindow (pluginWindow, XInternAtom (display, "_XEventProc", False)); int w = 250, h = 150; if (rect != 0) { w = rect->right - rect->left; h = rect->bottom - rect->top; if (w == 0 || h == 0) { w = 250; h = 150; } } if (pluginWindow != 0) XMapRaised (display, pluginWindow); #endif // double-check it's not too tiny w = jmax (w, 32); h = jmax (h, 32); setSize (w, h); #if JUCE_WINDOWS checkPluginWindowSize(); #endif startTimer (18 + JUCE_NAMESPACE::Random::getSystemRandom().nextInt (5)); repaint(); } #endif #if ! JUCE_MAC void closePluginWindow() { if (isOpen) { log ("Closing VST UI: " + plugin.getName()); isOpen = false; dispatch (effEditClose, 0, 0, 0, 0); #if JUCE_WINDOWS #pragma warning (push) #pragma warning (disable: 4244) if (pluginHWND != 0 && IsWindow (pluginHWND)) SetWindowLongPtr (pluginHWND, GWLP_WNDPROC, (LONG_PTR) originalWndProc); #pragma warning (pop) stopTimer(); if (pluginHWND != 0 && IsWindow (pluginHWND)) DestroyWindow (pluginHWND); pluginHWND = 0; #elif JUCE_LINUX stopTimer(); pluginWindow = 0; pluginProc = 0; #endif } } #endif int dispatch (const int opcode, const int index, const int value, void* const ptr, float opt) { return plugin.dispatch (opcode, index, value, ptr, opt); } #if JUCE_WINDOWS void checkPluginWindowSize() { RECT r; GetWindowRect (pluginHWND, &r); const int w = r.right - r.left; const int h = r.bottom - r.top; if (isShowing() && w > 0 && h > 0 && (w != getWidth() || h != getHeight()) && ! pluginRefusesToResize) { setSize (w, h); sizeCheckCount = 0; } } // hooks to get keyboard events from VST windows.. static LRESULT CALLBACK vstHookWndProc (HWND hW, UINT message, WPARAM wParam, LPARAM lParam) { for (int i = activeVSTWindows.size(); --i >= 0;) { const VSTPluginWindow* const w = activeVSTWindows.getUnchecked (i); if (w->pluginHWND == hW) { if (message == WM_CHAR || message == WM_KEYDOWN || message == WM_SYSKEYDOWN || message == WM_KEYUP || message == WM_SYSKEYUP || message == WM_APPCOMMAND) { SendMessage ((HWND) w->getTopLevelComponent()->getWindowHandle(), message, wParam, lParam); } return CallWindowProc ((WNDPROC) (w->originalWndProc), (HWND) w->pluginHWND, message, wParam, lParam); } } return DefWindowProc (hW, message, wParam, lParam); } #endif #if JUCE_LINUX // overload mouse/keyboard events to forward them to the plugin's inner window.. void sendEventToChild (XEvent* event) { if (pluginProc != 0) { // if the plugin publishes an event procedure, pass the event directly.. pluginProc (event); } else if (pluginWindow != 0) { // if the plugin has a window, then send the event to the window so that // its message thread will pick it up.. XSendEvent (display, pluginWindow, False, 0L, event); XFlush (display); } } void mouseEnter (const MouseEvent& e) { if (pluginWindow != 0) { XEvent ev; zerostruct (ev); ev.xcrossing.display = display; ev.xcrossing.type = EnterNotify; ev.xcrossing.window = pluginWindow; ev.xcrossing.root = RootWindow (display, DefaultScreen (display)); ev.xcrossing.time = CurrentTime; ev.xcrossing.x = e.x; ev.xcrossing.y = e.y; ev.xcrossing.x_root = e.getScreenX(); ev.xcrossing.y_root = e.getScreenY(); ev.xcrossing.mode = NotifyNormal; // NotifyGrab, NotifyUngrab ev.xcrossing.detail = NotifyAncestor; // NotifyVirtual, NotifyInferior, NotifyNonlinear,NotifyNonlinearVirtual translateJuceToXCrossingModifiers (e, ev); sendEventToChild (&ev); } } void mouseExit (const MouseEvent& e) { if (pluginWindow != 0) { XEvent ev; zerostruct (ev); ev.xcrossing.display = display; ev.xcrossing.type = LeaveNotify; ev.xcrossing.window = pluginWindow; ev.xcrossing.root = RootWindow (display, DefaultScreen (display)); ev.xcrossing.time = CurrentTime; ev.xcrossing.x = e.x; ev.xcrossing.y = e.y; ev.xcrossing.x_root = e.getScreenX(); ev.xcrossing.y_root = e.getScreenY(); ev.xcrossing.mode = NotifyNormal; // NotifyGrab, NotifyUngrab ev.xcrossing.detail = NotifyAncestor; // NotifyVirtual, NotifyInferior, NotifyNonlinear,NotifyNonlinearVirtual ev.xcrossing.focus = hasKeyboardFocus (true); // TODO - yes ? translateJuceToXCrossingModifiers (e, ev); sendEventToChild (&ev); } } void mouseMove (const MouseEvent& e) { if (pluginWindow != 0) { XEvent ev; zerostruct (ev); ev.xmotion.display = display; ev.xmotion.type = MotionNotify; ev.xmotion.window = pluginWindow; ev.xmotion.root = RootWindow (display, DefaultScreen (display)); ev.xmotion.time = CurrentTime; ev.xmotion.is_hint = NotifyNormal; ev.xmotion.x = e.x; ev.xmotion.y = e.y; ev.xmotion.x_root = e.getScreenX(); ev.xmotion.y_root = e.getScreenY(); sendEventToChild (&ev); } } void mouseDrag (const MouseEvent& e) { if (pluginWindow != 0) { XEvent ev; zerostruct (ev); ev.xmotion.display = display; ev.xmotion.type = MotionNotify; ev.xmotion.window = pluginWindow; ev.xmotion.root = RootWindow (display, DefaultScreen (display)); ev.xmotion.time = CurrentTime; ev.xmotion.x = e.x ; ev.xmotion.y = e.y; ev.xmotion.x_root = e.getScreenX(); ev.xmotion.y_root = e.getScreenY(); ev.xmotion.is_hint = NotifyNormal; translateJuceToXMotionModifiers (e, ev); sendEventToChild (&ev); } } void mouseUp (const MouseEvent& e) { if (pluginWindow != 0) { XEvent ev; zerostruct (ev); ev.xbutton.display = display; ev.xbutton.type = ButtonRelease; ev.xbutton.window = pluginWindow; ev.xbutton.root = RootWindow (display, DefaultScreen (display)); ev.xbutton.time = CurrentTime; ev.xbutton.x = e.x; ev.xbutton.y = e.y; ev.xbutton.x_root = e.getScreenX(); ev.xbutton.y_root = e.getScreenY(); translateJuceToXButtonModifiers (e, ev); sendEventToChild (&ev); } } void mouseWheelMove (const MouseEvent& e, float incrementX, float incrementY) { if (pluginWindow != 0) { XEvent ev; zerostruct (ev); ev.xbutton.display = display; ev.xbutton.type = ButtonPress; ev.xbutton.window = pluginWindow; ev.xbutton.root = RootWindow (display, DefaultScreen (display)); ev.xbutton.time = CurrentTime; ev.xbutton.x = e.x; ev.xbutton.y = e.y; ev.xbutton.x_root = e.getScreenX(); ev.xbutton.y_root = e.getScreenY(); translateJuceToXMouseWheelModifiers (e, incrementY, ev); sendEventToChild (&ev); // TODO - put a usleep here ? ev.xbutton.type = ButtonRelease; sendEventToChild (&ev); } } #endif #if JUCE_MAC #if ! JUCE_SUPPORT_CARBON #error "To build VSTs, you need to enable the JUCE_SUPPORT_CARBON flag in your config!" #endif class InnerWrapperComponent : public CarbonViewWrapperComponent { public: InnerWrapperComponent (VSTPluginWindow* const owner_) : owner (owner_), alreadyInside (false) { } ~InnerWrapperComponent() { deleteWindow(); } HIViewRef attachView (WindowRef windowRef, HIViewRef rootView) { owner->openPluginWindow (windowRef); return 0; } void removeView (HIViewRef) { owner->dispatch (effEditClose, 0, 0, 0, 0); owner->dispatch (effEditSleep, 0, 0, 0, 0); } bool getEmbeddedViewSize (int& w, int& h) { ERect* rect = 0; owner->dispatch (effEditGetRect, 0, 0, &rect, 0); w = rect->right - rect->left; h = rect->bottom - rect->top; return true; } void mouseDown (int x, int y) { if (! alreadyInside) { alreadyInside = true; getTopLevelComponent()->toFront (true); owner->dispatch (effEditMouse, x, y, 0, 0); alreadyInside = false; } else { PostEvent (::mouseDown, 0); } } void paint() { ComponentPeer* const peer = getPeer(); if (peer != 0) { const Point pos (getScreenPosition() - peer->getScreenPosition()); ERect r; r.left = pos.getX(); r.right = r.left + getWidth(); r.top = pos.getY(); r.bottom = r.top + getHeight(); owner->dispatch (effEditDraw, 0, 0, &r, 0); } } private: VSTPluginWindow* const owner; bool alreadyInside; }; friend class InnerWrapperComponent; ScopedPointer innerWrapper; void resized() { innerWrapper->setSize (getWidth(), getHeight()); } #endif }; AudioProcessorEditor* VSTPluginInstance::createEditor() { if (hasEditor()) return new VSTPluginWindow (*this); return 0; } void VSTPluginInstance::handleAsyncUpdate() { // indicates that something about the plugin has changed.. updateHostDisplay(); } bool VSTPluginInstance::restoreProgramSettings (const fxProgram* const prog) { if (vst_swap (prog->chunkMagic) == 'CcnK' && vst_swap (prog->fxMagic) == 'FxCk') { changeProgramName (getCurrentProgram(), prog->prgName); for (int i = 0; i < vst_swap (prog->numParams); ++i) setParameter (i, vst_swapFloat (prog->params[i])); return true; } return false; } bool VSTPluginInstance::loadFromFXBFile (const void* const data, const int dataSize) { if (dataSize < 28) return false; const fxSet* const set = (const fxSet*) data; if ((vst_swap (set->chunkMagic) != 'CcnK' && vst_swap (set->chunkMagic) != 'KncC') || vst_swap (set->version) > fxbVersionNum) return false; if (vst_swap (set->fxMagic) == 'FxBk') { // bank of programs if (vst_swap (set->numPrograms) >= 0) { const int oldProg = getCurrentProgram(); const int numParams = vst_swap (((const fxProgram*) (set->programs))->numParams); const int progLen = sizeof (fxProgram) + (numParams - 1) * sizeof (float); for (int i = 0; i < vst_swap (set->numPrograms); ++i) { if (i != oldProg) { const fxProgram* const prog = (const fxProgram*) (((const char*) (set->programs)) + i * progLen); if (((const char*) prog) - ((const char*) set) >= dataSize) return false; if (vst_swap (set->numPrograms) > 0) setCurrentProgram (i); if (! restoreProgramSettings (prog)) return false; } } if (vst_swap (set->numPrograms) > 0) setCurrentProgram (oldProg); const fxProgram* const prog = (const fxProgram*) (((const char*) (set->programs)) + oldProg * progLen); if (((const char*) prog) - ((const char*) set) >= dataSize) return false; if (! restoreProgramSettings (prog)) return false; } } else if (vst_swap (set->fxMagic) == 'FxCk') { // single program const fxProgram* const prog = (const fxProgram*) data; if (vst_swap (prog->chunkMagic) != 'CcnK') return false; changeProgramName (getCurrentProgram(), prog->prgName); for (int i = 0; i < vst_swap (prog->numParams); ++i) setParameter (i, vst_swapFloat (prog->params[i])); } else if (vst_swap (set->fxMagic) == 'FBCh' || vst_swap (set->fxMagic) == 'hCBF') { // non-preset chunk const fxChunkSet* const cset = (const fxChunkSet*) data; if (vst_swap (cset->chunkSize) + sizeof (fxChunkSet) - 8 > (unsigned int) dataSize) return false; setChunkData (cset->chunk, vst_swap (cset->chunkSize), false); } else if (vst_swap (set->fxMagic) == 'FPCh' || vst_swap (set->fxMagic) == 'hCPF') { // preset chunk const fxProgramSet* const cset = (const fxProgramSet*) data; if (vst_swap (cset->chunkSize) + sizeof (fxProgramSet) - 8 > (unsigned int) dataSize) return false; setChunkData (cset->chunk, vst_swap (cset->chunkSize), true); changeProgramName (getCurrentProgram(), cset->name); } else { return false; } return true; } void VSTPluginInstance::setParamsInProgramBlock (fxProgram* const prog) { const int numParams = getNumParameters(); prog->chunkMagic = vst_swap ('CcnK'); prog->byteSize = 0; prog->fxMagic = vst_swap ('FxCk'); prog->version = vst_swap (fxbVersionNum); prog->fxID = vst_swap (getUID()); prog->fxVersion = vst_swap (getVersionNumber()); prog->numParams = vst_swap (numParams); getCurrentProgramName().copyToCString (prog->prgName, sizeof (prog->prgName) - 1); for (int i = 0; i < numParams; ++i) prog->params[i] = vst_swapFloat (getParameter (i)); } bool VSTPluginInstance::saveToFXBFile (MemoryBlock& dest, bool isFXB, int maxSizeMB) { const int numPrograms = getNumPrograms(); const int numParams = getNumParameters(); if (usesChunks()) { if (isFXB) { MemoryBlock chunk; getChunkData (chunk, false, maxSizeMB); const size_t totalLen = sizeof (fxChunkSet) + chunk.getSize() - 8; dest.setSize (totalLen, true); fxChunkSet* const set = (fxChunkSet*) dest.getData(); set->chunkMagic = vst_swap ('CcnK'); set->byteSize = 0; set->fxMagic = vst_swap ('FBCh'); set->version = vst_swap (fxbVersionNum); set->fxID = vst_swap (getUID()); set->fxVersion = vst_swap (getVersionNumber()); set->numPrograms = vst_swap (numPrograms); set->chunkSize = vst_swap ((long) chunk.getSize()); chunk.copyTo (set->chunk, 0, chunk.getSize()); } else { MemoryBlock chunk; getChunkData (chunk, true, maxSizeMB); const size_t totalLen = sizeof (fxProgramSet) + chunk.getSize() - 8; dest.setSize (totalLen, true); fxProgramSet* const set = (fxProgramSet*) dest.getData(); set->chunkMagic = vst_swap ('CcnK'); set->byteSize = 0; set->fxMagic = vst_swap ('FPCh'); set->version = vst_swap (fxbVersionNum); set->fxID = vst_swap (getUID()); set->fxVersion = vst_swap (getVersionNumber()); set->numPrograms = vst_swap (numPrograms); set->chunkSize = vst_swap ((long) chunk.getSize()); getCurrentProgramName().copyToCString (set->name, sizeof (set->name) - 1); chunk.copyTo (set->chunk, 0, chunk.getSize()); } } else { if (isFXB) { const int progLen = sizeof (fxProgram) + (numParams - 1) * sizeof (float); const int len = (sizeof (fxSet) - sizeof (fxProgram)) + progLen * jmax (1, numPrograms); dest.setSize (len, true); fxSet* const set = (fxSet*) dest.getData(); set->chunkMagic = vst_swap ('CcnK'); set->byteSize = 0; set->fxMagic = vst_swap ('FxBk'); set->version = vst_swap (fxbVersionNum); set->fxID = vst_swap (getUID()); set->fxVersion = vst_swap (getVersionNumber()); set->numPrograms = vst_swap (numPrograms); const int oldProgram = getCurrentProgram(); MemoryBlock oldSettings; createTempParameterStore (oldSettings); setParamsInProgramBlock ((fxProgram*) (((char*) (set->programs)) + oldProgram * progLen)); for (int i = 0; i < numPrograms; ++i) { if (i != oldProgram) { setCurrentProgram (i); setParamsInProgramBlock ((fxProgram*) (((char*) (set->programs)) + i * progLen)); } } setCurrentProgram (oldProgram); restoreFromTempParameterStore (oldSettings); } else { const int totalLen = sizeof (fxProgram) + (numParams - 1) * sizeof (float); dest.setSize (totalLen, true); setParamsInProgramBlock ((fxProgram*) dest.getData()); } } return true; } void VSTPluginInstance::getChunkData (MemoryBlock& mb, bool isPreset, int maxSizeMB) const { if (usesChunks()) { void* data = 0; const int bytes = dispatch (effGetChunk, isPreset ? 1 : 0, 0, &data, 0.0f); if (data != 0 && bytes <= maxSizeMB * 1024 * 1024) { mb.setSize (bytes); mb.copyFrom (data, 0, bytes); } } } void VSTPluginInstance::setChunkData (const char* data, int size, bool isPreset) { if (size > 0 && usesChunks()) { dispatch (effSetChunk, isPreset ? 1 : 0, size, (void*) data, 0.0f); if (! isPreset) updateStoredProgramNames(); } } void VSTPluginInstance::timerCallback() { if (dispatch (effIdle, 0, 0, 0, 0) == 0) stopTimer(); } int VSTPluginInstance::dispatch (const int opcode, const int index, const int value, void* const ptr, float opt) const { const ScopedLock sl (lock); ++insideVSTCallback; int result = 0; try { if (effect != 0) { #if JUCE_MAC if (module->resFileId != 0) UseResFile (module->resFileId); #endif result = effect->dispatcher (effect, opcode, index, value, ptr, opt); #if JUCE_MAC module->resFileId = CurResFile(); #endif --insideVSTCallback; return result; } } catch (...) { } --insideVSTCallback; return result; } // handles non plugin-specific callbacks.. static const int defaultVSTSampleRateValue = 16384; static const int defaultVSTBlockSizeValue = 512; static VstIntPtr handleGeneralCallback (VstInt32 opcode, VstInt32 index, VstInt32 value, void *ptr, float opt) { (void) index; (void) value; (void) opt; switch (opcode) { case audioMasterCanDo: { static const char* canDos[] = { "supplyIdle", "sendVstEvents", "sendVstMidiEvent", "sendVstTimeInfo", "receiveVstEvents", "receiveVstMidiEvent", "supportShell", "shellCategory" }; for (int i = 0; i < numElementsInArray (canDos); ++i) if (strcmp (canDos[i], (const char*) ptr) == 0) return 1; return 0; } case audioMasterVersion: return 0x2400; case audioMasterCurrentId: return shellUIDToCreate; case audioMasterGetNumAutomatableParameters: return 0; case audioMasterGetAutomationState: return 1; case audioMasterGetVendorVersion: return 0x0101; case audioMasterGetVendorString: case audioMasterGetProductString: { String hostName ("Juce VST Host"); if (JUCEApplication::getInstance() != 0) hostName = JUCEApplication::getInstance()->getApplicationName(); hostName.copyToCString ((char*) ptr, jmin (kVstMaxVendorStrLen, kVstMaxProductStrLen) - 1); break; } case audioMasterGetSampleRate: return (VstIntPtr) defaultVSTSampleRateValue; case audioMasterGetBlockSize: return (VstIntPtr) defaultVSTBlockSizeValue; case audioMasterSetOutputSampleRate: return 0; default: DBG ("*** Unhandled VST Callback: " + String ((int) opcode)); break; } return 0; } // handles callbacks for a specific plugin VstIntPtr VSTPluginInstance::handleCallback (VstInt32 opcode, VstInt32 index, VstInt32 value, void *ptr, float opt) { switch (opcode) { case audioMasterAutomate: sendParamChangeMessageToListeners (index, opt); break; case audioMasterProcessEvents: handleMidiFromPlugin ((const VstEvents*) ptr); break; case audioMasterGetTime: #if JUCE_MSVC #pragma warning (push) #pragma warning (disable: 4311) #endif return (VstIntPtr) &vstHostTime; #if JUCE_MSVC #pragma warning (pop) #endif break; case audioMasterIdle: if (insideVSTCallback == 0 && MessageManager::getInstance()->isThisTheMessageThread()) { ++insideVSTCallback; #if JUCE_MAC if (getActiveEditor() != 0) dispatch (effEditIdle, 0, 0, 0, 0); #endif juce_callAnyTimersSynchronously(); handleUpdateNowIfNeeded(); for (int i = ComponentPeer::getNumPeers(); --i >= 0;) ComponentPeer::getPeer (i)->performAnyPendingRepaintsNow(); --insideVSTCallback; } break; case audioMasterUpdateDisplay: triggerAsyncUpdate(); break; case audioMasterTempoAt: // returns (10000 * bpm) break; case audioMasterNeedIdle: startTimer (50); break; case audioMasterSizeWindow: if (getActiveEditor() != 0) getActiveEditor()->setSize (index, value); return 1; case audioMasterGetSampleRate: return (VstIntPtr) (getSampleRate() > 0 ? getSampleRate() : defaultVSTSampleRateValue); case audioMasterGetBlockSize: return (VstIntPtr) (getBlockSize() > 0 ? getBlockSize() : defaultVSTBlockSizeValue); case audioMasterWantMidi: wantsMidiMessages = true; break; case audioMasterGetDirectory: #if JUCE_MAC return (VstIntPtr) (void*) &module->parentDirFSSpec; #else return (VstIntPtr) (pointer_sized_uint) module->fullParentDirectoryPathName.toUTF8(); #endif case audioMasterGetAutomationState: // returns 0: not supported, 1: off, 2:read, 3:write, 4:read/write break; // none of these are handled (yet).. case audioMasterBeginEdit: case audioMasterEndEdit: case audioMasterSetTime: case audioMasterPinConnected: case audioMasterGetParameterQuantization: case audioMasterIOChanged: case audioMasterGetInputLatency: case audioMasterGetOutputLatency: case audioMasterGetPreviousPlug: case audioMasterGetNextPlug: case audioMasterWillReplaceOrAccumulate: case audioMasterGetCurrentProcessLevel: case audioMasterOfflineStart: case audioMasterOfflineRead: case audioMasterOfflineWrite: case audioMasterOfflineGetCurrentPass: case audioMasterOfflineGetCurrentMetaPass: case audioMasterVendorSpecific: case audioMasterSetIcon: case audioMasterGetLanguage: case audioMasterOpenWindow: case audioMasterCloseWindow: break; default: return handleGeneralCallback (opcode, index, value, ptr, opt); } return 0; } // entry point for all callbacks from the plugin static VstIntPtr VSTCALLBACK audioMaster (AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt) { try { if (effect != 0 && effect->resvd2 != 0) { return ((VSTPluginInstance*)(effect->resvd2)) ->handleCallback (opcode, index, value, ptr, opt); } return handleGeneralCallback (opcode, index, value, ptr, opt); } catch (...) { return 0; } } const String VSTPluginInstance::getVersion() const { unsigned int v = dispatch (effGetVendorVersion, 0, 0, 0, 0); String s; if (v == 0 || v == -1) v = getVersionNumber(); if (v != 0) { int versionBits[4]; int n = 0; while (v != 0) { versionBits [n++] = (v & 0xff); v >>= 8; } s << 'V'; while (n > 0) { s << versionBits [--n]; if (n > 0) s << '.'; } } return s; } int VSTPluginInstance::getUID() const { int uid = effect != 0 ? effect->uniqueID : 0; if (uid == 0) uid = module->file.hashCode(); return uid; } const String VSTPluginInstance::getCategory() const { const char* result = 0; switch (dispatch (effGetPlugCategory, 0, 0, 0, 0)) { case kPlugCategEffect: result = "Effect"; break; case kPlugCategSynth: result = "Synth"; break; case kPlugCategAnalysis: result = "Anaylsis"; break; case kPlugCategMastering: result = "Mastering"; break; case kPlugCategSpacializer: result = "Spacial"; break; case kPlugCategRoomFx: result = "Reverb"; break; case kPlugSurroundFx: result = "Surround"; break; case kPlugCategRestoration: result = "Restoration"; break; case kPlugCategGenerator: result = "Tone generation"; break; default: break; } return result; } float VSTPluginInstance::getParameter (int index) { if (effect != 0 && ((unsigned int) index) < (unsigned int) effect->numParams) { try { const ScopedLock sl (lock); return effect->getParameter (effect, index); } catch (...) { } } return 0.0f; } void VSTPluginInstance::setParameter (int index, float newValue) { if (effect != 0 && ((unsigned int) index) < (unsigned int) effect->numParams) { try { const ScopedLock sl (lock); if (effect->getParameter (effect, index) != newValue) effect->setParameter (effect, index, newValue); } catch (...) { } } } const String VSTPluginInstance::getParameterName (int index) { if (effect != 0) { jassert (index >= 0 && index < effect->numParams); char nm [256]; zerostruct (nm); dispatch (effGetParamName, index, 0, nm, 0); return String (nm).trim(); } return String::empty; } const String VSTPluginInstance::getParameterLabel (int index) const { if (effect != 0) { jassert (index >= 0 && index < effect->numParams); char nm [256]; zerostruct (nm); dispatch (effGetParamLabel, index, 0, nm, 0); return String (nm).trim(); } return String::empty; } const String VSTPluginInstance::getParameterText (int index) { if (effect != 0) { jassert (index >= 0 && index < effect->numParams); char nm [256]; zerostruct (nm); dispatch (effGetParamDisplay, index, 0, nm, 0); return String (nm).trim(); } return String::empty; } bool VSTPluginInstance::isParameterAutomatable (int index) const { if (effect != 0) { jassert (index >= 0 && index < effect->numParams); return dispatch (effCanBeAutomated, index, 0, 0, 0) != 0; } return false; } void VSTPluginInstance::createTempParameterStore (MemoryBlock& dest) { dest.setSize (64 + 4 * getNumParameters()); dest.fillWith (0); getCurrentProgramName().copyToCString ((char*) dest.getData(), 63); float* const p = (float*) (((char*) dest.getData()) + 64); for (int i = 0; i < getNumParameters(); ++i) p[i] = getParameter(i); } void VSTPluginInstance::restoreFromTempParameterStore (const MemoryBlock& m) { changeProgramName (getCurrentProgram(), (const char*) m.getData()); float* p = (float*) (((char*) m.getData()) + 64); for (int i = 0; i < getNumParameters(); ++i) setParameter (i, p[i]); } void VSTPluginInstance::setCurrentProgram (int newIndex) { if (getNumPrograms() > 0 && newIndex != getCurrentProgram()) dispatch (effSetProgram, 0, jlimit (0, getNumPrograms() - 1, newIndex), 0, 0); } const String VSTPluginInstance::getProgramName (int index) { if (index == getCurrentProgram()) { return getCurrentProgramName(); } else if (effect != 0) { char nm [256]; zerostruct (nm); if (dispatch (effGetProgramNameIndexed, jlimit (0, getNumPrograms(), index), -1, nm, 0) != 0) { return String (nm).trim(); } } return programNames [index]; } void VSTPluginInstance::changeProgramName (int index, const String& newName) { if (index == getCurrentProgram()) { if (getNumPrograms() > 0 && newName != getCurrentProgramName()) dispatch (effSetProgramName, 0, 0, (void*) newName.substring (0, 24).toCString(), 0.0f); } else { jassertfalse; // xxx not implemented! } } void VSTPluginInstance::updateStoredProgramNames() { if (effect != 0 && getNumPrograms() > 0) { char nm [256]; zerostruct (nm); // only do this if the plugin can't use indexed names.. if (dispatch (effGetProgramNameIndexed, 0, -1, nm, 0) == 0) { const int oldProgram = getCurrentProgram(); MemoryBlock oldSettings; createTempParameterStore (oldSettings); for (int i = 0; i < getNumPrograms(); ++i) { setCurrentProgram (i); getCurrentProgramName(); // (this updates the list) } setCurrentProgram (oldProgram); restoreFromTempParameterStore (oldSettings); } } } const String VSTPluginInstance::getCurrentProgramName() { if (effect != 0) { char nm [256]; zerostruct (nm); dispatch (effGetProgramName, 0, 0, nm, 0); const int index = getCurrentProgram(); if (programNames[index].isEmpty()) { while (programNames.size() < index) programNames.add (String::empty); programNames.set (index, String (nm).trim()); } return String (nm).trim(); } return String::empty; } const String VSTPluginInstance::getInputChannelName (int index) const { if (index >= 0 && index < getNumInputChannels()) { VstPinProperties pinProps; if (dispatch (effGetInputProperties, index, 0, &pinProps, 0.0f) != 0) return String (pinProps.label, sizeof (pinProps.label)); } return String::empty; } bool VSTPluginInstance::isInputChannelStereoPair (int index) const { if (index < 0 || index >= getNumInputChannels()) return false; VstPinProperties pinProps; if (dispatch (effGetInputProperties, index, 0, &pinProps, 0.0f) != 0) return (pinProps.flags & kVstPinIsStereo) != 0; return true; } const String VSTPluginInstance::getOutputChannelName (int index) const { if (index >= 0 && index < getNumOutputChannels()) { VstPinProperties pinProps; if (dispatch (effGetOutputProperties, index, 0, &pinProps, 0.0f) != 0) return String (pinProps.label, sizeof (pinProps.label)); } return String::empty; } bool VSTPluginInstance::isOutputChannelStereoPair (int index) const { if (index < 0 || index >= getNumOutputChannels()) return false; VstPinProperties pinProps; if (dispatch (effGetOutputProperties, index, 0, &pinProps, 0.0f) != 0) return (pinProps.flags & kVstPinIsStereo) != 0; return true; } void VSTPluginInstance::setPower (const bool on) { dispatch (effMainsChanged, 0, on ? 1 : 0, 0, 0); isPowerOn = on; } const int defaultMaxSizeMB = 64; void VSTPluginInstance::getStateInformation (MemoryBlock& destData) { saveToFXBFile (destData, true, defaultMaxSizeMB); } void VSTPluginInstance::getCurrentProgramStateInformation (MemoryBlock& destData) { saveToFXBFile (destData, false, defaultMaxSizeMB); } void VSTPluginInstance::setStateInformation (const void* data, int sizeInBytes) { loadFromFXBFile (data, sizeInBytes); } void VSTPluginInstance::setCurrentProgramStateInformation (const void* data, int sizeInBytes) { loadFromFXBFile (data, sizeInBytes); } VSTPluginFormat::VSTPluginFormat() { } VSTPluginFormat::~VSTPluginFormat() { } void VSTPluginFormat::findAllTypesForFile (OwnedArray & results, const String& fileOrIdentifier) { if (! fileMightContainThisPluginType (fileOrIdentifier)) return; PluginDescription desc; desc.fileOrIdentifier = fileOrIdentifier; desc.uid = 0; ScopedPointer instance (dynamic_cast (createInstanceFromDescription (desc))); if (instance == 0) return; try { #if JUCE_MAC if (instance->module->resFileId != 0) UseResFile (instance->module->resFileId); #endif instance->fillInPluginDescription (desc); VstPlugCategory category = (VstPlugCategory) instance->dispatch (effGetPlugCategory, 0, 0, 0, 0); if (category != kPlugCategShell) { // Normal plugin... results.add (new PluginDescription (desc)); ++insideVSTCallback; instance->dispatch (effOpen, 0, 0, 0, 0); --insideVSTCallback; } else { // It's a shell plugin, so iterate all the subtypes... char shellEffectName [64]; for (;;) { zerostruct (shellEffectName); const int uid = instance->dispatch (effShellGetNextPlugin, 0, 0, shellEffectName, 0); if (uid == 0) { break; } else { desc.uid = uid; desc.name = shellEffectName; bool alreadyThere = false; for (int i = results.size(); --i >= 0;) { PluginDescription* const d = results.getUnchecked(i); if (d->isDuplicateOf (desc)) { alreadyThere = true; break; } } if (! alreadyThere) results.add (new PluginDescription (desc)); } } } } catch (...) { // crashed while loading... } } AudioPluginInstance* VSTPluginFormat::createInstanceFromDescription (const PluginDescription& desc) { ScopedPointer result; if (fileMightContainThisPluginType (desc.fileOrIdentifier)) { File file (desc.fileOrIdentifier); const File previousWorkingDirectory (File::getCurrentWorkingDirectory()); file.getParentDirectory().setAsCurrentWorkingDirectory(); const ReferenceCountedObjectPtr module (ModuleHandle::findOrCreateModule (file)); if (module != 0) { shellUIDToCreate = desc.uid; result = new VSTPluginInstance (module); if (result->effect != 0) { result->effect->resvd2 = (VstIntPtr) (pointer_sized_int) (VSTPluginInstance*) result; result->initialise(); } else { result = 0; } } previousWorkingDirectory.setAsCurrentWorkingDirectory(); } return result.release(); } bool VSTPluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier) { const File f (fileOrIdentifier); #if JUCE_MAC if (f.isDirectory() && f.hasFileExtension (".vst")) return true; #if JUCE_PPC FSRef fileRef; if (PlatformUtilities::makeFSRefFromPath (&fileRef, f.getFullPathName())) { const short resFileId = FSOpenResFile (&fileRef, fsRdPerm); if (resFileId != -1) { const int numEffects = Count1Resources ('aEff'); CloseResFile (resFileId); if (numEffects > 0) return true; } } #endif return false; #elif JUCE_WINDOWS return f.existsAsFile() && f.hasFileExtension (".dll"); #elif JUCE_LINUX return f.existsAsFile() && f.hasFileExtension (".so"); #endif } const String VSTPluginFormat::getNameOfPluginFromIdentifier (const String& fileOrIdentifier) { return fileOrIdentifier; } bool VSTPluginFormat::doesPluginStillExist (const PluginDescription& desc) { return File (desc.fileOrIdentifier).exists(); } const StringArray VSTPluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive) { StringArray results; for (int j = 0; j < directoriesToSearch.getNumPaths(); ++j) recursiveFileSearch (results, directoriesToSearch [j], recursive); return results; } void VSTPluginFormat::recursiveFileSearch (StringArray& results, const File& dir, const bool recursive) { // avoid allowing the dir iterator to be recursive, because we want to avoid letting it delve inside // .component or .vst directories. DirectoryIterator iter (dir, false, "*", File::findFilesAndDirectories); while (iter.next()) { const File f (iter.getFile()); bool isPlugin = false; if (fileMightContainThisPluginType (f.getFullPathName())) { isPlugin = true; results.add (f.getFullPathName()); } if (recursive && (! isPlugin) && f.isDirectory()) recursiveFileSearch (results, f, true); } } const FileSearchPath VSTPluginFormat::getDefaultLocationsToSearch() { #if JUCE_MAC return FileSearchPath ("~/Library/Audio/Plug-Ins/VST;/Library/Audio/Plug-Ins/VST"); #elif JUCE_WINDOWS const String programFiles (File::getSpecialLocation (File::globalApplicationsDirectory).getFullPathName()); return FileSearchPath (programFiles + "\\Steinberg\\VstPlugins"); #elif JUCE_LINUX return FileSearchPath ("/usr/lib/vst"); #endif } END_JUCE_NAMESPACE #endif #undef log #endif /*** End of inlined file: juce_VSTPluginFormat.cpp ***/ /*** End of inlined file: juce_VSTPluginFormat.mm ***/ /*** Start of inlined file: juce_AudioProcessor.cpp ***/ BEGIN_JUCE_NAMESPACE AudioProcessor::AudioProcessor() : playHead (0), activeEditor (0), sampleRate (0), blockSize (0), numInputChannels (0), numOutputChannels (0), latencySamples (0), suspended (false), nonRealtime (false) { } AudioProcessor::~AudioProcessor() { // ooh, nasty - the editor should have been deleted before the filter // that it refers to is deleted.. jassert (activeEditor == 0); #if JUCE_DEBUG // This will fail if you've called beginParameterChangeGesture() for one // or more parameters without having made a corresponding call to endParameterChangeGesture... jassert (changingParams.countNumberOfSetBits() == 0); #endif } void AudioProcessor::setPlayHead (AudioPlayHead* const newPlayHead) throw() { playHead = newPlayHead; } void AudioProcessor::addListener (AudioProcessorListener* const newListener) { const ScopedLock sl (listenerLock); listeners.addIfNotAlreadyThere (newListener); } void AudioProcessor::removeListener (AudioProcessorListener* const listenerToRemove) { const ScopedLock sl (listenerLock); listeners.removeValue (listenerToRemove); } void AudioProcessor::setPlayConfigDetails (const int numIns, const int numOuts, const double sampleRate_, const int blockSize_) throw() { numInputChannels = numIns; numOutputChannels = numOuts; sampleRate = sampleRate_; blockSize = blockSize_; } void AudioProcessor::setNonRealtime (const bool nonRealtime_) throw() { nonRealtime = nonRealtime_; } void AudioProcessor::setLatencySamples (const int newLatency) { if (latencySamples != newLatency) { latencySamples = newLatency; updateHostDisplay(); } } void AudioProcessor::setParameterNotifyingHost (const int parameterIndex, const float newValue) { setParameter (parameterIndex, newValue); sendParamChangeMessageToListeners (parameterIndex, newValue); } void AudioProcessor::sendParamChangeMessageToListeners (const int parameterIndex, const float newValue) { jassert (((unsigned int) parameterIndex) < (unsigned int) getNumParameters()); for (int i = listeners.size(); --i >= 0;) { AudioProcessorListener* l; { const ScopedLock sl (listenerLock); l = listeners [i]; } if (l != 0) l->audioProcessorParameterChanged (this, parameterIndex, newValue); } } void AudioProcessor::beginParameterChangeGesture (int parameterIndex) { jassert (((unsigned int) parameterIndex) < (unsigned int) getNumParameters()); #if JUCE_DEBUG // This means you've called beginParameterChangeGesture twice in succession without a matching // call to endParameterChangeGesture. That might be fine in most hosts, but better to avoid doing it. jassert (! changingParams [parameterIndex]); changingParams.setBit (parameterIndex); #endif for (int i = listeners.size(); --i >= 0;) { AudioProcessorListener* l; { const ScopedLock sl (listenerLock); l = listeners [i]; } if (l != 0) l->audioProcessorParameterChangeGestureBegin (this, parameterIndex); } } void AudioProcessor::endParameterChangeGesture (int parameterIndex) { jassert (((unsigned int) parameterIndex) < (unsigned int) getNumParameters()); #if JUCE_DEBUG // This means you've called endParameterChangeGesture without having previously called // endParameterChangeGesture. That might be fine in most hosts, but better to keep the // calls matched correctly. jassert (changingParams [parameterIndex]); changingParams.clearBit (parameterIndex); #endif for (int i = listeners.size(); --i >= 0;) { AudioProcessorListener* l; { const ScopedLock sl (listenerLock); l = listeners [i]; } if (l != 0) l->audioProcessorParameterChangeGestureEnd (this, parameterIndex); } } void AudioProcessor::updateHostDisplay() { for (int i = listeners.size(); --i >= 0;) { AudioProcessorListener* l; { const ScopedLock sl (listenerLock); l = listeners [i]; } if (l != 0) l->audioProcessorChanged (this); } } bool AudioProcessor::isParameterAutomatable (int /*parameterIndex*/) const { return true; } bool AudioProcessor::isMetaParameter (int /*parameterIndex*/) const { return false; } void AudioProcessor::suspendProcessing (const bool shouldBeSuspended) { const ScopedLock sl (callbackLock); suspended = shouldBeSuspended; } void AudioProcessor::reset() { } void AudioProcessor::editorBeingDeleted (AudioProcessorEditor* const editor) throw() { const ScopedLock sl (callbackLock); jassert (activeEditor == editor); if (activeEditor == editor) activeEditor = 0; } AudioProcessorEditor* AudioProcessor::createEditorIfNeeded() { if (activeEditor != 0) return activeEditor; AudioProcessorEditor* const ed = createEditor(); if (ed != 0) { // you must give your editor comp a size before returning it.. jassert (ed->getWidth() > 0 && ed->getHeight() > 0); const ScopedLock sl (callbackLock); activeEditor = ed; } return ed; } void AudioProcessor::getCurrentProgramStateInformation (JUCE_NAMESPACE::MemoryBlock& destData) { getStateInformation (destData); } void AudioProcessor::setCurrentProgramStateInformation (const void* data, int sizeInBytes) { setStateInformation (data, sizeInBytes); } // magic number to identify memory blocks that we've stored as XML const uint32 magicXmlNumber = 0x21324356; void AudioProcessor::copyXmlToBinary (const XmlElement& xml, JUCE_NAMESPACE::MemoryBlock& destData) { const String xmlString (xml.createDocument (String::empty, true, false)); const int stringLength = xmlString.getNumBytesAsUTF8(); destData.setSize (stringLength + 10); char* const d = static_cast (destData.getData()); *(uint32*) d = ByteOrder::swapIfBigEndian ((const uint32) magicXmlNumber); *(uint32*) (d + 4) = ByteOrder::swapIfBigEndian ((const uint32) stringLength); xmlString.copyToUTF8 (d + 8, stringLength + 1); } XmlElement* AudioProcessor::getXmlFromBinary (const void* data, const int sizeInBytes) { if (sizeInBytes > 8 && ByteOrder::littleEndianInt (data) == magicXmlNumber) { const int stringLength = (int) ByteOrder::littleEndianInt (addBytesToPointer (data, 4)); if (stringLength > 0) { XmlDocument doc (String::fromUTF8 (static_cast (data) + 8, jmin ((sizeInBytes - 8), stringLength))); return doc.getDocumentElement(); } } return 0; } void AudioProcessorListener::audioProcessorParameterChangeGestureBegin (AudioProcessor*, int) {} void AudioProcessorListener::audioProcessorParameterChangeGestureEnd (AudioProcessor*, int) {} bool AudioPlayHead::CurrentPositionInfo::operator== (const CurrentPositionInfo& other) const throw() { return timeInSeconds == other.timeInSeconds && ppqPosition == other.ppqPosition && editOriginTime == other.editOriginTime && ppqPositionOfLastBarStart == other.ppqPositionOfLastBarStart && frameRate == other.frameRate && isPlaying == other.isPlaying && isRecording == other.isRecording && bpm == other.bpm && timeSigNumerator == other.timeSigNumerator && timeSigDenominator == other.timeSigDenominator; } bool AudioPlayHead::CurrentPositionInfo::operator!= (const CurrentPositionInfo& other) const throw() { return ! operator== (other); } void AudioPlayHead::CurrentPositionInfo::resetToDefault() { zerostruct (*this); timeSigNumerator = 4; timeSigDenominator = 4; bpm = 120; } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioProcessor.cpp ***/ /*** Start of inlined file: juce_AudioProcessorEditor.cpp ***/ BEGIN_JUCE_NAMESPACE AudioProcessorEditor::AudioProcessorEditor (AudioProcessor* const owner_) : owner (owner_) { // the filter must be valid.. jassert (owner != 0); } AudioProcessorEditor::~AudioProcessorEditor() { // if this fails, then the wrapper hasn't called editorBeingDeleted() on the // filter for some reason.. jassert (owner->getActiveEditor() != this); } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioProcessorEditor.cpp ***/ /*** Start of inlined file: juce_AudioProcessorGraph.cpp ***/ BEGIN_JUCE_NAMESPACE const int AudioProcessorGraph::midiChannelIndex = 0x1000; AudioProcessorGraph::Node::Node (const uint32 id_, AudioProcessor* const processor_) : id (id_), processor (processor_), isPrepared (false) { jassert (processor_ != 0); } AudioProcessorGraph::Node::~Node() { } void AudioProcessorGraph::Node::prepare (const double sampleRate, const int blockSize, AudioProcessorGraph* const graph) { if (! isPrepared) { isPrepared = true; AudioProcessorGraph::AudioGraphIOProcessor* const ioProc = dynamic_cast (static_cast (processor)); if (ioProc != 0) ioProc->setParentGraph (graph); processor->setPlayConfigDetails (processor->getNumInputChannels(), processor->getNumOutputChannels(), sampleRate, blockSize); processor->prepareToPlay (sampleRate, blockSize); } } void AudioProcessorGraph::Node::unprepare() { if (isPrepared) { isPrepared = false; processor->releaseResources(); } } AudioProcessorGraph::AudioProcessorGraph() : lastNodeId (0), renderingBuffers (1, 1), currentAudioOutputBuffer (1, 1) { } AudioProcessorGraph::~AudioProcessorGraph() { clearRenderingSequence(); clear(); } const String AudioProcessorGraph::getName() const { return "Audio Graph"; } void AudioProcessorGraph::clear() { nodes.clear(); connections.clear(); triggerAsyncUpdate(); } AudioProcessorGraph::Node* AudioProcessorGraph::getNodeForId (const uint32 nodeId) const { for (int i = nodes.size(); --i >= 0;) if (nodes.getUnchecked(i)->id == nodeId) return nodes.getUnchecked(i); return 0; } AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const newProcessor, uint32 nodeId) { if (newProcessor == 0) { jassertfalse; return 0; } if (nodeId == 0) { nodeId = ++lastNodeId; } else { // you can't add a node with an id that already exists in the graph.. jassert (getNodeForId (nodeId) == 0); removeNode (nodeId); } lastNodeId = nodeId; Node* const n = new Node (nodeId, newProcessor); nodes.add (n); triggerAsyncUpdate(); AudioProcessorGraph::AudioGraphIOProcessor* const ioProc = dynamic_cast (static_cast (n->processor)); if (ioProc != 0) ioProc->setParentGraph (this); return n; } bool AudioProcessorGraph::removeNode (const uint32 nodeId) { disconnectNode (nodeId); for (int i = nodes.size(); --i >= 0;) { if (nodes.getUnchecked(i)->id == nodeId) { AudioProcessorGraph::AudioGraphIOProcessor* const ioProc = dynamic_cast (static_cast (nodes.getUnchecked(i)->processor)); if (ioProc != 0) ioProc->setParentGraph (0); nodes.remove (i); triggerAsyncUpdate(); return true; } } return false; } const AudioProcessorGraph::Connection* AudioProcessorGraph::getConnectionBetween (const uint32 sourceNodeId, const int sourceChannelIndex, const uint32 destNodeId, const int destChannelIndex) const { for (int i = connections.size(); --i >= 0;) { const Connection* const c = connections.getUnchecked(i); if (c->sourceNodeId == sourceNodeId && c->destNodeId == destNodeId && c->sourceChannelIndex == sourceChannelIndex && c->destChannelIndex == destChannelIndex) { return c; } } return 0; } bool AudioProcessorGraph::isConnected (const uint32 possibleSourceNodeId, const uint32 possibleDestNodeId) const { for (int i = connections.size(); --i >= 0;) { const Connection* const c = connections.getUnchecked(i); if (c->sourceNodeId == possibleSourceNodeId && c->destNodeId == possibleDestNodeId) { return true; } } return false; } bool AudioProcessorGraph::canConnect (const uint32 sourceNodeId, const int sourceChannelIndex, const uint32 destNodeId, const int destChannelIndex) const { if (sourceChannelIndex < 0 || destChannelIndex < 0 || sourceNodeId == destNodeId || (destChannelIndex == midiChannelIndex) != (sourceChannelIndex == midiChannelIndex)) return false; const Node* const source = getNodeForId (sourceNodeId); if (source == 0 || (sourceChannelIndex != midiChannelIndex && sourceChannelIndex >= source->processor->getNumOutputChannels()) || (sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi())) return false; const Node* const dest = getNodeForId (destNodeId); if (dest == 0 || (destChannelIndex != midiChannelIndex && destChannelIndex >= dest->processor->getNumInputChannels()) || (destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi())) return false; return getConnectionBetween (sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex) == 0; } bool AudioProcessorGraph::addConnection (const uint32 sourceNodeId, const int sourceChannelIndex, const uint32 destNodeId, const int destChannelIndex) { if (! canConnect (sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex)) return false; Connection* const c = new Connection(); c->sourceNodeId = sourceNodeId; c->sourceChannelIndex = sourceChannelIndex; c->destNodeId = destNodeId; c->destChannelIndex = destChannelIndex; connections.add (c); triggerAsyncUpdate(); return true; } void AudioProcessorGraph::removeConnection (const int index) { connections.remove (index); triggerAsyncUpdate(); } bool AudioProcessorGraph::removeConnection (const uint32 sourceNodeId, const int sourceChannelIndex, const uint32 destNodeId, const int destChannelIndex) { bool doneAnything = false; for (int i = connections.size(); --i >= 0;) { const Connection* const c = connections.getUnchecked(i); if (c->sourceNodeId == sourceNodeId && c->destNodeId == destNodeId && c->sourceChannelIndex == sourceChannelIndex && c->destChannelIndex == destChannelIndex) { removeConnection (i); doneAnything = true; triggerAsyncUpdate(); } } return doneAnything; } bool AudioProcessorGraph::disconnectNode (const uint32 nodeId) { bool doneAnything = false; for (int i = connections.size(); --i >= 0;) { const Connection* const c = connections.getUnchecked(i); if (c->sourceNodeId == nodeId || c->destNodeId == nodeId) { removeConnection (i); doneAnything = true; triggerAsyncUpdate(); } } return doneAnything; } bool AudioProcessorGraph::removeIllegalConnections() { bool doneAnything = false; for (int i = connections.size(); --i >= 0;) { const Connection* const c = connections.getUnchecked(i); const Node* const source = getNodeForId (c->sourceNodeId); const Node* const dest = getNodeForId (c->destNodeId); if (source == 0 || dest == 0 || (c->sourceChannelIndex != midiChannelIndex && (((unsigned int) c->sourceChannelIndex) >= (unsigned int) source->processor->getNumOutputChannels())) || (c->sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi()) || (c->destChannelIndex != midiChannelIndex && (((unsigned int) c->destChannelIndex) >= (unsigned int) dest->processor->getNumInputChannels())) || (c->destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi())) { removeConnection (i); doneAnything = true; triggerAsyncUpdate(); } } return doneAnything; } namespace GraphRenderingOps { class AudioGraphRenderingOp { public: AudioGraphRenderingOp() {} virtual ~AudioGraphRenderingOp() {} virtual void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray & sharedMidiBuffers, const int numSamples) = 0; juce_UseDebuggingNewOperator }; class ClearChannelOp : public AudioGraphRenderingOp { public: ClearChannelOp (const int channelNum_) : channelNum (channelNum_) {} ~ClearChannelOp() {} void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray &, const int numSamples) { sharedBufferChans.clear (channelNum, 0, numSamples); } private: const int channelNum; ClearChannelOp (const ClearChannelOp&); ClearChannelOp& operator= (const ClearChannelOp&); }; class CopyChannelOp : public AudioGraphRenderingOp { public: CopyChannelOp (const int srcChannelNum_, const int dstChannelNum_) : srcChannelNum (srcChannelNum_), dstChannelNum (dstChannelNum_) {} ~CopyChannelOp() {} void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray &, const int numSamples) { sharedBufferChans.copyFrom (dstChannelNum, 0, sharedBufferChans, srcChannelNum, 0, numSamples); } private: const int srcChannelNum, dstChannelNum; CopyChannelOp (const CopyChannelOp&); CopyChannelOp& operator= (const CopyChannelOp&); }; class AddChannelOp : public AudioGraphRenderingOp { public: AddChannelOp (const int srcChannelNum_, const int dstChannelNum_) : srcChannelNum (srcChannelNum_), dstChannelNum (dstChannelNum_) {} ~AddChannelOp() {} void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray &, const int numSamples) { sharedBufferChans.addFrom (dstChannelNum, 0, sharedBufferChans, srcChannelNum, 0, numSamples); } private: const int srcChannelNum, dstChannelNum; AddChannelOp (const AddChannelOp&); AddChannelOp& operator= (const AddChannelOp&); }; class ClearMidiBufferOp : public AudioGraphRenderingOp { public: ClearMidiBufferOp (const int bufferNum_) : bufferNum (bufferNum_) {} ~ClearMidiBufferOp() {} void perform (AudioSampleBuffer&, const OwnedArray & sharedMidiBuffers, const int) { sharedMidiBuffers.getUnchecked (bufferNum)->clear(); } private: const int bufferNum; ClearMidiBufferOp (const ClearMidiBufferOp&); ClearMidiBufferOp& operator= (const ClearMidiBufferOp&); }; class CopyMidiBufferOp : public AudioGraphRenderingOp { public: CopyMidiBufferOp (const int srcBufferNum_, const int dstBufferNum_) : srcBufferNum (srcBufferNum_), dstBufferNum (dstBufferNum_) {} ~CopyMidiBufferOp() {} void perform (AudioSampleBuffer&, const OwnedArray & sharedMidiBuffers, const int) { *sharedMidiBuffers.getUnchecked (dstBufferNum) = *sharedMidiBuffers.getUnchecked (srcBufferNum); } private: const int srcBufferNum, dstBufferNum; CopyMidiBufferOp (const CopyMidiBufferOp&); CopyMidiBufferOp& operator= (const CopyMidiBufferOp&); }; class AddMidiBufferOp : public AudioGraphRenderingOp { public: AddMidiBufferOp (const int srcBufferNum_, const int dstBufferNum_) : srcBufferNum (srcBufferNum_), dstBufferNum (dstBufferNum_) {} ~AddMidiBufferOp() {} void perform (AudioSampleBuffer&, const OwnedArray & sharedMidiBuffers, const int numSamples) { sharedMidiBuffers.getUnchecked (dstBufferNum) ->addEvents (*sharedMidiBuffers.getUnchecked (srcBufferNum), 0, numSamples, 0); } private: const int srcBufferNum, dstBufferNum; AddMidiBufferOp (const AddMidiBufferOp&); AddMidiBufferOp& operator= (const AddMidiBufferOp&); }; class ProcessBufferOp : public AudioGraphRenderingOp { public: ProcessBufferOp (const AudioProcessorGraph::Node::Ptr& node_, const Array & audioChannelsToUse_, const int totalChans_, const int midiBufferToUse_) : node (node_), processor (node_->getProcessor()), audioChannelsToUse (audioChannelsToUse_), totalChans (jmax (1, totalChans_)), midiBufferToUse (midiBufferToUse_) { channels.calloc (totalChans); while (audioChannelsToUse.size() < totalChans) audioChannelsToUse.add (0); } ~ProcessBufferOp() { } void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray & sharedMidiBuffers, const int numSamples) { for (int i = totalChans; --i >= 0;) channels[i] = sharedBufferChans.getSampleData (audioChannelsToUse.getUnchecked (i), 0); AudioSampleBuffer buffer (channels, totalChans, numSamples); processor->processBlock (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); } const AudioProcessorGraph::Node::Ptr node; AudioProcessor* const processor; private: Array audioChannelsToUse; HeapBlock channels; int totalChans; int midiBufferToUse; ProcessBufferOp (const ProcessBufferOp&); ProcessBufferOp& operator= (const ProcessBufferOp&); }; /** Used to calculate the correct sequence of rendering ops needed, based on the best re-use of shared buffers at each stage. */ class RenderingOpSequenceCalculator { public: RenderingOpSequenceCalculator (AudioProcessorGraph& graph_, const Array& orderedNodes_, Array& renderingOps) : graph (graph_), orderedNodes (orderedNodes_) { nodeIds.add (-2); // first buffer is read-only zeros channels.add (0); midiNodeIds.add (-2); for (int i = 0; i < orderedNodes.size(); ++i) { createRenderingOpsForNode ((AudioProcessorGraph::Node*) orderedNodes.getUnchecked(i), renderingOps, i); markAnyUnusedBuffersAsFree (i); } } int getNumBuffersNeeded() const { return nodeIds.size(); } int getNumMidiBuffersNeeded() const { return midiNodeIds.size(); } juce_UseDebuggingNewOperator private: AudioProcessorGraph& graph; const Array& orderedNodes; Array nodeIds, channels, midiNodeIds; void createRenderingOpsForNode (AudioProcessorGraph::Node* const node, Array& renderingOps, const int ourRenderingIndex) { const int numIns = node->getProcessor()->getNumInputChannels(); const int numOuts = node->getProcessor()->getNumOutputChannels(); const int totalChans = jmax (numIns, numOuts); Array audioChannelsToUse; int midiBufferToUse = -1; for (int inputChan = 0; inputChan < numIns; ++inputChan) { // get a list of all the inputs to this node Array sourceNodes, sourceOutputChans; for (int i = graph.getNumConnections(); --i >= 0;) { const AudioProcessorGraph::Connection* const c = graph.getConnection (i); if (c->destNodeId == node->id && c->destChannelIndex == inputChan) { sourceNodes.add (c->sourceNodeId); sourceOutputChans.add (c->sourceChannelIndex); } } int bufIndex = -1; if (sourceNodes.size() == 0) { // unconnected input channel if (inputChan >= numOuts) { bufIndex = getReadOnlyEmptyBuffer(); jassert (bufIndex >= 0); } else { bufIndex = getFreeBuffer (false); renderingOps.add (new ClearChannelOp (bufIndex)); } } else if (sourceNodes.size() == 1) { // channel with a straightforward single input.. const int srcNode = sourceNodes.getUnchecked(0); const int srcChan = sourceOutputChans.getUnchecked(0); bufIndex = getBufferContaining (srcNode, srcChan); if (bufIndex < 0) { // if not found, this is probably a feedback loop bufIndex = getReadOnlyEmptyBuffer(); jassert (bufIndex >= 0); } if (inputChan < numOuts && isBufferNeededLater (ourRenderingIndex, inputChan, srcNode, srcChan)) { // can't mess up this channel because it's needed later by another node, so we // need to use a copy of it.. const int newFreeBuffer = getFreeBuffer (false); renderingOps.add (new CopyChannelOp (bufIndex, newFreeBuffer)); bufIndex = newFreeBuffer; } } else { // channel with a mix of several inputs.. // try to find a re-usable channel from our inputs.. int reusableInputIndex = -1; for (int i = 0; i < sourceNodes.size(); ++i) { const int sourceBufIndex = getBufferContaining (sourceNodes.getUnchecked(i), sourceOutputChans.getUnchecked(i)); if (sourceBufIndex >= 0 && ! isBufferNeededLater (ourRenderingIndex, inputChan, sourceNodes.getUnchecked(i), sourceOutputChans.getUnchecked(i))) { // we've found one of our input chans that can be re-used.. reusableInputIndex = i; bufIndex = sourceBufIndex; break; } } if (reusableInputIndex < 0) { // can't re-use any of our input chans, so get a new one and copy everything into it.. bufIndex = getFreeBuffer (false); jassert (bufIndex != 0); const int srcIndex = getBufferContaining (sourceNodes.getUnchecked (0), sourceOutputChans.getUnchecked (0)); if (srcIndex < 0) { // if not found, this is probably a feedback loop renderingOps.add (new ClearChannelOp (bufIndex)); } else { renderingOps.add (new CopyChannelOp (srcIndex, bufIndex)); } reusableInputIndex = 0; } for (int j = 0; j < sourceNodes.size(); ++j) { if (j != reusableInputIndex) { const int srcIndex = getBufferContaining (sourceNodes.getUnchecked(j), sourceOutputChans.getUnchecked(j)); if (srcIndex >= 0) renderingOps.add (new AddChannelOp (srcIndex, bufIndex)); } } } jassert (bufIndex >= 0); audioChannelsToUse.add (bufIndex); if (inputChan < numOuts) markBufferAsContaining (bufIndex, node->id, inputChan); } for (int outputChan = numIns; outputChan < numOuts; ++outputChan) { const int bufIndex = getFreeBuffer (false); jassert (bufIndex != 0); audioChannelsToUse.add (bufIndex); markBufferAsContaining (bufIndex, node->id, outputChan); } // Now the same thing for midi.. Array midiSourceNodes; for (int i = graph.getNumConnections(); --i >= 0;) { const AudioProcessorGraph::Connection* const c = graph.getConnection (i); if (c->destNodeId == node->id && c->destChannelIndex == AudioProcessorGraph::midiChannelIndex) midiSourceNodes.add (c->sourceNodeId); } if (midiSourceNodes.size() == 0) { // No midi inputs.. midiBufferToUse = getFreeBuffer (true); // need to pick a buffer even if the processor doesn't use midi if (node->getProcessor()->acceptsMidi() || node->getProcessor()->producesMidi()) renderingOps.add (new ClearMidiBufferOp (midiBufferToUse)); } else if (midiSourceNodes.size() == 1) { // One midi input.. midiBufferToUse = getBufferContaining (midiSourceNodes.getUnchecked(0), AudioProcessorGraph::midiChannelIndex); if (midiBufferToUse >= 0) { if (isBufferNeededLater (ourRenderingIndex, AudioProcessorGraph::midiChannelIndex, midiSourceNodes.getUnchecked(0), AudioProcessorGraph::midiChannelIndex)) { // can't mess up this channel because it's needed later by another node, so we // need to use a copy of it.. const int newFreeBuffer = getFreeBuffer (true); renderingOps.add (new CopyMidiBufferOp (midiBufferToUse, newFreeBuffer)); midiBufferToUse = newFreeBuffer; } } else { // probably a feedback loop, so just use an empty one.. midiBufferToUse = getFreeBuffer (true); // need to pick a buffer even if the processor doesn't use midi } } else { // More than one midi input being mixed.. int reusableInputIndex = -1; for (int i = 0; i < midiSourceNodes.size(); ++i) { const int sourceBufIndex = getBufferContaining (midiSourceNodes.getUnchecked(i), AudioProcessorGraph::midiChannelIndex); if (sourceBufIndex >= 0 && ! isBufferNeededLater (ourRenderingIndex, AudioProcessorGraph::midiChannelIndex, midiSourceNodes.getUnchecked(i), AudioProcessorGraph::midiChannelIndex)) { // we've found one of our input buffers that can be re-used.. reusableInputIndex = i; midiBufferToUse = sourceBufIndex; break; } } if (reusableInputIndex < 0) { // can't re-use any of our input buffers, so get a new one and copy everything into it.. midiBufferToUse = getFreeBuffer (true); jassert (midiBufferToUse >= 0); const int srcIndex = getBufferContaining (midiSourceNodes.getUnchecked(0), AudioProcessorGraph::midiChannelIndex); if (srcIndex >= 0) renderingOps.add (new CopyMidiBufferOp (srcIndex, midiBufferToUse)); else renderingOps.add (new ClearMidiBufferOp (midiBufferToUse)); reusableInputIndex = 0; } for (int j = 0; j < midiSourceNodes.size(); ++j) { if (j != reusableInputIndex) { const int srcIndex = getBufferContaining (midiSourceNodes.getUnchecked(j), AudioProcessorGraph::midiChannelIndex); if (srcIndex >= 0) renderingOps.add (new AddMidiBufferOp (srcIndex, midiBufferToUse)); } } } if (node->getProcessor()->producesMidi()) markBufferAsContaining (midiBufferToUse, node->id, AudioProcessorGraph::midiChannelIndex); renderingOps.add (new ProcessBufferOp (node, audioChannelsToUse, totalChans, midiBufferToUse)); } int getFreeBuffer (const bool forMidi) { if (forMidi) { for (int i = 1; i < midiNodeIds.size(); ++i) if (midiNodeIds.getUnchecked(i) < 0) return i; midiNodeIds.add (-1); return midiNodeIds.size() - 1; } else { for (int i = 1; i < nodeIds.size(); ++i) if (nodeIds.getUnchecked(i) < 0) return i; nodeIds.add (-1); channels.add (0); return nodeIds.size() - 1; } } int getReadOnlyEmptyBuffer() const { return 0; } int getBufferContaining (const int nodeId, const int outputChannel) const { if (outputChannel == AudioProcessorGraph::midiChannelIndex) { for (int i = midiNodeIds.size(); --i >= 0;) if (midiNodeIds.getUnchecked(i) == nodeId) return i; } else { for (int i = nodeIds.size(); --i >= 0;) if (nodeIds.getUnchecked(i) == nodeId && channels.getUnchecked(i) == outputChannel) return i; } return -1; } void markAnyUnusedBuffersAsFree (const int stepIndex) { int i; for (i = 0; i < nodeIds.size(); ++i) { if (nodeIds.getUnchecked(i) >= 0 && ! isBufferNeededLater (stepIndex, -1, nodeIds.getUnchecked(i), channels.getUnchecked(i))) { nodeIds.set (i, -1); } } for (i = 0; i < midiNodeIds.size(); ++i) { if (midiNodeIds.getUnchecked(i) >= 0 && ! isBufferNeededLater (stepIndex, -1, midiNodeIds.getUnchecked(i), AudioProcessorGraph::midiChannelIndex)) { midiNodeIds.set (i, -1); } } } bool isBufferNeededLater (int stepIndexToSearchFrom, int inputChannelOfIndexToIgnore, const int nodeId, const int outputChanIndex) const { while (stepIndexToSearchFrom < orderedNodes.size()) { const AudioProcessorGraph::Node* const node = (const AudioProcessorGraph::Node*) orderedNodes.getUnchecked (stepIndexToSearchFrom); if (outputChanIndex == AudioProcessorGraph::midiChannelIndex) { if (inputChannelOfIndexToIgnore != AudioProcessorGraph::midiChannelIndex && graph.getConnectionBetween (nodeId, AudioProcessorGraph::midiChannelIndex, node->id, AudioProcessorGraph::midiChannelIndex) != 0) return true; } else { for (int i = 0; i < node->getProcessor()->getNumInputChannels(); ++i) if (i != inputChannelOfIndexToIgnore && graph.getConnectionBetween (nodeId, outputChanIndex, node->id, i) != 0) return true; } inputChannelOfIndexToIgnore = -1; ++stepIndexToSearchFrom; } return false; } void markBufferAsContaining (int bufferNum, int nodeId, int outputIndex) { if (outputIndex == AudioProcessorGraph::midiChannelIndex) { jassert (bufferNum > 0 && bufferNum < midiNodeIds.size()); midiNodeIds.set (bufferNum, nodeId); } else { jassert (bufferNum >= 0 && bufferNum < nodeIds.size()); nodeIds.set (bufferNum, nodeId); channels.set (bufferNum, outputIndex); } } RenderingOpSequenceCalculator (const RenderingOpSequenceCalculator&); RenderingOpSequenceCalculator& operator= (const RenderingOpSequenceCalculator&); }; } void AudioProcessorGraph::clearRenderingSequence() { const ScopedLock sl (renderLock); for (int i = renderingOps.size(); --i >= 0;) { GraphRenderingOps::AudioGraphRenderingOp* const r = (GraphRenderingOps::AudioGraphRenderingOp*) renderingOps.getUnchecked(i); renderingOps.remove (i); delete r; } } bool AudioProcessorGraph::isAnInputTo (const uint32 possibleInputId, const uint32 possibleDestinationId, const int recursionCheck) const { if (recursionCheck > 0) { for (int i = connections.size(); --i >= 0;) { const AudioProcessorGraph::Connection* const c = connections.getUnchecked (i); if (c->destNodeId == possibleDestinationId && (c->sourceNodeId == possibleInputId || isAnInputTo (possibleInputId, c->sourceNodeId, recursionCheck - 1))) return true; } } return false; } void AudioProcessorGraph::buildRenderingSequence() { Array newRenderingOps; int numRenderingBuffersNeeded = 2; int numMidiBuffersNeeded = 1; { MessageManagerLock mml; Array orderedNodes; int i; for (i = 0; i < nodes.size(); ++i) { Node* const node = nodes.getUnchecked(i); node->prepare (getSampleRate(), getBlockSize(), this); int j = 0; for (; j < orderedNodes.size(); ++j) if (isAnInputTo (node->id, ((Node*) orderedNodes.getUnchecked (j))->id, nodes.size() + 1)) break; orderedNodes.insert (j, node); } GraphRenderingOps::RenderingOpSequenceCalculator calculator (*this, orderedNodes, newRenderingOps); numRenderingBuffersNeeded = calculator.getNumBuffersNeeded(); numMidiBuffersNeeded = calculator.getNumMidiBuffersNeeded(); } Array oldRenderingOps (renderingOps); { // swap over to the new rendering sequence.. const ScopedLock sl (renderLock); renderingBuffers.setSize (numRenderingBuffersNeeded, getBlockSize()); renderingBuffers.clear(); for (int i = midiBuffers.size(); --i >= 0;) midiBuffers.getUnchecked(i)->clear(); while (midiBuffers.size() < numMidiBuffersNeeded) midiBuffers.add (new MidiBuffer()); renderingOps = newRenderingOps; } for (int i = oldRenderingOps.size(); --i >= 0;) delete (GraphRenderingOps::AudioGraphRenderingOp*) oldRenderingOps.getUnchecked(i); } void AudioProcessorGraph::handleAsyncUpdate() { buildRenderingSequence(); } void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSamplesPerBlock) { currentAudioInputBuffer = 0; currentAudioOutputBuffer.setSize (jmax (1, getNumOutputChannels()), estimatedSamplesPerBlock); currentMidiInputBuffer = 0; currentMidiOutputBuffer.clear(); clearRenderingSequence(); buildRenderingSequence(); } void AudioProcessorGraph::releaseResources() { for (int i = 0; i < nodes.size(); ++i) nodes.getUnchecked(i)->unprepare(); renderingBuffers.setSize (1, 1); midiBuffers.clear(); currentAudioInputBuffer = 0; currentAudioOutputBuffer.setSize (1, 1); currentMidiInputBuffer = 0; currentMidiOutputBuffer.clear(); } void AudioProcessorGraph::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { const int numSamples = buffer.getNumSamples(); const ScopedLock sl (renderLock); currentAudioInputBuffer = &buffer; currentAudioOutputBuffer.setSize (jmax (1, buffer.getNumChannels()), numSamples); currentAudioOutputBuffer.clear(); currentMidiInputBuffer = &midiMessages; currentMidiOutputBuffer.clear(); int i; for (i = 0; i < renderingOps.size(); ++i) { GraphRenderingOps::AudioGraphRenderingOp* const op = (GraphRenderingOps::AudioGraphRenderingOp*) renderingOps.getUnchecked(i); op->perform (renderingBuffers, midiBuffers, numSamples); } for (i = 0; i < buffer.getNumChannels(); ++i) buffer.copyFrom (i, 0, currentAudioOutputBuffer, i, 0, numSamples); midiMessages.clear(); midiMessages.addEvents (currentMidiOutputBuffer, 0, buffer.getNumSamples(), 0); } const String AudioProcessorGraph::getInputChannelName (int channelIndex) const { return "Input " + String (channelIndex + 1); } const String AudioProcessorGraph::getOutputChannelName (int channelIndex) const { return "Output " + String (channelIndex + 1); } bool AudioProcessorGraph::isInputChannelStereoPair (int /*index*/) const { return true; } bool AudioProcessorGraph::isOutputChannelStereoPair (int /*index*/) const { return true; } bool AudioProcessorGraph::acceptsMidi() const { return true; } bool AudioProcessorGraph::producesMidi() const { return true; } void AudioProcessorGraph::getStateInformation (JUCE_NAMESPACE::MemoryBlock& /*destData*/) {} void AudioProcessorGraph::setStateInformation (const void* /*data*/, int /*sizeInBytes*/) {} AudioProcessorGraph::AudioGraphIOProcessor::AudioGraphIOProcessor (const IODeviceType type_) : type (type_), graph (0) { } AudioProcessorGraph::AudioGraphIOProcessor::~AudioGraphIOProcessor() { } const String AudioProcessorGraph::AudioGraphIOProcessor::getName() const { switch (type) { case audioOutputNode: return "Audio Output"; case audioInputNode: return "Audio Input"; case midiOutputNode: return "Midi Output"; case midiInputNode: return "Midi Input"; default: break; } return String::empty; } void AudioProcessorGraph::AudioGraphIOProcessor::fillInPluginDescription (PluginDescription& d) const { d.name = getName(); d.uid = d.name.hashCode(); d.category = "I/O devices"; d.pluginFormatName = "Internal"; d.manufacturerName = "Raw Material Software"; d.version = "1.0"; d.isInstrument = false; d.numInputChannels = getNumInputChannels(); if (type == audioOutputNode && graph != 0) d.numInputChannels = graph->getNumInputChannels(); d.numOutputChannels = getNumOutputChannels(); if (type == audioInputNode && graph != 0) d.numOutputChannels = graph->getNumOutputChannels(); } void AudioProcessorGraph::AudioGraphIOProcessor::prepareToPlay (double, int) { jassert (graph != 0); } void AudioProcessorGraph::AudioGraphIOProcessor::releaseResources() { } void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { jassert (graph != 0); switch (type) { case audioOutputNode: { for (int i = jmin (graph->currentAudioOutputBuffer.getNumChannels(), buffer.getNumChannels()); --i >= 0;) { graph->currentAudioOutputBuffer.addFrom (i, 0, buffer, i, 0, buffer.getNumSamples()); } break; } case audioInputNode: { for (int i = jmin (graph->currentAudioInputBuffer->getNumChannels(), buffer.getNumChannels()); --i >= 0;) { buffer.copyFrom (i, 0, *graph->currentAudioInputBuffer, i, 0, buffer.getNumSamples()); } break; } case midiOutputNode: graph->currentMidiOutputBuffer.addEvents (midiMessages, 0, buffer.getNumSamples(), 0); break; case midiInputNode: midiMessages.addEvents (*graph->currentMidiInputBuffer, 0, buffer.getNumSamples(), 0); break; default: break; } } bool AudioProcessorGraph::AudioGraphIOProcessor::acceptsMidi() const { return type == midiOutputNode; } bool AudioProcessorGraph::AudioGraphIOProcessor::producesMidi() const { return type == midiInputNode; } const String AudioProcessorGraph::AudioGraphIOProcessor::getInputChannelName (int channelIndex) const { switch (type) { case audioOutputNode: return "Output " + String (channelIndex + 1); case midiOutputNode: return "Midi Output"; default: break; } return String::empty; } const String AudioProcessorGraph::AudioGraphIOProcessor::getOutputChannelName (int channelIndex) const { switch (type) { case audioInputNode: return "Input " + String (channelIndex + 1); case midiInputNode: return "Midi Input"; default: break; } return String::empty; } bool AudioProcessorGraph::AudioGraphIOProcessor::isInputChannelStereoPair (int /*index*/) const { return type == audioInputNode || type == audioOutputNode; } bool AudioProcessorGraph::AudioGraphIOProcessor::isOutputChannelStereoPair (int index) const { return isInputChannelStereoPair (index); } bool AudioProcessorGraph::AudioGraphIOProcessor::isInput() const { return type == audioInputNode || type == midiInputNode; } bool AudioProcessorGraph::AudioGraphIOProcessor::isOutput() const { return type == audioOutputNode || type == midiOutputNode; } AudioProcessorEditor* AudioProcessorGraph::AudioGraphIOProcessor::createEditor() { return 0; } int AudioProcessorGraph::AudioGraphIOProcessor::getNumParameters() { return 0; } const String AudioProcessorGraph::AudioGraphIOProcessor::getParameterName (int) { return String::empty; } float AudioProcessorGraph::AudioGraphIOProcessor::getParameter (int) { return 0.0f; } const String AudioProcessorGraph::AudioGraphIOProcessor::getParameterText (int) { return String::empty; } void AudioProcessorGraph::AudioGraphIOProcessor::setParameter (int, float) { } int AudioProcessorGraph::AudioGraphIOProcessor::getNumPrograms() { return 0; } int AudioProcessorGraph::AudioGraphIOProcessor::getCurrentProgram() { return 0; } void AudioProcessorGraph::AudioGraphIOProcessor::setCurrentProgram (int) { } const String AudioProcessorGraph::AudioGraphIOProcessor::getProgramName (int) { return String::empty; } void AudioProcessorGraph::AudioGraphIOProcessor::changeProgramName (int, const String&) { } void AudioProcessorGraph::AudioGraphIOProcessor::getStateInformation (JUCE_NAMESPACE::MemoryBlock&) { } void AudioProcessorGraph::AudioGraphIOProcessor::setStateInformation (const void*, int) { } void AudioProcessorGraph::AudioGraphIOProcessor::setParentGraph (AudioProcessorGraph* const newGraph) { graph = newGraph; if (graph != 0) { setPlayConfigDetails (type == audioOutputNode ? graph->getNumOutputChannels() : 0, type == audioInputNode ? graph->getNumInputChannels() : 0, getSampleRate(), getBlockSize()); updateHostDisplay(); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioProcessorGraph.cpp ***/ /*** Start of inlined file: juce_AudioProcessorPlayer.cpp ***/ BEGIN_JUCE_NAMESPACE AudioProcessorPlayer::AudioProcessorPlayer() : processor (0), sampleRate (0), blockSize (0), isPrepared (false), numInputChans (0), numOutputChans (0), tempBuffer (1, 1) { } AudioProcessorPlayer::~AudioProcessorPlayer() { setProcessor (0); } void AudioProcessorPlayer::setProcessor (AudioProcessor* const processorToPlay) { if (processor != processorToPlay) { if (processorToPlay != 0 && sampleRate > 0 && blockSize > 0) { processorToPlay->setPlayConfigDetails (numInputChans, numOutputChans, sampleRate, blockSize); processorToPlay->prepareToPlay (sampleRate, blockSize); } AudioProcessor* oldOne; { const ScopedLock sl (lock); oldOne = isPrepared ? processor : 0; processor = processorToPlay; isPrepared = true; } if (oldOne != 0) oldOne->releaseResources(); } } void AudioProcessorPlayer::audioDeviceIOCallback (const float** const inputChannelData, const int numInputChannels, float** const outputChannelData, const int numOutputChannels, const int numSamples) { // these should have been prepared by audioDeviceAboutToStart()... jassert (sampleRate > 0 && blockSize > 0); incomingMidi.clear(); messageCollector.removeNextBlockOfMessages (incomingMidi, numSamples); int i, totalNumChans = 0; if (numInputChannels > numOutputChannels) { // if there aren't enough output channels for the number of // inputs, we need to create some temporary extra ones (can't // use the input data in case it gets written to) tempBuffer.setSize (numInputChannels - numOutputChannels, numSamples, false, false, true); for (i = 0; i < numOutputChannels; ++i) { channels[totalNumChans] = outputChannelData[i]; memcpy (channels[totalNumChans], inputChannelData[i], sizeof (float) * numSamples); ++totalNumChans; } for (i = numOutputChannels; i < numInputChannels; ++i) { channels[totalNumChans] = tempBuffer.getSampleData (i - numOutputChannels, 0); memcpy (channels[totalNumChans], inputChannelData[i], sizeof (float) * numSamples); ++totalNumChans; } } else { for (i = 0; i < numInputChannels; ++i) { channels[totalNumChans] = outputChannelData[i]; memcpy (channels[totalNumChans], inputChannelData[i], sizeof (float) * numSamples); ++totalNumChans; } for (i = numInputChannels; i < numOutputChannels; ++i) { channels[totalNumChans] = outputChannelData[i]; zeromem (channels[totalNumChans], sizeof (float) * numSamples); ++totalNumChans; } } AudioSampleBuffer buffer (channels, totalNumChans, numSamples); const ScopedLock sl (lock); if (processor != 0) { const ScopedLock sl (processor->getCallbackLock()); if (processor->isSuspended()) { for (i = 0; i < numOutputChannels; ++i) zeromem (outputChannelData[i], sizeof (float) * numSamples); } else { processor->processBlock (buffer, incomingMidi); } } } void AudioProcessorPlayer::audioDeviceAboutToStart (AudioIODevice* device) { const ScopedLock sl (lock); sampleRate = device->getCurrentSampleRate(); blockSize = device->getCurrentBufferSizeSamples(); numInputChans = device->getActiveInputChannels().countNumberOfSetBits(); numOutputChans = device->getActiveOutputChannels().countNumberOfSetBits(); messageCollector.reset (sampleRate); zeromem (channels, sizeof (channels)); if (processor != 0) { if (isPrepared) processor->releaseResources(); AudioProcessor* const oldProcessor = processor; setProcessor (0); setProcessor (oldProcessor); } } void AudioProcessorPlayer::audioDeviceStopped() { const ScopedLock sl (lock); if (processor != 0 && isPrepared) processor->releaseResources(); sampleRate = 0.0; blockSize = 0; isPrepared = false; tempBuffer.setSize (1, 1); } void AudioProcessorPlayer::handleIncomingMidiMessage (MidiInput*, const MidiMessage& message) { messageCollector.addMessageToQueue (message); } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioProcessorPlayer.cpp ***/ /*** Start of inlined file: juce_GenericAudioProcessorEditor.cpp ***/ BEGIN_JUCE_NAMESPACE class ProcessorParameterPropertyComp : public PropertyComponent, public AudioProcessorListener, public AsyncUpdater { public: ProcessorParameterPropertyComp (const String& name, AudioProcessor* const owner_, const int index_) : PropertyComponent (name), owner (owner_), index (index_) { addAndMakeVisible (slider = new ParamSlider (owner_, index_)); owner_->addListener (this); } ~ProcessorParameterPropertyComp() { owner->removeListener (this); deleteAllChildren(); } void refresh() { slider->setValue (owner->getParameter (index), false); } void audioProcessorChanged (AudioProcessor*) {} void audioProcessorParameterChanged (AudioProcessor*, int parameterIndex, float) { if (parameterIndex == index) triggerAsyncUpdate(); } void handleAsyncUpdate() { refresh(); } juce_UseDebuggingNewOperator private: AudioProcessor* const owner; const int index; Slider* slider; class ParamSlider : public Slider { public: ParamSlider (AudioProcessor* const owner_, const int index_) : Slider (String::empty), owner (owner_), index (index_) { setRange (0.0, 1.0, 0.0); setSliderStyle (Slider::LinearBar); setTextBoxIsEditable (false); setScrollWheelEnabled (false); } ~ParamSlider() { } void valueChanged() { const float newVal = (float) getValue(); if (owner->getParameter (index) != newVal) owner->setParameter (index, newVal); } const String getTextFromValue (double /*value*/) { return owner->getParameterText (index); } juce_UseDebuggingNewOperator private: AudioProcessor* const owner; const int index; ParamSlider (const ParamSlider&); ParamSlider& operator= (const ParamSlider&); }; ProcessorParameterPropertyComp (const ProcessorParameterPropertyComp&); ProcessorParameterPropertyComp& operator= (const ProcessorParameterPropertyComp&); }; GenericAudioProcessorEditor::GenericAudioProcessorEditor (AudioProcessor* const owner_) : AudioProcessorEditor (owner_) { setOpaque (true); addAndMakeVisible (panel = new PropertyPanel()); Array params; const int numParams = owner_->getNumParameters(); int totalHeight = 0; for (int i = 0; i < numParams; ++i) { String name (owner_->getParameterName (i)); if (name.trim().isEmpty()) name = "Unnamed"; ProcessorParameterPropertyComp* const pc = new ProcessorParameterPropertyComp (name, owner_, i); params.add (pc); totalHeight += pc->getPreferredHeight(); } panel->addProperties (params); setSize (400, jlimit (25, 400, totalHeight)); } GenericAudioProcessorEditor::~GenericAudioProcessorEditor() { deleteAllChildren(); } void GenericAudioProcessorEditor::paint (Graphics& g) { g.fillAll (Colours::white); } void GenericAudioProcessorEditor::resized() { panel->setSize (getWidth(), getHeight()); } END_JUCE_NAMESPACE /*** End of inlined file: juce_GenericAudioProcessorEditor.cpp ***/ /*** Start of inlined file: juce_Sampler.cpp ***/ BEGIN_JUCE_NAMESPACE SamplerSound::SamplerSound (const String& name_, AudioFormatReader& source, const BigInteger& midiNotes_, const int midiNoteForNormalPitch, const double attackTimeSecs, const double releaseTimeSecs, const double maxSampleLengthSeconds) : name (name_), midiNotes (midiNotes_), midiRootNote (midiNoteForNormalPitch) { sourceSampleRate = source.sampleRate; if (sourceSampleRate <= 0 || source.lengthInSamples <= 0) { length = 0; attackSamples = 0; releaseSamples = 0; } else { length = jmin ((int) source.lengthInSamples, (int) (maxSampleLengthSeconds * sourceSampleRate)); data = new AudioSampleBuffer (jmin (2, (int) source.numChannels), length + 4); data->readFromAudioReader (&source, 0, length + 4, 0, true, true); attackSamples = roundToInt (attackTimeSecs * sourceSampleRate); releaseSamples = roundToInt (releaseTimeSecs * sourceSampleRate); } } SamplerSound::~SamplerSound() { } bool SamplerSound::appliesToNote (const int midiNoteNumber) { return midiNotes [midiNoteNumber]; } bool SamplerSound::appliesToChannel (const int /*midiChannel*/) { return true; } SamplerVoice::SamplerVoice() : pitchRatio (0.0), sourceSamplePosition (0.0), lgain (0.0f), rgain (0.0f), isInAttack (false), isInRelease (false) { } SamplerVoice::~SamplerVoice() { } bool SamplerVoice::canPlaySound (SynthesiserSound* sound) { return dynamic_cast (sound) != 0; } void SamplerVoice::startNote (const int midiNoteNumber, const float velocity, SynthesiserSound* s, const int /*currentPitchWheelPosition*/) { const SamplerSound* const sound = dynamic_cast (s); jassert (sound != 0); // this object can only play SamplerSounds! if (sound != 0) { const double targetFreq = MidiMessage::getMidiNoteInHertz (midiNoteNumber); const double naturalFreq = MidiMessage::getMidiNoteInHertz (sound->midiRootNote); pitchRatio = (targetFreq * sound->sourceSampleRate) / (naturalFreq * getSampleRate()); sourceSamplePosition = 0.0; lgain = velocity; rgain = velocity; isInAttack = (sound->attackSamples > 0); isInRelease = false; if (isInAttack) { attackReleaseLevel = 0.0f; attackDelta = (float) (pitchRatio / sound->attackSamples); } else { attackReleaseLevel = 1.0f; attackDelta = 0.0f; } if (sound->releaseSamples > 0) { releaseDelta = (float) (-pitchRatio / sound->releaseSamples); } else { releaseDelta = 0.0f; } } } void SamplerVoice::stopNote (const bool allowTailOff) { if (allowTailOff) { isInAttack = false; isInRelease = true; } else { clearCurrentNote(); } } void SamplerVoice::pitchWheelMoved (const int /*newValue*/) { } void SamplerVoice::controllerMoved (const int /*controllerNumber*/, const int /*newValue*/) { } void SamplerVoice::renderNextBlock (AudioSampleBuffer& outputBuffer, int startSample, int numSamples) { const SamplerSound* const playingSound = static_cast (getCurrentlyPlayingSound().getObject()); if (playingSound != 0) { const float* const inL = playingSound->data->getSampleData (0, 0); const float* const inR = playingSound->data->getNumChannels() > 1 ? playingSound->data->getSampleData (1, 0) : 0; float* outL = outputBuffer.getSampleData (0, startSample); float* outR = outputBuffer.getNumChannels() > 1 ? outputBuffer.getSampleData (1, startSample) : 0; while (--numSamples >= 0) { const int pos = (int) sourceSamplePosition; const float alpha = (float) (sourceSamplePosition - pos); const float invAlpha = 1.0f - alpha; // just using a very simple linear interpolation here.. float l = (inL [pos] * invAlpha + inL [pos + 1] * alpha); float r = (inR != 0) ? (inR [pos] * invAlpha + inR [pos + 1] * alpha) : l; l *= lgain; r *= rgain; if (isInAttack) { l *= attackReleaseLevel; r *= attackReleaseLevel; attackReleaseLevel += attackDelta; if (attackReleaseLevel >= 1.0f) { attackReleaseLevel = 1.0f; isInAttack = false; } } else if (isInRelease) { l *= attackReleaseLevel; r *= attackReleaseLevel; attackReleaseLevel += releaseDelta; if (attackReleaseLevel <= 0.0f) { stopNote (false); break; } } if (outR != 0) { *outL++ += l; *outR++ += r; } else { *outL++ += (l + r) * 0.5f; } sourceSamplePosition += pitchRatio; if (sourceSamplePosition > playingSound->length) { stopNote (false); break; } } } } END_JUCE_NAMESPACE /*** End of inlined file: juce_Sampler.cpp ***/ /*** Start of inlined file: juce_Synthesiser.cpp ***/ BEGIN_JUCE_NAMESPACE SynthesiserSound::SynthesiserSound() { } SynthesiserSound::~SynthesiserSound() { } SynthesiserVoice::SynthesiserVoice() : currentSampleRate (44100.0), currentlyPlayingNote (-1), noteOnTime (0), currentlyPlayingSound (0) { } SynthesiserVoice::~SynthesiserVoice() { } bool SynthesiserVoice::isPlayingChannel (const int midiChannel) const { return currentlyPlayingSound != 0 && currentlyPlayingSound->appliesToChannel (midiChannel); } void SynthesiserVoice::setCurrentPlaybackSampleRate (const double newRate) { currentSampleRate = newRate; } void SynthesiserVoice::clearCurrentNote() { currentlyPlayingNote = -1; currentlyPlayingSound = 0; } Synthesiser::Synthesiser() : sampleRate (0), lastNoteOnCounter (0), shouldStealNotes (true) { for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i) lastPitchWheelValues[i] = 0x2000; } Synthesiser::~Synthesiser() { } SynthesiserVoice* Synthesiser::getVoice (const int index) const { const ScopedLock sl (lock); return voices [index]; } void Synthesiser::clearVoices() { const ScopedLock sl (lock); voices.clear(); } void Synthesiser::addVoice (SynthesiserVoice* const newVoice) { const ScopedLock sl (lock); voices.add (newVoice); } void Synthesiser::removeVoice (const int index) { const ScopedLock sl (lock); voices.remove (index); } void Synthesiser::clearSounds() { const ScopedLock sl (lock); sounds.clear(); } void Synthesiser::addSound (const SynthesiserSound::Ptr& newSound) { const ScopedLock sl (lock); sounds.add (newSound); } void Synthesiser::removeSound (const int index) { const ScopedLock sl (lock); sounds.remove (index); } void Synthesiser::setNoteStealingEnabled (const bool shouldStealNotes_) { shouldStealNotes = shouldStealNotes_; } void Synthesiser::setCurrentPlaybackSampleRate (const double newRate) { if (sampleRate != newRate) { const ScopedLock sl (lock); allNotesOff (0, false); sampleRate = newRate; for (int i = voices.size(); --i >= 0;) voices.getUnchecked (i)->setCurrentPlaybackSampleRate (newRate); } } void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBuffer& midiData, int startSample, int numSamples) { // must set the sample rate before using this! jassert (sampleRate != 0); const ScopedLock sl (lock); MidiBuffer::Iterator midiIterator (midiData); midiIterator.setNextSamplePosition (startSample); MidiMessage m (0xf4, 0.0); while (numSamples > 0) { int midiEventPos; const bool useEvent = midiIterator.getNextEvent (m, midiEventPos) && midiEventPos < startSample + numSamples; const int numThisTime = useEvent ? midiEventPos - startSample : numSamples; if (numThisTime > 0) { for (int i = voices.size(); --i >= 0;) voices.getUnchecked (i)->renderNextBlock (outputBuffer, startSample, numThisTime); } if (useEvent) { if (m.isNoteOn()) { const int channel = m.getChannel(); noteOn (channel, m.getNoteNumber(), m.getFloatVelocity()); } else if (m.isNoteOff()) { noteOff (m.getChannel(), m.getNoteNumber(), true); } else if (m.isAllNotesOff() || m.isAllSoundOff()) { allNotesOff (m.getChannel(), true); } else if (m.isPitchWheel()) { const int channel = m.getChannel(); const int wheelPos = m.getPitchWheelValue(); lastPitchWheelValues [channel - 1] = wheelPos; handlePitchWheel (channel, wheelPos); } else if (m.isController()) { handleController (m.getChannel(), m.getControllerNumber(), m.getControllerValue()); } } startSample += numThisTime; numSamples -= numThisTime; } } void Synthesiser::noteOn (const int midiChannel, const int midiNoteNumber, const float velocity) { const ScopedLock sl (lock); for (int i = sounds.size(); --i >= 0;) { SynthesiserSound* const sound = sounds.getUnchecked(i); if (sound->appliesToNote (midiNoteNumber) && sound->appliesToChannel (midiChannel)) { startVoice (findFreeVoice (sound, shouldStealNotes), sound, midiChannel, midiNoteNumber, velocity); } } } void Synthesiser::startVoice (SynthesiserVoice* const voice, SynthesiserSound* const sound, const int midiChannel, const int midiNoteNumber, const float velocity) { if (voice != 0 && sound != 0) { if (voice->currentlyPlayingSound != 0) voice->stopNote (false); voice->startNote (midiNoteNumber, velocity, sound, lastPitchWheelValues [midiChannel - 1]); voice->currentlyPlayingNote = midiNoteNumber; voice->noteOnTime = ++lastNoteOnCounter; voice->currentlyPlayingSound = sound; } } void Synthesiser::noteOff (const int midiChannel, const int midiNoteNumber, const bool allowTailOff) { const ScopedLock sl (lock); for (int i = voices.size(); --i >= 0;) { SynthesiserVoice* const voice = voices.getUnchecked (i); if (voice->getCurrentlyPlayingNote() == midiNoteNumber) { SynthesiserSound* const sound = voice->getCurrentlyPlayingSound(); if (sound != 0 && sound->appliesToNote (midiNoteNumber) && sound->appliesToChannel (midiChannel)) { voice->stopNote (allowTailOff); // the subclass MUST call clearCurrentNote() if it's not tailing off! RTFM for stopNote()! jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == 0)); } } } } void Synthesiser::allNotesOff (const int midiChannel, const bool allowTailOff) { const ScopedLock sl (lock); for (int i = voices.size(); --i >= 0;) { SynthesiserVoice* const voice = voices.getUnchecked (i); if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) voice->stopNote (allowTailOff); } } void Synthesiser::handlePitchWheel (const int midiChannel, const int wheelValue) { const ScopedLock sl (lock); for (int i = voices.size(); --i >= 0;) { SynthesiserVoice* const voice = voices.getUnchecked (i); if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) { voice->pitchWheelMoved (wheelValue); } } } void Synthesiser::handleController (const int midiChannel, const int controllerNumber, const int controllerValue) { const ScopedLock sl (lock); for (int i = voices.size(); --i >= 0;) { SynthesiserVoice* const voice = voices.getUnchecked (i); if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) voice->controllerMoved (controllerNumber, controllerValue); } } SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, const bool stealIfNoneAvailable) const { const ScopedLock sl (lock); for (int i = voices.size(); --i >= 0;) if (voices.getUnchecked (i)->getCurrentlyPlayingNote() < 0 && voices.getUnchecked (i)->canPlaySound (soundToPlay)) return voices.getUnchecked (i); if (stealIfNoneAvailable) { // currently this just steals the one that's been playing the longest, but could be made a bit smarter.. SynthesiserVoice* oldest = 0; for (int i = voices.size(); --i >= 0;) { SynthesiserVoice* const voice = voices.getUnchecked (i); if (voice->canPlaySound (soundToPlay) && (oldest == 0 || oldest->noteOnTime > voice->noteOnTime)) oldest = voice; } jassert (oldest != 0); return oldest; } return 0; } END_JUCE_NAMESPACE /*** End of inlined file: juce_Synthesiser.cpp ***/ /*** Start of inlined file: juce_ActionBroadcaster.cpp ***/ BEGIN_JUCE_NAMESPACE ActionBroadcaster::ActionBroadcaster() throw() { // are you trying to create this object before or after juce has been intialised?? jassert (MessageManager::instance != 0); } ActionBroadcaster::~ActionBroadcaster() { // all event-based objects must be deleted BEFORE juce is shut down! jassert (MessageManager::instance != 0); } void ActionBroadcaster::addActionListener (ActionListener* const listener) { actionListenerList.addActionListener (listener); } void ActionBroadcaster::removeActionListener (ActionListener* const listener) { jassert (actionListenerList.isValidMessageListener()); if (actionListenerList.isValidMessageListener()) actionListenerList.removeActionListener (listener); } void ActionBroadcaster::removeAllActionListeners() { actionListenerList.removeAllActionListeners(); } void ActionBroadcaster::sendActionMessage (const String& message) const { actionListenerList.sendActionMessage (message); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ActionBroadcaster.cpp ***/ /*** Start of inlined file: juce_ActionListenerList.cpp ***/ BEGIN_JUCE_NAMESPACE // special message of our own with a string in it class ActionMessage : public Message { public: const String message; ActionMessage (const String& messageText, void* const listener_) throw() : message (messageText) { pointerParameter = listener_; } ~ActionMessage() throw() { } private: ActionMessage (const ActionMessage&); ActionMessage& operator= (const ActionMessage&); }; ActionListenerList::ActionListenerList() { } ActionListenerList::~ActionListenerList() { } void ActionListenerList::addActionListener (ActionListener* const listener) { const ScopedLock sl (actionListenerLock_); jassert (listener != 0); jassert (! actionListeners_.contains (listener)); // trying to add a listener to the list twice! if (listener != 0) actionListeners_.add (listener); } void ActionListenerList::removeActionListener (ActionListener* const listener) { const ScopedLock sl (actionListenerLock_); jassert (actionListeners_.contains (listener)); // trying to remove a listener that isn't on the list! actionListeners_.removeValue (listener); } void ActionListenerList::removeAllActionListeners() { const ScopedLock sl (actionListenerLock_); actionListeners_.clear(); } void ActionListenerList::sendActionMessage (const String& message) const { const ScopedLock sl (actionListenerLock_); for (int i = actionListeners_.size(); --i >= 0;) postMessage (new ActionMessage (message, static_cast (actionListeners_.getUnchecked(i)))); } void ActionListenerList::handleMessage (const Message& message) { const ActionMessage& am = (const ActionMessage&) message; if (actionListeners_.contains (am.pointerParameter)) static_cast (am.pointerParameter)->actionListenerCallback (am.message); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ActionListenerList.cpp ***/ /*** Start of inlined file: juce_AsyncUpdater.cpp ***/ BEGIN_JUCE_NAMESPACE AsyncUpdater::AsyncUpdater() throw() : asyncMessagePending (false) { internalAsyncHandler.owner = this; } AsyncUpdater::~AsyncUpdater() { } void AsyncUpdater::triggerAsyncUpdate() { if (! asyncMessagePending) { asyncMessagePending = true; internalAsyncHandler.postMessage (new Message()); } } void AsyncUpdater::cancelPendingUpdate() throw() { asyncMessagePending = false; } void AsyncUpdater::handleUpdateNowIfNeeded() { if (asyncMessagePending) { asyncMessagePending = false; handleAsyncUpdate(); } } void AsyncUpdater::AsyncUpdaterInternal::handleMessage (const Message&) { owner->handleUpdateNowIfNeeded(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_AsyncUpdater.cpp ***/ /*** Start of inlined file: juce_ChangeBroadcaster.cpp ***/ BEGIN_JUCE_NAMESPACE ChangeBroadcaster::ChangeBroadcaster() throw() { // are you trying to create this object before or after juce has been intialised?? jassert (MessageManager::instance != 0); } ChangeBroadcaster::~ChangeBroadcaster() { // all event-based objects must be deleted BEFORE juce is shut down! jassert (MessageManager::instance != 0); } void ChangeBroadcaster::addChangeListener (ChangeListener* const listener) { changeListenerList.addChangeListener (listener); } void ChangeBroadcaster::removeChangeListener (ChangeListener* const listener) { jassert (changeListenerList.isValidMessageListener()); if (changeListenerList.isValidMessageListener()) changeListenerList.removeChangeListener (listener); } void ChangeBroadcaster::removeAllChangeListeners() { changeListenerList.removeAllChangeListeners(); } void ChangeBroadcaster::sendChangeMessage (void* objectThatHasChanged) { changeListenerList.sendChangeMessage (objectThatHasChanged); } void ChangeBroadcaster::sendSynchronousChangeMessage (void* objectThatHasChanged) { changeListenerList.sendSynchronousChangeMessage (objectThatHasChanged); } void ChangeBroadcaster::dispatchPendingMessages() { changeListenerList.dispatchPendingMessages(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ChangeBroadcaster.cpp ***/ /*** Start of inlined file: juce_ChangeListenerList.cpp ***/ BEGIN_JUCE_NAMESPACE ChangeListenerList::ChangeListenerList() : lastChangedObject (0), messagePending (false) { } ChangeListenerList::~ChangeListenerList() { } void ChangeListenerList::addChangeListener (ChangeListener* const listener) { const ScopedLock sl (lock); jassert (listener != 0); if (listener != 0) listeners.add (listener); } void ChangeListenerList::removeChangeListener (ChangeListener* const listener) { const ScopedLock sl (lock); listeners.removeValue (listener); } void ChangeListenerList::removeAllChangeListeners() { const ScopedLock sl (lock); listeners.clear(); } void ChangeListenerList::sendChangeMessage (void* const objectThatHasChanged) { const ScopedLock sl (lock); if ((! messagePending) && (listeners.size() > 0)) { lastChangedObject = objectThatHasChanged; postMessage (new Message (0, 0, 0, objectThatHasChanged)); messagePending = true; } } void ChangeListenerList::handleMessage (const Message& message) { sendSynchronousChangeMessage (message.pointerParameter); } void ChangeListenerList::sendSynchronousChangeMessage (void* const objectThatHasChanged) { const ScopedLock sl (lock); messagePending = false; for (int i = listeners.size(); --i >= 0;) { ChangeListener* const l = static_cast (listeners.getUnchecked (i)); { const ScopedUnlock tempUnlocker (lock); l->changeListenerCallback (objectThatHasChanged); } i = jmin (i, listeners.size()); } } void ChangeListenerList::dispatchPendingMessages() { if (messagePending) sendSynchronousChangeMessage (lastChangedObject); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ChangeListenerList.cpp ***/ /*** Start of inlined file: juce_InterprocessConnection.cpp ***/ BEGIN_JUCE_NAMESPACE InterprocessConnection::InterprocessConnection (const bool callbacksOnMessageThread, const uint32 magicMessageHeaderNumber) : Thread ("Juce IPC connection"), callbackConnectionState (false), useMessageThread (callbacksOnMessageThread), magicMessageHeader (magicMessageHeaderNumber), pipeReceiveMessageTimeout (-1) { } InterprocessConnection::~InterprocessConnection() { callbackConnectionState = false; disconnect(); } bool InterprocessConnection::connectToSocket (const String& hostName, const int portNumber, const int timeOutMillisecs) { disconnect(); const ScopedLock sl (pipeAndSocketLock); socket = new StreamingSocket(); if (socket->connect (hostName, portNumber, timeOutMillisecs)) { connectionMadeInt(); startThread(); return true; } else { socket = 0; return false; } } bool InterprocessConnection::connectToPipe (const String& pipeName, const int pipeReceiveMessageTimeoutMs) { disconnect(); ScopedPointer newPipe (new NamedPipe()); if (newPipe->openExisting (pipeName)) { const ScopedLock sl (pipeAndSocketLock); pipeReceiveMessageTimeout = pipeReceiveMessageTimeoutMs; initialiseWithPipe (newPipe.release()); return true; } return false; } bool InterprocessConnection::createPipe (const String& pipeName, const int pipeReceiveMessageTimeoutMs) { disconnect(); ScopedPointer newPipe (new NamedPipe()); if (newPipe->createNewPipe (pipeName)) { const ScopedLock sl (pipeAndSocketLock); pipeReceiveMessageTimeout = pipeReceiveMessageTimeoutMs; initialiseWithPipe (newPipe.release()); return true; } return false; } void InterprocessConnection::disconnect() { if (socket != 0) socket->close(); if (pipe != 0) { pipe->cancelPendingReads(); pipe->close(); } stopThread (4000); { const ScopedLock sl (pipeAndSocketLock); socket = 0; pipe = 0; } connectionLostInt(); } bool InterprocessConnection::isConnected() const { const ScopedLock sl (pipeAndSocketLock); return ((socket != 0 && socket->isConnected()) || (pipe != 0 && pipe->isOpen())) && isThreadRunning(); } const String InterprocessConnection::getConnectedHostName() const { if (pipe != 0) { return "localhost"; } else if (socket != 0) { if (! socket->isLocal()) return socket->getHostName(); return "localhost"; } return String::empty; } bool InterprocessConnection::sendMessage (const MemoryBlock& message) { uint32 messageHeader[2]; messageHeader [0] = ByteOrder::swapIfBigEndian (magicMessageHeader); messageHeader [1] = ByteOrder::swapIfBigEndian ((uint32) message.getSize()); MemoryBlock messageData (sizeof (messageHeader) + message.getSize()); messageData.copyFrom (messageHeader, 0, sizeof (messageHeader)); messageData.copyFrom (message.getData(), sizeof (messageHeader), message.getSize()); size_t bytesWritten = 0; const ScopedLock sl (pipeAndSocketLock); if (socket != 0) { bytesWritten = socket->write (messageData.getData(), (int) messageData.getSize()); } else if (pipe != 0) { bytesWritten = pipe->write (messageData.getData(), (int) messageData.getSize()); } if (bytesWritten < 0) { // error.. return false; } return (bytesWritten == messageData.getSize()); } void InterprocessConnection::initialiseWithSocket (StreamingSocket* const socket_) { jassert (socket == 0); socket = socket_; connectionMadeInt(); startThread(); } void InterprocessConnection::initialiseWithPipe (NamedPipe* const pipe_) { jassert (pipe == 0); pipe = pipe_; connectionMadeInt(); startThread(); } const int messageMagicNumber = 0xb734128b; void InterprocessConnection::handleMessage (const Message& message) { if (message.intParameter1 == messageMagicNumber) { switch (message.intParameter2) { case 0: { ScopedPointer data (static_cast (message.pointerParameter)); messageReceived (*data); break; } case 1: connectionMade(); break; case 2: connectionLost(); break; } } } void InterprocessConnection::connectionMadeInt() { if (! callbackConnectionState) { callbackConnectionState = true; if (useMessageThread) postMessage (new Message (messageMagicNumber, 1, 0, 0)); else connectionMade(); } } void InterprocessConnection::connectionLostInt() { if (callbackConnectionState) { callbackConnectionState = false; if (useMessageThread) postMessage (new Message (messageMagicNumber, 2, 0, 0)); else connectionLost(); } } void InterprocessConnection::deliverDataInt (const MemoryBlock& data) { jassert (callbackConnectionState); if (useMessageThread) postMessage (new Message (messageMagicNumber, 0, 0, new MemoryBlock (data))); else messageReceived (data); } bool InterprocessConnection::readNextMessageInt() { const int maximumMessageSize = 1024 * 1024 * 10; // sanity check uint32 messageHeader[2]; const int bytes = (socket != 0) ? socket->read (messageHeader, sizeof (messageHeader), true) : pipe->read (messageHeader, sizeof (messageHeader), pipeReceiveMessageTimeout); if (bytes == sizeof (messageHeader) && ByteOrder::swapIfBigEndian (messageHeader[0]) == magicMessageHeader) { int bytesInMessage = (int) ByteOrder::swapIfBigEndian (messageHeader[1]); if (bytesInMessage > 0 && bytesInMessage < maximumMessageSize) { MemoryBlock messageData (bytesInMessage, true); int bytesRead = 0; while (bytesInMessage > 0) { if (threadShouldExit()) return false; const int numThisTime = jmin (bytesInMessage, 65536); const int bytesIn = (socket != 0) ? socket->read (static_cast (messageData.getData()) + bytesRead, numThisTime, true) : pipe->read (static_cast (messageData.getData()) + bytesRead, numThisTime, pipeReceiveMessageTimeout); if (bytesIn <= 0) break; bytesRead += bytesIn; bytesInMessage -= bytesIn; } if (bytesRead >= 0) deliverDataInt (messageData); } } else if (bytes < 0) { { const ScopedLock sl (pipeAndSocketLock); socket = 0; } connectionLostInt(); return false; } return true; } void InterprocessConnection::run() { while (! threadShouldExit()) { if (socket != 0) { const int ready = socket->waitUntilReady (true, 0); if (ready < 0) { { const ScopedLock sl (pipeAndSocketLock); socket = 0; } connectionLostInt(); break; } else if (ready > 0) { if (! readNextMessageInt()) break; } else { Thread::sleep (2); } } else if (pipe != 0) { if (! pipe->isOpen()) { { const ScopedLock sl (pipeAndSocketLock); pipe = 0; } connectionLostInt(); break; } else { if (! readNextMessageInt()) break; } } else { break; } } } END_JUCE_NAMESPACE /*** End of inlined file: juce_InterprocessConnection.cpp ***/ /*** Start of inlined file: juce_InterprocessConnectionServer.cpp ***/ BEGIN_JUCE_NAMESPACE InterprocessConnectionServer::InterprocessConnectionServer() : Thread ("Juce IPC server") { } InterprocessConnectionServer::~InterprocessConnectionServer() { stop(); } bool InterprocessConnectionServer::beginWaitingForSocket (const int portNumber) { stop(); socket = new StreamingSocket(); if (socket->createListener (portNumber)) { startThread(); return true; } socket = 0; return false; } void InterprocessConnectionServer::stop() { signalThreadShouldExit(); if (socket != 0) socket->close(); stopThread (4000); socket = 0; } void InterprocessConnectionServer::run() { while ((! threadShouldExit()) && socket != 0) { ScopedPointer clientSocket (socket->waitForNextConnection()); if (clientSocket != 0) { InterprocessConnection* newConnection = createConnectionObject(); if (newConnection != 0) newConnection->initialiseWithSocket (clientSocket.release()); } } } END_JUCE_NAMESPACE /*** End of inlined file: juce_InterprocessConnectionServer.cpp ***/ /*** Start of inlined file: juce_Message.cpp ***/ BEGIN_JUCE_NAMESPACE Message::Message() throw() : intParameter1 (0), intParameter2 (0), intParameter3 (0), pointerParameter (0) { } Message::Message (const int intParameter1_, const int intParameter2_, const int intParameter3_, void* const pointerParameter_) throw() : intParameter1 (intParameter1_), intParameter2 (intParameter2_), intParameter3 (intParameter3_), pointerParameter (pointerParameter_) { } Message::~Message() throw() { } END_JUCE_NAMESPACE /*** End of inlined file: juce_Message.cpp ***/ /*** Start of inlined file: juce_MessageListener.cpp ***/ BEGIN_JUCE_NAMESPACE MessageListener::MessageListener() throw() { // are you trying to create a messagelistener before or after juce has been intialised?? jassert (MessageManager::instance != 0); if (MessageManager::instance != 0) MessageManager::instance->messageListeners.add (this); } MessageListener::~MessageListener() { if (MessageManager::instance != 0) MessageManager::instance->messageListeners.removeValue (this); } void MessageListener::postMessage (Message* const message) const throw() { message->messageRecipient = const_cast (this); if (MessageManager::instance == 0) MessageManager::getInstance(); MessageManager::instance->postMessageToQueue (message); } bool MessageListener::isValidMessageListener() const throw() { return (MessageManager::instance != 0) && MessageManager::instance->messageListeners.contains (this); } END_JUCE_NAMESPACE /*** End of inlined file: juce_MessageListener.cpp ***/ /*** Start of inlined file: juce_MessageManager.cpp ***/ BEGIN_JUCE_NAMESPACE // platform-specific functions.. bool juce_dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages); bool juce_postMessageToSystemQueue (Message* message); MessageManager* MessageManager::instance = 0; static const int quitMessageId = 0xfffff321; MessageManager::MessageManager() throw() : quitMessagePosted (false), quitMessageReceived (false), threadWithLock (0) { messageThreadId = Thread::getCurrentThreadId(); } MessageManager::~MessageManager() throw() { broadcastListeners = 0; doPlatformSpecificShutdown(); // If you hit this assertion, then you've probably leaked a Component or some other // kind of MessageListener object... jassert (messageListeners.size() == 0); jassert (instance == this); instance = 0; // do this last in case this instance is still needed by doPlatformSpecificShutdown() } MessageManager* MessageManager::getInstance() throw() { if (instance == 0) { instance = new MessageManager(); doPlatformSpecificInitialisation(); } return instance; } void MessageManager::postMessageToQueue (Message* const message) { if (quitMessagePosted || ! juce_postMessageToSystemQueue (message)) delete message; } CallbackMessage::CallbackMessage() throw() {} CallbackMessage::~CallbackMessage() throw() {} void CallbackMessage::post() { if (MessageManager::instance != 0) MessageManager::instance->postCallbackMessage (this); } void MessageManager::postCallbackMessage (Message* const message) { message->messageRecipient = 0; postMessageToQueue (message); } // not for public use.. void MessageManager::deliverMessage (Message* const message) { const ScopedPointer messageDeleter (message); MessageListener* const recipient = message->messageRecipient; JUCE_TRY { if (messageListeners.contains (recipient)) { recipient->handleMessage (*message); } else if (recipient == 0) { if (message->intParameter1 == quitMessageId) { quitMessageReceived = true; } else { CallbackMessage* const cm = dynamic_cast (message); if (cm != 0) cm->messageCallback(); } } } JUCE_CATCH_EXCEPTION } #if ! (JUCE_MAC || JUCE_IOS) void MessageManager::runDispatchLoop() { jassert (isThisTheMessageThread()); // must only be called by the message thread runDispatchLoopUntil (-1); } void MessageManager::stopDispatchLoop() { Message* const m = new Message (quitMessageId, 0, 0, 0); m->messageRecipient = 0; postMessageToQueue (m); quitMessagePosted = true; } bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) { jassert (isThisTheMessageThread()); // must only be called by the message thread const int64 endTime = Time::currentTimeMillis() + millisecondsToRunFor; while ((millisecondsToRunFor < 0 || endTime > Time::currentTimeMillis()) && ! quitMessageReceived) { JUCE_TRY { if (! juce_dispatchNextMessageOnSystemQueue (millisecondsToRunFor >= 0)) { const int msToWait = (int) (endTime - Time::currentTimeMillis()); if (msToWait > 0) Thread::sleep (jmin (5, msToWait)); } } JUCE_CATCH_EXCEPTION } return ! quitMessageReceived; } #endif void MessageManager::deliverBroadcastMessage (const String& value) { if (broadcastListeners != 0) broadcastListeners->sendActionMessage (value); } void MessageManager::registerBroadcastListener (ActionListener* const listener) { if (broadcastListeners == 0) broadcastListeners = new ActionListenerList(); broadcastListeners->addActionListener (listener); } void MessageManager::deregisterBroadcastListener (ActionListener* const listener) { if (broadcastListeners != 0) broadcastListeners->removeActionListener (listener); } bool MessageManager::isThisTheMessageThread() const throw() { return Thread::getCurrentThreadId() == messageThreadId; } void MessageManager::setCurrentThreadAsMessageThread() { if (messageThreadId != Thread::getCurrentThreadId()) { messageThreadId = Thread::getCurrentThreadId(); // This is needed on windows to make sure the message window is created by this thread doPlatformSpecificShutdown(); doPlatformSpecificInitialisation(); } } bool MessageManager::currentThreadHasLockedMessageManager() const throw() { const Thread::ThreadID thisThread = Thread::getCurrentThreadId(); return thisThread == messageThreadId || thisThread == threadWithLock; } /* The only safe way to lock the message thread while another thread does some work is by posting a special message, whose purpose is to tie up the event loop until the other thread has finished its business. Any other approach can get horribly deadlocked if the OS uses its own hidden locks which get locked before making an event callback, because if the same OS lock gets indirectly accessed from another thread inside a MM lock, you're screwed. (this is exactly what happens in Cocoa). */ class MessageManagerLock::SharedEvents : public ReferenceCountedObject { public: SharedEvents() {} ~SharedEvents() {} /* This class just holds a couple of events to communicate between the BlockingMessage and the MessageManagerLock. Because both of these objects may be deleted at any time, this shared data must be kept in a separate, ref-counted container. */ WaitableEvent lockedEvent, releaseEvent; private: SharedEvents (const SharedEvents&); SharedEvents& operator= (const SharedEvents&); }; class MessageManagerLock::BlockingMessage : public CallbackMessage { public: BlockingMessage (MessageManagerLock::SharedEvents* const events_) : events (events_) {} ~BlockingMessage() throw() {} void messageCallback() { events->lockedEvent.signal(); events->releaseEvent.wait(); } juce_UseDebuggingNewOperator private: ReferenceCountedObjectPtr events; BlockingMessage (const BlockingMessage&); BlockingMessage& operator= (const BlockingMessage&); }; MessageManagerLock::MessageManagerLock (Thread* const threadToCheck) : sharedEvents (0), locked (false) { init (threadToCheck, 0); } MessageManagerLock::MessageManagerLock (ThreadPoolJob* const jobToCheckForExitSignal) : sharedEvents (0), locked (false) { init (0, jobToCheckForExitSignal); } void MessageManagerLock::init (Thread* const threadToCheck, ThreadPoolJob* const job) { if (MessageManager::instance != 0) { if (MessageManager::instance->currentThreadHasLockedMessageManager()) { locked = true; // either we're on the message thread, or this is a re-entrant call. } else { if (threadToCheck == 0 && job == 0) { MessageManager::instance->lockingLock.enter(); } else { while (! MessageManager::instance->lockingLock.tryEnter()) { if ((threadToCheck != 0 && threadToCheck->threadShouldExit()) || (job != 0 && job->shouldExit())) return; Thread::sleep (1); } } sharedEvents = new SharedEvents(); sharedEvents->incReferenceCount(); (new BlockingMessage (sharedEvents))->post(); while (! sharedEvents->lockedEvent.wait (50)) { if ((threadToCheck != 0 && threadToCheck->threadShouldExit()) || (job != 0 && job->shouldExit())) { sharedEvents->releaseEvent.signal(); sharedEvents->decReferenceCount(); sharedEvents = 0; MessageManager::instance->lockingLock.exit(); return; } } jassert (MessageManager::instance->threadWithLock == 0); MessageManager::instance->threadWithLock = Thread::getCurrentThreadId(); locked = true; } } } MessageManagerLock::~MessageManagerLock() throw() { if (sharedEvents != 0) { jassert (MessageManager::instance == 0 || MessageManager::instance->currentThreadHasLockedMessageManager()); sharedEvents->releaseEvent.signal(); sharedEvents->decReferenceCount(); if (MessageManager::instance != 0) { MessageManager::instance->threadWithLock = 0; MessageManager::instance->lockingLock.exit(); } } } END_JUCE_NAMESPACE /*** End of inlined file: juce_MessageManager.cpp ***/ /*** Start of inlined file: juce_MultiTimer.cpp ***/ BEGIN_JUCE_NAMESPACE class MultiTimer::MultiTimerCallback : public Timer { public: MultiTimerCallback (const int timerId_, MultiTimer& owner_) : timerId (timerId_), owner (owner_) { } ~MultiTimerCallback() { } void timerCallback() { owner.timerCallback (timerId); } const int timerId; private: MultiTimer& owner; }; MultiTimer::MultiTimer() throw() { } MultiTimer::MultiTimer (const MultiTimer&) throw() { } MultiTimer::~MultiTimer() { const ScopedLock sl (timerListLock); timers.clear(); } void MultiTimer::startTimer (const int timerId, const int intervalInMilliseconds) throw() { const ScopedLock sl (timerListLock); for (int i = timers.size(); --i >= 0;) { MultiTimerCallback* const t = timers.getUnchecked(i); if (t->timerId == timerId) { t->startTimer (intervalInMilliseconds); return; } } MultiTimerCallback* const newTimer = new MultiTimerCallback (timerId, *this); timers.add (newTimer); newTimer->startTimer (intervalInMilliseconds); } void MultiTimer::stopTimer (const int timerId) throw() { const ScopedLock sl (timerListLock); for (int i = timers.size(); --i >= 0;) { MultiTimerCallback* const t = timers.getUnchecked(i); if (t->timerId == timerId) t->stopTimer(); } } bool MultiTimer::isTimerRunning (const int timerId) const throw() { const ScopedLock sl (timerListLock); for (int i = timers.size(); --i >= 0;) { const MultiTimerCallback* const t = timers.getUnchecked(i); if (t->timerId == timerId) return t->isTimerRunning(); } return false; } int MultiTimer::getTimerInterval (const int timerId) const throw() { const ScopedLock sl (timerListLock); for (int i = timers.size(); --i >= 0;) { const MultiTimerCallback* const t = timers.getUnchecked(i); if (t->timerId == timerId) return t->getTimerInterval(); } return 0; } END_JUCE_NAMESPACE /*** End of inlined file: juce_MultiTimer.cpp ***/ /*** Start of inlined file: juce_Timer.cpp ***/ BEGIN_JUCE_NAMESPACE class InternalTimerThread : private Thread, private MessageListener, private DeletedAtShutdown, private AsyncUpdater { public: InternalTimerThread() : Thread ("Juce Timer"), firstTimer (0), callbackNeeded (0) { triggerAsyncUpdate(); } ~InternalTimerThread() throw() { stopThread (4000); jassert (instance == this || instance == 0); if (instance == this) instance = 0; } void run() { uint32 lastTime = Time::getMillisecondCounter(); while (! threadShouldExit()) { const uint32 now = Time::getMillisecondCounter(); if (now <= lastTime) { wait (2); continue; } const int elapsed = now - lastTime; lastTime = now; int timeUntilFirstTimer = 1000; { const ScopedLock sl (lock); decrementAllCounters (elapsed); if (firstTimer != 0) timeUntilFirstTimer = firstTimer->countdownMs; } if (timeUntilFirstTimer <= 0) { /* If we managed to set the atomic boolean to true then send a message, this is needed as a memory barrier so the message won't be sent before callbackNeeded is set to true, but if it fails it means the message-thread changed the value from under us so at least some processing is happenening and we can just loop around and try again */ if (callbackNeeded.compareAndSetBool (1, 0)) { postMessage (new Message()); /* Sometimes our message can get discarded by the OS (e.g. when running as an RTAS when the app has a modal loop), so this is how long to wait before assuming the message has been lost and trying again. */ const uint32 messageDeliveryTimeout = now + 2000; while (callbackNeeded.get() != 0) { wait (4); if (threadShouldExit()) return; if (Time::getMillisecondCounter() > messageDeliveryTimeout) break; } } } else { // don't wait for too long because running this loop also helps keep the // Time::getApproximateMillisecondTimer value stay up-to-date wait (jlimit (1, 50, timeUntilFirstTimer)); } } } void callTimers() { const ScopedLock sl (lock); while (firstTimer != 0 && firstTimer->countdownMs <= 0) { Timer* const t = firstTimer; t->countdownMs = t->periodMs; removeTimer (t); addTimer (t); const ScopedUnlock ul (lock); JUCE_TRY { t->timerCallback(); } JUCE_CATCH_EXCEPTION } /* This is needed as a memory barrier to make sure all processing of current timers is done before the boolean is set. This set should never fail since if it was false in the first place, we wouldn't get a message (so it can't be changed from false to true from under us), and if we get a message then the value is true and the other thread can only set it to true again and we will get another callback to set it to false. */ callbackNeeded.set (0); } void handleMessage (const Message&) { callTimers(); } void callTimersSynchronously() { if (! isThreadRunning()) { // (This is relied on by some plugins in cases where the MM has // had to restart and the async callback never started) cancelPendingUpdate(); triggerAsyncUpdate(); } callTimers(); } static void callAnyTimersSynchronously() { if (InternalTimerThread::instance != 0) InternalTimerThread::instance->callTimersSynchronously(); } static inline void add (Timer* const tim) throw() { if (instance == 0) instance = new InternalTimerThread(); const ScopedLock sl (instance->lock); instance->addTimer (tim); } static inline void remove (Timer* const tim) throw() { if (instance != 0) { const ScopedLock sl (instance->lock); instance->removeTimer (tim); } } static inline void resetCounter (Timer* const tim, const int newCounter) throw() { if (instance != 0) { tim->countdownMs = newCounter; tim->periodMs = newCounter; if ((tim->next != 0 && tim->next->countdownMs < tim->countdownMs) || (tim->previous != 0 && tim->previous->countdownMs > tim->countdownMs)) { const ScopedLock sl (instance->lock); instance->removeTimer (tim); instance->addTimer (tim); } } } private: friend class Timer; static InternalTimerThread* instance; static CriticalSection lock; Timer* volatile firstTimer; Atomic callbackNeeded; void addTimer (Timer* const t) throw() { #if JUCE_DEBUG Timer* tt = firstTimer; while (tt != 0) { // trying to add a timer that's already here - shouldn't get to this point, // so if you get this assertion, let me know! jassert (tt != t); tt = tt->next; } jassert (t->previous == 0 && t->next == 0); #endif Timer* i = firstTimer; if (i == 0 || i->countdownMs > t->countdownMs) { t->next = firstTimer; firstTimer = t; } else { while (i->next != 0 && i->next->countdownMs <= t->countdownMs) i = i->next; jassert (i != 0); t->next = i->next; t->previous = i; i->next = t; } if (t->next != 0) t->next->previous = t; jassert ((t->next == 0 || t->next->countdownMs >= t->countdownMs) && (t->previous == 0 || t->previous->countdownMs <= t->countdownMs)); notify(); } void removeTimer (Timer* const t) throw() { #if JUCE_DEBUG Timer* tt = firstTimer; bool found = false; while (tt != 0) { if (tt == t) { found = true; break; } tt = tt->next; } // trying to remove a timer that's not here - shouldn't get to this point, // so if you get this assertion, let me know! jassert (found); #endif if (t->previous != 0) { jassert (firstTimer != t); t->previous->next = t->next; } else { jassert (firstTimer == t); firstTimer = t->next; } if (t->next != 0) t->next->previous = t->previous; t->next = 0; t->previous = 0; } void decrementAllCounters (const int numMillisecs) const { Timer* t = firstTimer; while (t != 0) { t->countdownMs -= numMillisecs; t = t->next; } } void handleAsyncUpdate() { startThread (7); } InternalTimerThread (const InternalTimerThread&); InternalTimerThread& operator= (const InternalTimerThread&); }; InternalTimerThread* InternalTimerThread::instance = 0; CriticalSection InternalTimerThread::lock; void juce_callAnyTimersSynchronously() { InternalTimerThread::callAnyTimersSynchronously(); } #if JUCE_DEBUG static SortedSet activeTimers; #endif Timer::Timer() throw() : countdownMs (0), periodMs (0), previous (0), next (0) { #if JUCE_DEBUG activeTimers.add (this); #endif } Timer::Timer (const Timer&) throw() : countdownMs (0), periodMs (0), previous (0), next (0) { #if JUCE_DEBUG activeTimers.add (this); #endif } Timer::~Timer() { stopTimer(); #if JUCE_DEBUG activeTimers.removeValue (this); #endif } void Timer::startTimer (const int interval) throw() { const ScopedLock sl (InternalTimerThread::lock); #if JUCE_DEBUG // this isn't a valid object! Your timer might be a dangling pointer or something.. jassert (activeTimers.contains (this)); #endif if (periodMs == 0) { countdownMs = interval; periodMs = jmax (1, interval); InternalTimerThread::add (this); } else { InternalTimerThread::resetCounter (this, interval); } } void Timer::stopTimer() throw() { const ScopedLock sl (InternalTimerThread::lock); #if JUCE_DEBUG // this isn't a valid object! Your timer might be a dangling pointer or something.. jassert (activeTimers.contains (this)); #endif if (periodMs > 0) { InternalTimerThread::remove (this); periodMs = 0; } } END_JUCE_NAMESPACE /*** End of inlined file: juce_Timer.cpp ***/ #endif #if JUCE_BUILD_GUI /*** Start of inlined file: juce_Component.cpp ***/ BEGIN_JUCE_NAMESPACE #define checkMessageManagerIsLocked jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); enum ComponentMessageNumbers { customCommandMessage = 0x7fff0001, exitModalStateMessage = 0x7fff0002 }; static uint32 nextComponentUID = 0; Component* Component::currentlyFocusedComponent = 0; Component::Component() : parentComponent_ (0), componentUID (++nextComponentUID), numDeepMouseListeners (0), lookAndFeel_ (0), effect_ (0), bufferedImage_ (0), mouseListeners_ (0), keyListeners_ (0), componentFlags_ (0) { } Component::Component (const String& name) : componentName_ (name), parentComponent_ (0), componentUID (++nextComponentUID), numDeepMouseListeners (0), lookAndFeel_ (0), effect_ (0), bufferedImage_ (0), mouseListeners_ (0), keyListeners_ (0), componentFlags_ (0) { } Component::~Component() { componentListeners.call (&ComponentListener::componentBeingDeleted, *this); if (parentComponent_ != 0) { parentComponent_->removeChildComponent (this); } else if ((currentlyFocusedComponent == this) || isParentOf (currentlyFocusedComponent)) { giveAwayFocus(); } if (flags.hasHeavyweightPeerFlag) removeFromDesktop(); for (int i = childComponentList_.size(); --i >= 0;) childComponentList_.getUnchecked(i)->parentComponent_ = 0; delete mouseListeners_; delete keyListeners_; } void Component::setName (const String& name) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. checkMessageManagerIsLocked if (componentName_ != name) { componentName_ = name; if (flags.hasHeavyweightPeerFlag) { ComponentPeer* const peer = getPeer(); jassert (peer != 0); if (peer != 0) peer->setTitle (name); } BailOutChecker checker (this); componentListeners.callChecked (checker, &ComponentListener::componentNameChanged, *this); } } void Component::setVisible (bool shouldBeVisible) { if (flags.visibleFlag != shouldBeVisible) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. checkMessageManagerIsLocked SafePointer safePointer (this); flags.visibleFlag = shouldBeVisible; internalRepaint (0, 0, getWidth(), getHeight()); sendFakeMouseMove(); if (! shouldBeVisible) { if (currentlyFocusedComponent == this || isParentOf (currentlyFocusedComponent)) { if (parentComponent_ != 0) parentComponent_->grabKeyboardFocus(); else giveAwayFocus(); } } if (safePointer != 0) { sendVisibilityChangeMessage(); if (safePointer != 0 && flags.hasHeavyweightPeerFlag) { ComponentPeer* const peer = getPeer(); jassert (peer != 0); if (peer != 0) { peer->setVisible (shouldBeVisible); internalHierarchyChanged(); } } } } } void Component::visibilityChanged() { } void Component::sendVisibilityChangeMessage() { BailOutChecker checker (this); visibilityChanged(); if (! checker.shouldBailOut()) componentListeners.callChecked (checker, &ComponentListener::componentVisibilityChanged, *this); } bool Component::isShowing() const { if (flags.visibleFlag) { if (parentComponent_ != 0) { return parentComponent_->isShowing(); } else { const ComponentPeer* const peer = getPeer(); return peer != 0 && ! peer->isMinimised(); } } return false; } class FadeOutProxyComponent : public Component, public Timer { public: FadeOutProxyComponent (Component* comp, const int fadeLengthMs, const int deltaXToMove, const int deltaYToMove, const float scaleFactorAtEnd) : lastTime (0), alpha (1.0f), scale (1.0f) { image = comp->createComponentSnapshot (comp->getLocalBounds()); setBounds (comp->getBounds()); comp->getParentComponent()->addAndMakeVisible (this); toBehind (comp); alphaChangePerMs = -1.0f / (float)fadeLengthMs; centreX = comp->getX() + comp->getWidth() * 0.5f; xChangePerMs = deltaXToMove / (float)fadeLengthMs; centreY = comp->getY() + comp->getHeight() * 0.5f; yChangePerMs = deltaYToMove / (float)fadeLengthMs; scaleChangePerMs = (scaleFactorAtEnd - 1.0f) / (float)fadeLengthMs; setInterceptsMouseClicks (false, false); // 30 fps is enough for a fade, but we need a higher rate if it's moving as well.. startTimer (1000 / ((deltaXToMove == 0 && deltaYToMove == 0) ? 30 : 50)); } ~FadeOutProxyComponent() { } void paint (Graphics& g) { g.setOpacity (alpha); g.drawImage (image, 0, 0, getWidth(), getHeight(), 0, 0, image.getWidth(), image.getHeight()); } void timerCallback() { const uint32 now = Time::getMillisecondCounter(); if (lastTime == 0) lastTime = now; const int msPassed = (now > lastTime) ? now - lastTime : 0; lastTime = now; alpha += alphaChangePerMs * msPassed; if (alpha > 0) { if (xChangePerMs != 0.0f || yChangePerMs != 0.0f || scaleChangePerMs != 0.0f) { centreX += xChangePerMs * msPassed; centreY += yChangePerMs * msPassed; scale += scaleChangePerMs * msPassed; const int w = roundToInt (image.getWidth() * scale); const int h = roundToInt (image.getHeight() * scale); setBounds (roundToInt (centreX) - w / 2, roundToInt (centreY) - h / 2, w, h); } repaint(); } else { delete this; } } juce_UseDebuggingNewOperator private: Image image; uint32 lastTime; float alpha, alphaChangePerMs; float centreX, xChangePerMs; float centreY, yChangePerMs; float scale, scaleChangePerMs; FadeOutProxyComponent (const FadeOutProxyComponent&); FadeOutProxyComponent& operator= (const FadeOutProxyComponent&); }; void Component::fadeOutComponent (const int millisecondsToFade, const int deltaXToMove, const int deltaYToMove, const float scaleFactorAtEnd) { //xxx won't work for comps without parents if (isShowing() && millisecondsToFade > 0) new FadeOutProxyComponent (this, millisecondsToFade, deltaXToMove, deltaYToMove, scaleFactorAtEnd); setVisible (false); } bool Component::isValidComponent() const { return (this != 0) && isValidMessageListener(); } void* Component::getWindowHandle() const { const ComponentPeer* const peer = getPeer(); if (peer != 0) return peer->getNativeHandle(); return 0; } void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. checkMessageManagerIsLocked if (isOpaque()) styleWanted &= ~ComponentPeer::windowIsSemiTransparent; else styleWanted |= ComponentPeer::windowIsSemiTransparent; int currentStyleFlags = 0; // don't use getPeer(), so that we only get the peer that's specifically // for this comp, and not for one of its parents. ComponentPeer* peer = ComponentPeer::getPeerFor (this); if (peer != 0) currentStyleFlags = peer->getStyleFlags(); if (styleWanted != currentStyleFlags || ! flags.hasHeavyweightPeerFlag) { SafePointer safePointer (this); #if JUCE_LINUX // it's wise to give the component a non-zero size before // putting it on the desktop, as X windows get confused by this, and // a (1, 1) minimum size is enforced here. setSize (jmax (1, getWidth()), jmax (1, getHeight())); #endif const Point topLeft (relativePositionToGlobal (Point (0, 0))); bool wasFullscreen = false; bool wasMinimised = false; ComponentBoundsConstrainer* currentConstainer = 0; Rectangle oldNonFullScreenBounds; if (peer != 0) { wasFullscreen = peer->isFullScreen(); wasMinimised = peer->isMinimised(); currentConstainer = peer->getConstrainer(); oldNonFullScreenBounds = peer->getNonFullScreenBounds(); removeFromDesktop(); setTopLeftPosition (topLeft.getX(), topLeft.getY()); } if (parentComponent_ != 0) parentComponent_->removeChildComponent (this); if (safePointer != 0) { flags.hasHeavyweightPeerFlag = true; peer = createNewPeer (styleWanted, nativeWindowToAttachTo); Desktop::getInstance().addDesktopComponent (this); bounds_.setPosition (topLeft); peer->setBounds (topLeft.getX(), topLeft.getY(), getWidth(), getHeight(), false); peer->setVisible (isVisible()); if (wasFullscreen) { peer->setFullScreen (true); peer->setNonFullScreenBounds (oldNonFullScreenBounds); } if (wasMinimised) peer->setMinimised (true); if (isAlwaysOnTop()) peer->setAlwaysOnTop (true); peer->setConstrainer (currentConstainer); repaint(); } internalHierarchyChanged(); } } void Component::removeFromDesktop() { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. checkMessageManagerIsLocked if (flags.hasHeavyweightPeerFlag) { ComponentPeer* const peer = ComponentPeer::getPeerFor (this); flags.hasHeavyweightPeerFlag = false; jassert (peer != 0); delete peer; Desktop::getInstance().removeDesktopComponent (this); } } bool Component::isOnDesktop() const throw() { return flags.hasHeavyweightPeerFlag; } void Component::userTriedToCloseWindow() { /* This means that the user's trying to get rid of your window with the 'close window' system menu option (on windows) or possibly the task manager - you should really handle this and delete or hide your component in an appropriate way. If you want to ignore the event and don't want to trigger this assertion, just override this method and do nothing. */ jassertfalse; } void Component::minimisationStateChanged (bool) { } void Component::setOpaque (const bool shouldBeOpaque) { if (shouldBeOpaque != flags.opaqueFlag) { flags.opaqueFlag = shouldBeOpaque; if (flags.hasHeavyweightPeerFlag) { const ComponentPeer* const peer = ComponentPeer::getPeerFor (this); if (peer != 0) { // to make it recreate the heavyweight window addToDesktop (peer->getStyleFlags()); } } repaint(); } } bool Component::isOpaque() const throw() { return flags.opaqueFlag; } void Component::setBufferedToImage (const bool shouldBeBuffered) { if (shouldBeBuffered != flags.bufferToImageFlag) { bufferedImage_ = Image::null; flags.bufferToImageFlag = shouldBeBuffered; } } void Component::toFront (const bool setAsForeground) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. checkMessageManagerIsLocked if (flags.hasHeavyweightPeerFlag) { ComponentPeer* const peer = getPeer(); if (peer != 0) { peer->toFront (setAsForeground); if (setAsForeground && ! hasKeyboardFocus (true)) grabKeyboardFocus(); } } else if (parentComponent_ != 0) { Array& childList = parentComponent_->childComponentList_; if (childList.getLast() != this) { const int index = childList.indexOf (this); if (index >= 0) { int insertIndex = -1; if (! flags.alwaysOnTopFlag) { insertIndex = childList.size() - 1; while (insertIndex > 0 && childList.getUnchecked (insertIndex)->isAlwaysOnTop()) --insertIndex; } if (index != insertIndex) { childList.move (index, insertIndex); sendFakeMouseMove(); repaintParent(); } } } if (setAsForeground) { internalBroughtToFront(); grabKeyboardFocus(); } } } void Component::toBehind (Component* const other) { if (other != 0 && other != this) { // the two components must belong to the same parent.. jassert (parentComponent_ == other->parentComponent_); if (parentComponent_ != 0) { Array& childList = parentComponent_->childComponentList_; const int index = childList.indexOf (this); if (index >= 0 && childList [index + 1] != other) { int otherIndex = childList.indexOf (other); if (otherIndex >= 0) { if (index < otherIndex) --otherIndex; childList.move (index, otherIndex); sendFakeMouseMove(); repaintParent(); } } } else if (isOnDesktop()) { jassert (other->isOnDesktop()); if (other->isOnDesktop()) { ComponentPeer* const us = getPeer(); ComponentPeer* const them = other->getPeer(); jassert (us != 0 && them != 0); if (us != 0 && them != 0) us->toBehind (them); } } } } void Component::toBack() { Array& childList = parentComponent_->childComponentList_; if (isOnDesktop()) { jassertfalse; //xxx need to add this to native window } else if (parentComponent_ != 0 && childList.getFirst() != this) { const int index = childList.indexOf (this); if (index > 0) { int insertIndex = 0; if (flags.alwaysOnTopFlag) { while (insertIndex < childList.size() && ! childList.getUnchecked (insertIndex)->isAlwaysOnTop()) { ++insertIndex; } } if (index != insertIndex) { childList.move (index, insertIndex); sendFakeMouseMove(); repaintParent(); } } } } void Component::setAlwaysOnTop (const bool shouldStayOnTop) { if (shouldStayOnTop != flags.alwaysOnTopFlag) { flags.alwaysOnTopFlag = shouldStayOnTop; if (isOnDesktop()) { ComponentPeer* const peer = getPeer(); jassert (peer != 0); if (peer != 0) { if (! peer->setAlwaysOnTop (shouldStayOnTop)) { // some kinds of peer can't change their always-on-top status, so // for these, we'll need to create a new window const int oldFlags = peer->getStyleFlags(); removeFromDesktop(); addToDesktop (oldFlags); } } } if (shouldStayOnTop) toFront (false); internalHierarchyChanged(); } } bool Component::isAlwaysOnTop() const throw() { return flags.alwaysOnTopFlag; } int Component::proportionOfWidth (const float proportion) const throw() { return roundToInt (proportion * bounds_.getWidth()); } int Component::proportionOfHeight (const float proportion) const throw() { return roundToInt (proportion * bounds_.getHeight()); } int Component::getParentWidth() const throw() { return (parentComponent_ != 0) ? parentComponent_->getWidth() : getParentMonitorArea().getWidth(); } int Component::getParentHeight() const throw() { return (parentComponent_ != 0) ? parentComponent_->getHeight() : getParentMonitorArea().getHeight(); } int Component::getScreenX() const { return getScreenPosition().getX(); } int Component::getScreenY() const { return getScreenPosition().getY(); } const Point Component::getScreenPosition() const { return (parentComponent_ != 0) ? parentComponent_->getScreenPosition() + getPosition() : (flags.hasHeavyweightPeerFlag ? getPeer()->getScreenPosition() : getPosition()); } const Rectangle Component::getScreenBounds() const { return bounds_.withPosition (getScreenPosition()); } const Point Component::relativePositionToGlobal (const Point& relativePosition) const { const Component* c = this; Point p (relativePosition); do { if (c->flags.hasHeavyweightPeerFlag) return c->getPeer()->relativePositionToGlobal (p); p += c->getPosition(); c = c->parentComponent_; } while (c != 0); return p; } const Point Component::globalPositionToRelative (const Point& screenPosition) const { if (flags.hasHeavyweightPeerFlag) { return getPeer()->globalPositionToRelative (screenPosition); } else { if (parentComponent_ != 0) return parentComponent_->globalPositionToRelative (screenPosition) - getPosition(); return screenPosition - getPosition(); } } const Point Component::relativePositionToOtherComponent (const Component* const targetComponent, const Point& positionRelativeToThis) const { Point p (positionRelativeToThis); if (targetComponent != 0) { const Component* c = this; do { if (c == targetComponent) return p; if (c->flags.hasHeavyweightPeerFlag) { p = c->getPeer()->relativePositionToGlobal (p); break; } p += c->getPosition(); c = c->parentComponent_; } while (c != 0); p = targetComponent->globalPositionToRelative (p); } return p; } void Component::setBounds (const int x, const int y, int w, int h) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. checkMessageManagerIsLocked if (w < 0) w = 0; if (h < 0) h = 0; const bool wasResized = (getWidth() != w || getHeight() != h); const bool wasMoved = (getX() != x || getY() != y); #if JUCE_DEBUG // It's a very bad idea to try to resize a window during its paint() method! jassert (! (flags.isInsidePaintCall && wasResized && isOnDesktop())); #endif if (wasMoved || wasResized) { if (flags.visibleFlag) { // send a fake mouse move to trigger enter/exit messages if needed.. sendFakeMouseMove(); if (! flags.hasHeavyweightPeerFlag) repaintParent(); } bounds_.setBounds (x, y, w, h); if (wasResized) repaint(); else if (! flags.hasHeavyweightPeerFlag) repaintParent(); if (flags.hasHeavyweightPeerFlag) { ComponentPeer* const peer = getPeer(); if (peer != 0) { if (wasMoved && wasResized) peer->setBounds (getX(), getY(), getWidth(), getHeight(), false); else if (wasMoved) peer->setPosition (getX(), getY()); else if (wasResized) peer->setSize (getWidth(), getHeight()); } } sendMovedResizedMessages (wasMoved, wasResized); } } void Component::sendMovedResizedMessages (const bool wasMoved, const bool wasResized) { JUCE_TRY { if (wasMoved) moved(); if (wasResized) { resized(); for (int i = childComponentList_.size(); --i >= 0;) { childComponentList_.getUnchecked(i)->parentSizeChanged(); i = jmin (i, childComponentList_.size()); } } BailOutChecker checker (this); if (parentComponent_ != 0) parentComponent_->childBoundsChanged (this); if (! checker.shouldBailOut()) componentListeners.callChecked (checker, &ComponentListener::componentMovedOrResized, *this, wasMoved, wasResized); } JUCE_CATCH_EXCEPTION } void Component::setSize (const int w, const int h) { setBounds (getX(), getY(), w, h); } void Component::setTopLeftPosition (const int x, const int y) { setBounds (x, y, getWidth(), getHeight()); } void Component::setTopRightPosition (const int x, const int y) { setTopLeftPosition (x - getWidth(), y); } void Component::setBounds (const Rectangle& r) { setBounds (r.getX(), r.getY(), r.getWidth(), r.getHeight()); } void Component::setBoundsRelative (const float x, const float y, const float w, const float h) { const int pw = getParentWidth(); const int ph = getParentHeight(); setBounds (roundToInt (x * pw), roundToInt (y * ph), roundToInt (w * pw), roundToInt (h * ph)); } void Component::setCentrePosition (const int x, const int y) { setTopLeftPosition (x - getWidth() / 2, y - getHeight() / 2); } void Component::setCentreRelative (const float x, const float y) { setCentrePosition (roundToInt (getParentWidth() * x), roundToInt (getParentHeight() * y)); } void Component::centreWithSize (const int width, const int height) { const Rectangle parentArea (getParentOrMainMonitorBounds()); setBounds (parentArea.getCentreX() - width / 2, parentArea.getCentreY() - height / 2, width, height); } void Component::setBoundsInset (const BorderSize& borders) { setBounds (borders.subtractedFrom (getParentOrMainMonitorBounds())); } void Component::setBoundsToFit (int x, int y, int width, int height, const Justification& justification, const bool onlyReduceInSize) { // it's no good calling this method unless both the component and // target rectangle have a finite size. jassert (getWidth() > 0 && getHeight() > 0 && width > 0 && height > 0); if (getWidth() > 0 && getHeight() > 0 && width > 0 && height > 0) { int newW, newH; if (onlyReduceInSize && getWidth() <= width && getHeight() <= height) { newW = getWidth(); newH = getHeight(); } else { const double imageRatio = getHeight() / (double) getWidth(); const double targetRatio = height / (double) width; if (imageRatio <= targetRatio) { newW = width; newH = jmin (height, roundToInt (newW * imageRatio)); } else { newH = height; newW = jmin (width, roundToInt (newH / imageRatio)); } } if (newW > 0 && newH > 0) { int newX, newY; justification.applyToRectangle (newX, newY, newW, newH, x, y, width, height); setBounds (newX, newY, newW, newH); } } } bool Component::hitTest (int x, int y) { if (! flags.ignoresMouseClicksFlag) return true; if (flags.allowChildMouseClicksFlag) { for (int i = getNumChildComponents(); --i >= 0;) { Component* const c = getChildComponent (i); if (c->isVisible() && c->bounds_.contains (x, y) && c->hitTest (x - c->getX(), y - c->getY())) { return true; } } } return false; } void Component::setInterceptsMouseClicks (const bool allowClicks, const bool allowClicksOnChildComponents) throw() { flags.ignoresMouseClicksFlag = ! allowClicks; flags.allowChildMouseClicksFlag = allowClicksOnChildComponents; } void Component::getInterceptsMouseClicks (bool& allowsClicksOnThisComponent, bool& allowsClicksOnChildComponents) const throw() { allowsClicksOnThisComponent = ! flags.ignoresMouseClicksFlag; allowsClicksOnChildComponents = flags.allowChildMouseClicksFlag; } bool Component::contains (const int x, const int y) { if (((unsigned int) x) < (unsigned int) getWidth() && ((unsigned int) y) < (unsigned int) getHeight() && hitTest (x, y)) { if (parentComponent_ != 0) { return parentComponent_->contains (x + getX(), y + getY()); } else if (flags.hasHeavyweightPeerFlag) { const ComponentPeer* const peer = getPeer(); if (peer != 0) return peer->contains (Point (x, y), true); } } return false; } bool Component::reallyContains (int x, int y, const bool returnTrueIfWithinAChild) { if (! contains (x, y)) return false; Component* p = this; while (p->parentComponent_ != 0) { x += p->getX(); y += p->getY(); p = p->parentComponent_; } const Component* const c = p->getComponentAt (x, y); return (c == this) || (returnTrueIfWithinAChild && isParentOf (c)); } Component* Component::getComponentAt (const Point& position) { return getComponentAt (position.getX(), position.getY()); } Component* Component::getComponentAt (const int x, const int y) { if (flags.visibleFlag && ((unsigned int) x) < (unsigned int) getWidth() && ((unsigned int) y) < (unsigned int) getHeight() && hitTest (x, y)) { for (int i = childComponentList_.size(); --i >= 0;) { Component* const child = childComponentList_.getUnchecked(i); Component* const c = child->getComponentAt (x - child->getX(), y - child->getY()); if (c != 0) return c; } return this; } return 0; } void Component::addChildComponent (Component* const child, int zOrder) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. checkMessageManagerIsLocked if (child != 0 && child->parentComponent_ != this) { if (child->parentComponent_ != 0) child->parentComponent_->removeChildComponent (child); else child->removeFromDesktop(); child->parentComponent_ = this; if (child->isVisible()) child->repaintParent(); if (! child->isAlwaysOnTop()) { if (zOrder < 0 || zOrder > childComponentList_.size()) zOrder = childComponentList_.size(); while (zOrder > 0) { if (! childComponentList_.getUnchecked (zOrder - 1)->isAlwaysOnTop()) break; --zOrder; } } childComponentList_.insert (zOrder, child); child->internalHierarchyChanged(); internalChildrenChanged(); } } void Component::addAndMakeVisible (Component* const child, int zOrder) { if (child != 0) { child->setVisible (true); addChildComponent (child, zOrder); } } void Component::removeChildComponent (Component* const child) { removeChildComponent (childComponentList_.indexOf (child)); } Component* Component::removeChildComponent (const int index) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. checkMessageManagerIsLocked Component* const child = childComponentList_ [index]; if (child != 0) { sendFakeMouseMove(); child->repaintParent(); childComponentList_.remove (index); child->parentComponent_ = 0; JUCE_TRY { if ((currentlyFocusedComponent == child) || child->isParentOf (currentlyFocusedComponent)) { // get rid first to force the grabKeyboardFocus to change to us. giveAwayFocus(); grabKeyboardFocus(); } } #if JUCE_CATCH_UNHANDLED_EXCEPTIONS catch (const std::exception& e) { currentlyFocusedComponent = 0; Desktop::getInstance().triggerFocusCallback(); JUCEApplication::sendUnhandledException (&e, __FILE__, __LINE__); } catch (...) { currentlyFocusedComponent = 0; Desktop::getInstance().triggerFocusCallback(); JUCEApplication::sendUnhandledException (0, __FILE__, __LINE__); } #endif child->internalHierarchyChanged(); internalChildrenChanged(); } return child; } void Component::removeAllChildren() { while (childComponentList_.size() > 0) removeChildComponent (childComponentList_.size() - 1); } void Component::deleteAllChildren() { while (childComponentList_.size() > 0) delete (removeChildComponent (childComponentList_.size() - 1)); } int Component::getNumChildComponents() const throw() { return childComponentList_.size(); } Component* Component::getChildComponent (const int index) const throw() { return childComponentList_ [index]; } int Component::getIndexOfChildComponent (const Component* const child) const throw() { return childComponentList_.indexOf (const_cast (child)); } Component* Component::getTopLevelComponent() const throw() { const Component* comp = this; while (comp->parentComponent_ != 0) comp = comp->parentComponent_; return const_cast (comp); } bool Component::isParentOf (const Component* possibleChild) const throw() { if (! possibleChild->isValidComponent()) { jassert (possibleChild == 0); return false; } while (possibleChild != 0) { possibleChild = possibleChild->parentComponent_; if (possibleChild == this) return true; } return false; } void Component::parentHierarchyChanged() { } void Component::childrenChanged() { } void Component::internalChildrenChanged() { if (componentListeners.isEmpty()) { childrenChanged(); } else { BailOutChecker checker (this); childrenChanged(); if (! checker.shouldBailOut()) componentListeners.callChecked (checker, &ComponentListener::componentChildrenChanged, *this); } } void Component::internalHierarchyChanged() { BailOutChecker checker (this); parentHierarchyChanged(); if (checker.shouldBailOut()) return; componentListeners.callChecked (checker, &ComponentListener::componentParentHierarchyChanged, *this); if (checker.shouldBailOut()) return; for (int i = childComponentList_.size(); --i >= 0;) { childComponentList_.getUnchecked (i)->internalHierarchyChanged(); if (checker.shouldBailOut()) { // you really shouldn't delete the parent component during a callback telling you // that it's changed.. jassertfalse; return; } i = jmin (i, childComponentList_.size()); } } void* Component::runModalLoopCallback (void* userData) { return (void*) (pointer_sized_int) static_cast (userData)->runModalLoop(); } int Component::runModalLoop() { if (! MessageManager::getInstance()->isThisTheMessageThread()) { // use a callback so this can be called from non-gui threads return (int) (pointer_sized_int) MessageManager::getInstance() ->callFunctionOnMessageThread (&runModalLoopCallback, this); } if (! isCurrentlyModal()) enterModalState (true); return ModalComponentManager::getInstance()->runEventLoopForCurrentComponent(); } void Component::enterModalState (const bool takeKeyboardFocus_, ModalComponentManager::Callback* const callback) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. checkMessageManagerIsLocked // Check for an attempt to make a component modal when it already is! // This can cause nasty problems.. jassert (! flags.currentlyModalFlag); if (! isCurrentlyModal()) { ModalComponentManager::getInstance()->startModal (this, callback); flags.currentlyModalFlag = true; setVisible (true); if (takeKeyboardFocus_) grabKeyboardFocus(); } } void Component::exitModalState (const int returnValue) { if (isCurrentlyModal()) { if (MessageManager::getInstance()->isThisTheMessageThread()) { ModalComponentManager::getInstance()->endModal (this, returnValue); flags.currentlyModalFlag = false; bringModalComponentToFront(); } else { postMessage (new Message (exitModalStateMessage, returnValue, 0, 0)); } } } bool Component::isCurrentlyModal() const throw() { return flags.currentlyModalFlag && getCurrentlyModalComponent() == this; } bool Component::isCurrentlyBlockedByAnotherModalComponent() const { Component* const mc = getCurrentlyModalComponent(); return mc != 0 && mc != this && (! mc->isParentOf (this)) && ! mc->canModalEventBeSentToComponent (this); } int JUCE_CALLTYPE Component::getNumCurrentlyModalComponents() throw() { return ModalComponentManager::getInstance()->getNumModalComponents(); } Component* JUCE_CALLTYPE Component::getCurrentlyModalComponent (int index) throw() { return ModalComponentManager::getInstance()->getModalComponent (index); } void Component::bringModalComponentToFront() { ComponentPeer* lastOne = 0; for (int i = 0; i < getNumCurrentlyModalComponents(); ++i) { Component* const c = getCurrentlyModalComponent (i); if (c == 0) break; ComponentPeer* peer = c->getPeer(); if (peer != 0 && peer != lastOne) { if (lastOne == 0) { peer->toFront (true); peer->grabFocus(); } else peer->toBehind (lastOne); lastOne = peer; } } } void Component::setBroughtToFrontOnMouseClick (const bool shouldBeBroughtToFront) throw() { flags.bringToFrontOnClickFlag = shouldBeBroughtToFront; } bool Component::isBroughtToFrontOnMouseClick() const throw() { return flags.bringToFrontOnClickFlag; } void Component::setMouseCursor (const MouseCursor& cursor) { if (cursor_ != cursor) { cursor_ = cursor; if (flags.visibleFlag) updateMouseCursor(); } } const MouseCursor Component::getMouseCursor() { return cursor_; } void Component::updateMouseCursor() const { sendFakeMouseMove(); } void Component::setRepaintsOnMouseActivity (const bool shouldRepaint) throw() { flags.repaintOnMouseActivityFlag = shouldRepaint; } void Component::repaintParent() { if (flags.visibleFlag) internalRepaint (0, 0, getWidth(), getHeight()); } void Component::repaint() { repaint (0, 0, getWidth(), getHeight()); } void Component::repaint (const int x, const int y, const int w, const int h) { bufferedImage_ = Image::null; if (flags.visibleFlag) internalRepaint (x, y, w, h); } void Component::repaint (const Rectangle& area) { repaint (area.getX(), area.getY(), area.getWidth(), area.getHeight()); } void Component::internalRepaint (int x, int y, int w, int h) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. checkMessageManagerIsLocked if (x < 0) { w += x; x = 0; } if (x + w > getWidth()) w = getWidth() - x; if (w > 0) { if (y < 0) { h += y; y = 0; } if (y + h > getHeight()) h = getHeight() - y; if (h > 0) { if (parentComponent_ != 0) { x += getX(); y += getY(); if (parentComponent_->flags.visibleFlag) parentComponent_->internalRepaint (x, y, w, h); } else if (flags.hasHeavyweightPeerFlag) { ComponentPeer* const peer = getPeer(); if (peer != 0) peer->repaint (Rectangle (x, y, w, h)); } } } } void Component::renderComponent (Graphics& g) { const Rectangle clipBounds (g.getClipBounds()); g.saveState(); clipObscuredRegions (g, clipBounds, 0, 0); if (! g.isClipEmpty()) { if (flags.bufferToImageFlag) { if (bufferedImage_.isNull()) { bufferedImage_ = Image (flags.opaqueFlag ? Image::RGB : Image::ARGB, getWidth(), getHeight(), ! flags.opaqueFlag, Image::NativeImage); Graphics imG (bufferedImage_); paint (imG); } g.setColour (Colours::black); g.drawImageAt (bufferedImage_, 0, 0); } else { paint (g); } } g.restoreState(); for (int i = 0; i < childComponentList_.size(); ++i) { Component* const child = childComponentList_.getUnchecked (i); if (child->isVisible() && clipBounds.intersects (child->getBounds())) { g.saveState(); if (g.reduceClipRegion (child->getX(), child->getY(), child->getWidth(), child->getHeight())) { for (int j = i + 1; j < childComponentList_.size(); ++j) { const Component* const sibling = childComponentList_.getUnchecked (j); if (sibling->flags.opaqueFlag && sibling->isVisible()) g.excludeClipRegion (sibling->getBounds()); } if (! g.isClipEmpty()) { g.setOrigin (child->getX(), child->getY()); child->paintEntireComponent (g); } } g.restoreState(); } } g.saveState(); paintOverChildren (g); g.restoreState(); } void Component::paintEntireComponent (Graphics& g) { jassert (! g.isClipEmpty()); #if JUCE_DEBUG flags.isInsidePaintCall = true; #endif if (effect_ != 0) { Image effectImage (flags.opaqueFlag ? Image::RGB : Image::ARGB, getWidth(), getHeight(), ! flags.opaqueFlag, Image::NativeImage); { Graphics g2 (effectImage); renderComponent (g2); } effect_->applyEffect (effectImage, g); } else { renderComponent (g); } #if JUCE_DEBUG flags.isInsidePaintCall = false; #endif } const Image Component::createComponentSnapshot (const Rectangle& areaToGrab, const bool clipImageToComponentBounds) { Rectangle r (areaToGrab); if (clipImageToComponentBounds) r = r.getIntersection (getLocalBounds()); Image componentImage (flags.opaqueFlag ? Image::RGB : Image::ARGB, jmax (1, r.getWidth()), jmax (1, r.getHeight()), true); Graphics imageContext (componentImage); imageContext.setOrigin (-r.getX(), -r.getY()); paintEntireComponent (imageContext); return componentImage; } void Component::setComponentEffect (ImageEffectFilter* const effect) { if (effect_ != effect) { effect_ = effect; repaint(); } } LookAndFeel& Component::getLookAndFeel() const throw() { const Component* c = this; do { if (c->lookAndFeel_ != 0) return *(c->lookAndFeel_); c = c->parentComponent_; } while (c != 0); return LookAndFeel::getDefaultLookAndFeel(); } void Component::setLookAndFeel (LookAndFeel* const newLookAndFeel) { if (lookAndFeel_ != newLookAndFeel) { lookAndFeel_ = newLookAndFeel; sendLookAndFeelChange(); } } void Component::lookAndFeelChanged() { } void Component::sendLookAndFeelChange() { repaint(); lookAndFeelChanged(); // (it's not a great idea to do anything that would delete this component // during the lookAndFeelChanged() callback) jassert (isValidComponent()); SafePointer safePointer (this); for (int i = childComponentList_.size(); --i >= 0;) { childComponentList_.getUnchecked (i)->sendLookAndFeelChange(); if (safePointer == 0) return; i = jmin (i, childComponentList_.size()); } } static const Identifier getColourPropertyId (const int colourId) { String s; s.preallocateStorage (18); s << "jcclr_" << String::toHexString (colourId); return s; } const Colour Component::findColour (const int colourId, const bool inheritFromParent) const { var* v = properties.getItem (getColourPropertyId (colourId)); if (v != 0) return Colour ((int) *v); if (inheritFromParent && parentComponent_ != 0) return parentComponent_->findColour (colourId, true); return getLookAndFeel().findColour (colourId); } bool Component::isColourSpecified (const int colourId) const { return properties.contains (getColourPropertyId (colourId)); } void Component::removeColour (const int colourId) { if (properties.remove (getColourPropertyId (colourId))) colourChanged(); } void Component::setColour (const int colourId, const Colour& colour) { if (properties.set (getColourPropertyId (colourId), (int) colour.getARGB())) colourChanged(); } void Component::copyAllExplicitColoursTo (Component& target) const { bool changed = false; for (int i = properties.size(); --i >= 0;) { const Identifier name (properties.getName(i)); if (name.toString().startsWith ("jcclr_")) if (target.properties.set (name, properties [name])) changed = true; } if (changed) target.colourChanged(); } void Component::colourChanged() { } const Rectangle Component::getLocalBounds() const throw() { return Rectangle (getWidth(), getHeight()); } const Rectangle Component::getParentOrMainMonitorBounds() const { return parentComponent_ != 0 ? parentComponent_->getLocalBounds() : Desktop::getInstance().getMainMonitorArea(); } const Rectangle Component::getUnclippedArea() const { int x = 0, y = 0, w = getWidth(), h = getHeight(); Component* p = parentComponent_; int px = getX(); int py = getY(); while (p != 0) { if (! Rectangle::intersectRectangles (x, y, w, h, -px, -py, p->getWidth(), p->getHeight())) return Rectangle(); px += p->getX(); py += p->getY(); p = p->parentComponent_; } return Rectangle (x, y, w, h); } void Component::clipObscuredRegions (Graphics& g, const Rectangle& clipRect, const int deltaX, const int deltaY) const { for (int i = childComponentList_.size(); --i >= 0;) { const Component* const c = childComponentList_.getUnchecked(i); if (c->isVisible()) { const Rectangle newClip (clipRect.getIntersection (c->bounds_)); if (! newClip.isEmpty()) { if (c->isOpaque()) { g.excludeClipRegion (newClip.translated (deltaX, deltaY)); } else { c->clipObscuredRegions (g, newClip.translated (-c->getX(), -c->getY()), c->getX() + deltaX, c->getY() + deltaY); } } } } } void Component::getVisibleArea (RectangleList& result, const bool includeSiblings) const { result.clear(); const Rectangle unclipped (getUnclippedArea()); if (! unclipped.isEmpty()) { result.add (unclipped); if (includeSiblings) { const Component* const c = getTopLevelComponent(); c->subtractObscuredRegions (result, c->relativePositionToOtherComponent (this, Point()), c->getLocalBounds(), this); } subtractObscuredRegions (result, Point(), unclipped, 0); result.consolidate(); } } void Component::subtractObscuredRegions (RectangleList& result, const Point& delta, const Rectangle& clipRect, const Component* const compToAvoid) const { for (int i = childComponentList_.size(); --i >= 0;) { const Component* const c = childComponentList_.getUnchecked(i); if (c != compToAvoid && c->isVisible()) { if (c->isOpaque()) { Rectangle childBounds (c->bounds_.getIntersection (clipRect)); childBounds.translate (delta.getX(), delta.getY()); result.subtract (childBounds); } else { Rectangle newClip (clipRect.getIntersection (c->bounds_)); newClip.translate (-c->getX(), -c->getY()); c->subtractObscuredRegions (result, c->getPosition() + delta, newClip, compToAvoid); } } } } void Component::mouseEnter (const MouseEvent&) { // base class does nothing } void Component::mouseExit (const MouseEvent&) { // base class does nothing } void Component::mouseDown (const MouseEvent&) { // base class does nothing } void Component::mouseUp (const MouseEvent&) { // base class does nothing } void Component::mouseDrag (const MouseEvent&) { // base class does nothing } void Component::mouseMove (const MouseEvent&) { // base class does nothing } void Component::mouseDoubleClick (const MouseEvent&) { // base class does nothing } void Component::mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY) { // the base class just passes this event up to its parent.. if (parentComponent_ != 0) parentComponent_->mouseWheelMove (e.getEventRelativeTo (parentComponent_), wheelIncrementX, wheelIncrementY); } void Component::resized() { // base class does nothing } void Component::moved() { // base class does nothing } void Component::childBoundsChanged (Component*) { // base class does nothing } void Component::parentSizeChanged() { // base class does nothing } void Component::addComponentListener (ComponentListener* const newListener) { jassert (isValidComponent()); componentListeners.add (newListener); } void Component::removeComponentListener (ComponentListener* const listenerToRemove) { jassert (isValidComponent()); componentListeners.remove (listenerToRemove); } void Component::inputAttemptWhenModal() { bringModalComponentToFront(); getLookAndFeel().playAlertSound(); } bool Component::canModalEventBeSentToComponent (const Component*) { return false; } void Component::internalModalInputAttempt() { Component* const current = getCurrentlyModalComponent(); if (current != 0) current->inputAttemptWhenModal(); } void Component::paint (Graphics&) { // all painting is done in the subclasses jassert (! isOpaque()); // if your component's opaque, you've gotta paint it! } void Component::paintOverChildren (Graphics&) { // all painting is done in the subclasses } void Component::handleMessage (const Message& message) { if (message.intParameter1 == exitModalStateMessage) { exitModalState (message.intParameter2); } else if (message.intParameter1 == customCommandMessage) { handleCommandMessage (message.intParameter2); } } void Component::postCommandMessage (const int commandId) { postMessage (new Message (customCommandMessage, commandId, 0, 0)); } void Component::handleCommandMessage (int) { // used by subclasses } void Component::addMouseListener (MouseListener* const newListener, const bool wantsEventsForAllNestedChildComponents) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. checkMessageManagerIsLocked if (mouseListeners_ == 0) mouseListeners_ = new Array(); if (! mouseListeners_->contains (newListener)) { if (wantsEventsForAllNestedChildComponents) { mouseListeners_->insert (0, newListener); ++numDeepMouseListeners; } else { mouseListeners_->add (newListener); } } } void Component::removeMouseListener (MouseListener* const listenerToRemove) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. checkMessageManagerIsLocked if (mouseListeners_ != 0) { const int index = mouseListeners_->indexOf (listenerToRemove); if (index >= 0) { if (index < numDeepMouseListeners) --numDeepMouseListeners; mouseListeners_->remove (index); } } } void Component::internalMouseEnter (MouseInputSource& source, const Point& relativePos, const Time& time) { if (isCurrentlyBlockedByAnotherModalComponent()) { // if something else is modal, always just show a normal mouse cursor source.showMouseCursor (MouseCursor::NormalCursor); return; } if (! flags.mouseInsideFlag) { flags.mouseInsideFlag = true; flags.mouseOverFlag = true; flags.draggingFlag = false; BailOutChecker checker (this); if (flags.repaintOnMouseActivityFlag) repaint(); const MouseEvent me (source, relativePos, source.getCurrentModifiers(), this, this, time, relativePos, time, 0, false); mouseEnter (me); if (checker.shouldBailOut()) return; Desktop::getInstance().resetTimer(); Desktop::getInstance().mouseListeners.callChecked (checker, &MouseListener::mouseEnter, me); if (checker.shouldBailOut()) return; if (mouseListeners_ != 0) { for (int i = mouseListeners_->size(); --i >= 0;) { mouseListeners_->getUnchecked(i)->mouseEnter (me); if (checker.shouldBailOut()) return; i = jmin (i, mouseListeners_->size()); } } Component* p = parentComponent_; while (p != 0) { if (p->numDeepMouseListeners > 0) { BailOutChecker checker2 (this, p); for (int i = p->numDeepMouseListeners; --i >= 0;) { p->mouseListeners_->getUnchecked(i)->mouseEnter (me); if (checker2.shouldBailOut()) return; i = jmin (i, p->numDeepMouseListeners); } } p = p->parentComponent_; } } } void Component::internalMouseExit (MouseInputSource& source, const Point& relativePos, const Time& time) { BailOutChecker checker (this); if (flags.draggingFlag) { internalMouseUp (source, relativePos, time, source.getCurrentModifiers().getRawFlags()); if (checker.shouldBailOut()) return; } if (flags.mouseInsideFlag || flags.mouseOverFlag) { flags.mouseInsideFlag = false; flags.mouseOverFlag = false; flags.draggingFlag = false; if (flags.repaintOnMouseActivityFlag) repaint(); const MouseEvent me (source, relativePos, source.getCurrentModifiers(), this, this, time, relativePos, time, 0, false); mouseExit (me); if (checker.shouldBailOut()) return; Desktop::getInstance().resetTimer(); Desktop::getInstance().mouseListeners.callChecked (checker, &MouseListener::mouseExit, me); if (checker.shouldBailOut()) return; if (mouseListeners_ != 0) { for (int i = mouseListeners_->size(); --i >= 0;) { ((MouseListener*) mouseListeners_->getUnchecked (i))->mouseExit (me); if (checker.shouldBailOut()) return; i = jmin (i, mouseListeners_->size()); } } Component* p = parentComponent_; while (p != 0) { if (p->numDeepMouseListeners > 0) { BailOutChecker checker2 (this, p); for (int i = p->numDeepMouseListeners; --i >= 0;) { p->mouseListeners_->getUnchecked (i)->mouseExit (me); if (checker2.shouldBailOut()) return; i = jmin (i, p->numDeepMouseListeners); } } p = p->parentComponent_; } } } class InternalDragRepeater : public Timer { public: InternalDragRepeater() {} ~InternalDragRepeater() { clearSingletonInstance(); } juce_DeclareSingleton_SingleThreaded_Minimal (InternalDragRepeater) void timerCallback() { Desktop& desktop = Desktop::getInstance(); int numMiceDown = 0; for (int i = desktop.getNumMouseSources(); --i >= 0;) { MouseInputSource* const source = desktop.getMouseSource(i); if (source->isDragging()) { source->triggerFakeMove(); ++numMiceDown; } } if (numMiceDown == 0) deleteInstance(); } juce_UseDebuggingNewOperator private: InternalDragRepeater (const InternalDragRepeater&); InternalDragRepeater& operator= (const InternalDragRepeater&); }; juce_ImplementSingleton_SingleThreaded (InternalDragRepeater) void Component::beginDragAutoRepeat (const int interval) { if (interval > 0) { if (InternalDragRepeater::getInstance()->getTimerInterval() != interval) InternalDragRepeater::getInstance()->startTimer (interval); } else { InternalDragRepeater::deleteInstance(); } } void Component::internalMouseDown (MouseInputSource& source, const Point& relativePos, const Time& time) { Desktop& desktop = Desktop::getInstance(); BailOutChecker checker (this); if (isCurrentlyBlockedByAnotherModalComponent()) { internalModalInputAttempt(); if (checker.shouldBailOut()) return; // If processing the input attempt has exited the modal loop, we'll allow the event // to be delivered.. if (isCurrentlyBlockedByAnotherModalComponent()) { // allow blocked mouse-events to go to global listeners.. const MouseEvent me (source, relativePos, source.getCurrentModifiers(), this, this, time, relativePos, time, source.getNumberOfMultipleClicks(), false); desktop.resetTimer(); desktop.mouseListeners.callChecked (checker, &MouseListener::mouseDown, me); return; } } { Component* c = this; while (c != 0) { if (c->isBroughtToFrontOnMouseClick()) { c->toFront (true); if (checker.shouldBailOut()) return; } c = c->parentComponent_; } } if (! flags.dontFocusOnMouseClickFlag) { grabFocusInternal (focusChangedByMouseClick); if (checker.shouldBailOut()) return; } flags.draggingFlag = true; flags.mouseOverFlag = true; if (flags.repaintOnMouseActivityFlag) repaint(); const MouseEvent me (source, relativePos, source.getCurrentModifiers(), this, this, time, relativePos, time, source.getNumberOfMultipleClicks(), false); mouseDown (me); if (checker.shouldBailOut()) return; desktop.resetTimer(); desktop.mouseListeners.callChecked (checker, &MouseListener::mouseDown, me); if (checker.shouldBailOut()) return; if (mouseListeners_ != 0) { for (int i = mouseListeners_->size(); --i >= 0;) { ((MouseListener*) mouseListeners_->getUnchecked (i))->mouseDown (me); if (checker.shouldBailOut()) return; i = jmin (i, mouseListeners_->size()); } } Component* p = parentComponent_; while (p != 0) { if (p->numDeepMouseListeners > 0) { BailOutChecker checker2 (this, p); for (int i = p->numDeepMouseListeners; --i >= 0;) { p->mouseListeners_->getUnchecked (i)->mouseDown (me); if (checker2.shouldBailOut()) return; i = jmin (i, p->numDeepMouseListeners); } } p = p->parentComponent_; } } void Component::internalMouseUp (MouseInputSource& source, const Point& relativePos, const Time& time, const ModifierKeys& oldModifiers) { if (flags.draggingFlag) { Desktop& desktop = Desktop::getInstance(); flags.draggingFlag = false; BailOutChecker checker (this); if (flags.repaintOnMouseActivityFlag) repaint(); const MouseEvent me (source, relativePos, oldModifiers, this, this, time, globalPositionToRelative (source.getLastMouseDownPosition()), source.getLastMouseDownTime(), source.getNumberOfMultipleClicks(), source.hasMouseMovedSignificantlySincePressed()); mouseUp (me); if (checker.shouldBailOut()) return; desktop.resetTimer(); desktop.mouseListeners.callChecked (checker, &MouseListener::mouseUp, me); if (checker.shouldBailOut()) return; if (mouseListeners_ != 0) { for (int i = mouseListeners_->size(); --i >= 0;) { ((MouseListener*) mouseListeners_->getUnchecked (i))->mouseUp (me); if (checker.shouldBailOut()) return; i = jmin (i, mouseListeners_->size()); } } { Component* p = parentComponent_; while (p != 0) { if (p->numDeepMouseListeners > 0) { BailOutChecker checker2 (this, p); for (int i = p->numDeepMouseListeners; --i >= 0;) { p->mouseListeners_->getUnchecked (i)->mouseUp (me); if (checker2.shouldBailOut()) return; i = jmin (i, p->numDeepMouseListeners); } } p = p->parentComponent_; } } // check for double-click if (me.getNumberOfClicks() >= 2) { const int numListeners = (mouseListeners_ != 0) ? mouseListeners_->size() : 0; mouseDoubleClick (me); if (checker.shouldBailOut()) return; desktop.mouseListeners.callChecked (checker, &MouseListener::mouseDoubleClick, me); if (checker.shouldBailOut()) return; for (int i = numListeners; --i >= 0;) { if (checker.shouldBailOut()) return; MouseListener* const ml = (MouseListener*)((*mouseListeners_)[i]); if (ml != 0) ml->mouseDoubleClick (me); } if (checker.shouldBailOut()) return; Component* p = parentComponent_; while (p != 0) { if (p->numDeepMouseListeners > 0) { BailOutChecker checker2 (this, p); for (int i = p->numDeepMouseListeners; --i >= 0;) { p->mouseListeners_->getUnchecked (i)->mouseDoubleClick (me); if (checker2.shouldBailOut()) return; i = jmin (i, p->numDeepMouseListeners); } } p = p->parentComponent_; } } } } void Component::internalMouseDrag (MouseInputSource& source, const Point& relativePos, const Time& time) { if (flags.draggingFlag) { Desktop& desktop = Desktop::getInstance(); flags.mouseOverFlag = reallyContains (relativePos.getX(), relativePos.getY(), false); BailOutChecker checker (this); const MouseEvent me (source, relativePos, source.getCurrentModifiers(), this, this, time, globalPositionToRelative (source.getLastMouseDownPosition()), source.getLastMouseDownTime(), source.getNumberOfMultipleClicks(), source.hasMouseMovedSignificantlySincePressed()); mouseDrag (me); if (checker.shouldBailOut()) return; desktop.resetTimer(); desktop.mouseListeners.callChecked (checker, &MouseListener::mouseDrag, me); if (checker.shouldBailOut()) return; if (mouseListeners_ != 0) { for (int i = mouseListeners_->size(); --i >= 0;) { ((MouseListener*) mouseListeners_->getUnchecked (i))->mouseDrag (me); if (checker.shouldBailOut()) return; i = jmin (i, mouseListeners_->size()); } } Component* p = parentComponent_; while (p != 0) { if (p->numDeepMouseListeners > 0) { BailOutChecker checker2 (this, p); for (int i = p->numDeepMouseListeners; --i >= 0;) { p->mouseListeners_->getUnchecked (i)->mouseDrag (me); if (checker2.shouldBailOut()) return; i = jmin (i, p->numDeepMouseListeners); } } p = p->parentComponent_; } } } void Component::internalMouseMove (MouseInputSource& source, const Point& relativePos, const Time& time) { Desktop& desktop = Desktop::getInstance(); BailOutChecker checker (this); const MouseEvent me (source, relativePos, source.getCurrentModifiers(), this, this, time, relativePos, time, 0, false); if (isCurrentlyBlockedByAnotherModalComponent()) { // allow blocked mouse-events to go to global listeners.. desktop.sendMouseMove(); } else { flags.mouseOverFlag = true; mouseMove (me); if (checker.shouldBailOut()) return; desktop.resetTimer(); desktop.mouseListeners.callChecked (checker, &MouseListener::mouseMove, me); if (checker.shouldBailOut()) return; if (mouseListeners_ != 0) { for (int i = mouseListeners_->size(); --i >= 0;) { ((MouseListener*) mouseListeners_->getUnchecked (i))->mouseMove (me); if (checker.shouldBailOut()) return; i = jmin (i, mouseListeners_->size()); } } Component* p = parentComponent_; while (p != 0) { if (p->numDeepMouseListeners > 0) { BailOutChecker checker2 (this, p); for (int i = p->numDeepMouseListeners; --i >= 0;) { p->mouseListeners_->getUnchecked (i)->mouseMove (me); if (checker2.shouldBailOut()) return; i = jmin (i, p->numDeepMouseListeners); } } p = p->parentComponent_; } } } void Component::internalMouseWheel (MouseInputSource& source, const Point& relativePos, const Time& time, const float amountX, const float amountY) { Desktop& desktop = Desktop::getInstance(); BailOutChecker checker (this); const float wheelIncrementX = amountX / 256.0f; const float wheelIncrementY = amountY / 256.0f; const MouseEvent me (source, relativePos, source.getCurrentModifiers(), this, this, time, relativePos, time, 0, false); if (isCurrentlyBlockedByAnotherModalComponent()) { // allow blocked mouse-events to go to global listeners.. desktop.mouseListeners.callChecked (checker, &MouseListener::mouseWheelMove, me, wheelIncrementX, wheelIncrementY); } else { mouseWheelMove (me, wheelIncrementX, wheelIncrementY); if (checker.shouldBailOut()) return; desktop.mouseListeners.callChecked (checker, &MouseListener::mouseWheelMove, me, wheelIncrementX, wheelIncrementY); if (checker.shouldBailOut()) return; if (mouseListeners_ != 0) { for (int i = mouseListeners_->size(); --i >= 0;) { ((MouseListener*) mouseListeners_->getUnchecked (i))->mouseWheelMove (me, wheelIncrementX, wheelIncrementY); if (checker.shouldBailOut()) return; i = jmin (i, mouseListeners_->size()); } } Component* p = parentComponent_; while (p != 0) { if (p->numDeepMouseListeners > 0) { BailOutChecker checker2 (this, p); for (int i = p->numDeepMouseListeners; --i >= 0;) { p->mouseListeners_->getUnchecked (i)->mouseWheelMove (me, wheelIncrementX, wheelIncrementY); if (checker2.shouldBailOut()) return; i = jmin (i, p->numDeepMouseListeners); } } p = p->parentComponent_; } } } void Component::sendFakeMouseMove() const { Desktop::getInstance().getMainMouseSource().triggerFakeMove(); } void Component::broughtToFront() { } void Component::internalBroughtToFront() { if (! isValidComponent()) return; if (flags.hasHeavyweightPeerFlag) Desktop::getInstance().componentBroughtToFront (this); BailOutChecker checker (this); broughtToFront(); if (checker.shouldBailOut()) return; componentListeners.callChecked (checker, &ComponentListener::componentBroughtToFront, *this); if (checker.shouldBailOut()) return; // When brought to the front and there's a modal component blocking this one, // we need to bring the modal one to the front instead.. Component* const cm = getCurrentlyModalComponent(); if (cm != 0 && cm->getTopLevelComponent() != getTopLevelComponent()) bringModalComponentToFront(); } void Component::focusGained (FocusChangeType) { // base class does nothing } void Component::internalFocusGain (const FocusChangeType cause) { SafePointer safePointer (this); focusGained (cause); if (safePointer != 0) internalChildFocusChange (cause); } void Component::focusLost (FocusChangeType) { // base class does nothing } void Component::internalFocusLoss (const FocusChangeType cause) { SafePointer safePointer (this); focusLost (focusChangedDirectly); if (safePointer != 0) internalChildFocusChange (cause); } void Component::focusOfChildComponentChanged (FocusChangeType /*cause*/) { // base class does nothing } void Component::internalChildFocusChange (FocusChangeType cause) { const bool childIsNowFocused = hasKeyboardFocus (true); if (flags.childCompFocusedFlag != childIsNowFocused) { flags.childCompFocusedFlag = childIsNowFocused; SafePointer safePointer (this); focusOfChildComponentChanged (cause); if (safePointer == 0) return; } if (parentComponent_ != 0) parentComponent_->internalChildFocusChange (cause); } bool Component::isEnabled() const throw() { return (! flags.isDisabledFlag) && (parentComponent_ == 0 || parentComponent_->isEnabled()); } void Component::setEnabled (const bool shouldBeEnabled) { if (flags.isDisabledFlag == shouldBeEnabled) { flags.isDisabledFlag = ! shouldBeEnabled; // if any parent components are disabled, setting our flag won't make a difference, // so no need to send a change message if (parentComponent_ == 0 || parentComponent_->isEnabled()) sendEnablementChangeMessage(); } } void Component::sendEnablementChangeMessage() { SafePointer safePointer (this); enablementChanged(); if (safePointer == 0) return; for (int i = getNumChildComponents(); --i >= 0;) { Component* const c = getChildComponent (i); if (c != 0) { c->sendEnablementChangeMessage(); if (safePointer == 0) return; } } } void Component::enablementChanged() { } void Component::setWantsKeyboardFocus (const bool wantsFocus) throw() { flags.wantsFocusFlag = wantsFocus; } void Component::setMouseClickGrabsKeyboardFocus (const bool shouldGrabFocus) { flags.dontFocusOnMouseClickFlag = ! shouldGrabFocus; } bool Component::getMouseClickGrabsKeyboardFocus() const throw() { return ! flags.dontFocusOnMouseClickFlag; } bool Component::getWantsKeyboardFocus() const throw() { return flags.wantsFocusFlag && ! flags.isDisabledFlag; } void Component::setFocusContainer (const bool shouldBeFocusContainer) throw() { flags.isFocusContainerFlag = shouldBeFocusContainer; } bool Component::isFocusContainer() const throw() { return flags.isFocusContainerFlag; } static const Identifier juce_explicitFocusOrderId ("_jexfo"); int Component::getExplicitFocusOrder() const { return properties [juce_explicitFocusOrderId]; } void Component::setExplicitFocusOrder (const int newFocusOrderIndex) { properties.set (juce_explicitFocusOrderId, newFocusOrderIndex); } KeyboardFocusTraverser* Component::createFocusTraverser() { if (flags.isFocusContainerFlag || parentComponent_ == 0) return new KeyboardFocusTraverser(); return parentComponent_->createFocusTraverser(); } void Component::takeKeyboardFocus (const FocusChangeType cause) { // give the focus to this component if (currentlyFocusedComponent != this) { JUCE_TRY { // get the focus onto our desktop window ComponentPeer* const peer = getPeer(); if (peer != 0) { SafePointer safePointer (this); peer->grabFocus(); if (peer->isFocused() && currentlyFocusedComponent != this) { SafePointer componentLosingFocus (currentlyFocusedComponent); currentlyFocusedComponent = this; Desktop::getInstance().triggerFocusCallback(); // call this after setting currentlyFocusedComponent so that the one that's // losing it has a chance to see where focus is going if (componentLosingFocus != 0) componentLosingFocus->internalFocusLoss (cause); if (currentlyFocusedComponent == this) { focusGained (cause); if (safePointer != 0) internalChildFocusChange (cause); } } } } #if JUCE_CATCH_UNHANDLED_EXCEPTIONS catch (const std::exception& e) { currentlyFocusedComponent = 0; Desktop::getInstance().triggerFocusCallback(); JUCEApplication::sendUnhandledException (&e, __FILE__, __LINE__); } catch (...) { currentlyFocusedComponent = 0; Desktop::getInstance().triggerFocusCallback(); JUCEApplication::sendUnhandledException (0, __FILE__, __LINE__); } #endif } } void Component::grabFocusInternal (const FocusChangeType cause, const bool canTryParent) { if (isShowing()) { if (flags.wantsFocusFlag && (isEnabled() || parentComponent_ == 0)) { takeKeyboardFocus (cause); } else { if (isParentOf (currentlyFocusedComponent) && currentlyFocusedComponent->isShowing()) { // do nothing if the focused component is actually a child of ours.. } else { // find the default child component.. ScopedPointer traverser (createFocusTraverser()); if (traverser != 0) { Component* const defaultComp = traverser->getDefaultComponent (this); traverser = 0; if (defaultComp != 0) { defaultComp->grabFocusInternal (cause, false); return; } } if (canTryParent && parentComponent_ != 0) { // if no children want it and we're allowed to try our parent comp, // then pass up to parent, which will try our siblings. parentComponent_->grabFocusInternal (cause, true); } } } } } void Component::grabKeyboardFocus() { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. checkMessageManagerIsLocked grabFocusInternal (focusChangedDirectly); } void Component::moveKeyboardFocusToSibling (const bool moveToNext) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. checkMessageManagerIsLocked if (parentComponent_ != 0) { ScopedPointer traverser (createFocusTraverser()); if (traverser != 0) { Component* const nextComp = moveToNext ? traverser->getNextComponent (this) : traverser->getPreviousComponent (this); traverser = 0; if (nextComp != 0) { if (nextComp->isCurrentlyBlockedByAnotherModalComponent()) { SafePointer nextCompPointer (nextComp); internalModalInputAttempt(); if (nextCompPointer == 0 || nextComp->isCurrentlyBlockedByAnotherModalComponent()) return; } nextComp->grabFocusInternal (focusChangedByTabKey); return; } } parentComponent_->moveKeyboardFocusToSibling (moveToNext); } } bool Component::hasKeyboardFocus (const bool trueIfChildIsFocused) const { return (currentlyFocusedComponent == this) || (trueIfChildIsFocused && isParentOf (currentlyFocusedComponent)); } Component* JUCE_CALLTYPE Component::getCurrentlyFocusedComponent() throw() { return currentlyFocusedComponent; } void Component::giveAwayFocus() { // use a copy so we can clear the value before the call SafePointer componentLosingFocus (currentlyFocusedComponent); currentlyFocusedComponent = 0; Desktop::getInstance().triggerFocusCallback(); if (componentLosingFocus != 0) componentLosingFocus->internalFocusLoss (focusChangedDirectly); } bool Component::isMouseOver() const throw() { return flags.mouseOverFlag; } bool Component::isMouseButtonDown() const throw() { return flags.draggingFlag; } bool Component::isMouseOverOrDragging() const throw() { return flags.mouseOverFlag || flags.draggingFlag; } bool JUCE_CALLTYPE Component::isMouseButtonDownAnywhere() throw() { return ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown(); } const Point Component::getMouseXYRelative() const { return globalPositionToRelative (Desktop::getMousePosition()); } const Rectangle Component::getParentMonitorArea() const { return Desktop::getInstance() .getMonitorAreaContaining (relativePositionToGlobal (getLocalBounds().getCentre())); } void Component::addKeyListener (KeyListener* const newListener) { if (keyListeners_ == 0) keyListeners_ = new Array (); keyListeners_->addIfNotAlreadyThere (newListener); } void Component::removeKeyListener (KeyListener* const listenerToRemove) { if (keyListeners_ != 0) keyListeners_->removeValue (listenerToRemove); } bool Component::keyPressed (const KeyPress&) { return false; } bool Component::keyStateChanged (const bool /*isKeyDown*/) { return false; } void Component::modifierKeysChanged (const ModifierKeys& modifiers) { if (parentComponent_ != 0) parentComponent_->modifierKeysChanged (modifiers); } void Component::internalModifierKeysChanged() { sendFakeMouseMove(); modifierKeysChanged (ModifierKeys::getCurrentModifiers()); } ComponentPeer* Component::getPeer() const { if (flags.hasHeavyweightPeerFlag) return ComponentPeer::getPeerFor (this); else if (parentComponent_ != 0) return parentComponent_->getPeer(); else return 0; } Component::BailOutChecker::BailOutChecker (Component* const component1, Component* const component2_) : safePointer1 (component1), safePointer2 (component2_), component2 (component2_) { jassert (component1 != 0); } bool Component::BailOutChecker::shouldBailOut() const throw() { return safePointer1 == 0 || safePointer2.getComponent() != component2; } END_JUCE_NAMESPACE /*** End of inlined file: juce_Component.cpp ***/ /*** Start of inlined file: juce_ComponentListener.cpp ***/ BEGIN_JUCE_NAMESPACE void ComponentListener::componentMovedOrResized (Component&, bool, bool) {} void ComponentListener::componentBroughtToFront (Component&) {} void ComponentListener::componentVisibilityChanged (Component&) {} void ComponentListener::componentChildrenChanged (Component&) {} void ComponentListener::componentParentHierarchyChanged (Component&) {} void ComponentListener::componentNameChanged (Component&) {} void ComponentListener::componentBeingDeleted (Component&) {} END_JUCE_NAMESPACE /*** End of inlined file: juce_ComponentListener.cpp ***/ /*** Start of inlined file: juce_Desktop.cpp ***/ BEGIN_JUCE_NAMESPACE Desktop::Desktop() : mouseClickCounter (0), kioskModeComponent (0) { createMouseInputSources(); refreshMonitorSizes(); } Desktop::~Desktop() { jassert (instance == this); instance = 0; // doh! If you don't delete all your windows before exiting, you're going to // be leaking memory! jassert (desktopComponents.size() == 0); } Desktop& JUCE_CALLTYPE Desktop::getInstance() { if (instance == 0) instance = new Desktop(); return *instance; } Desktop* Desktop::instance = 0; extern void juce_updateMultiMonitorInfo (Array >& monitorCoords, const bool clipToWorkArea); void Desktop::refreshMonitorSizes() { const Array > oldClipped (monitorCoordsClipped); const Array > oldUnclipped (monitorCoordsUnclipped); monitorCoordsClipped.clear(); monitorCoordsUnclipped.clear(); juce_updateMultiMonitorInfo (monitorCoordsClipped, true); juce_updateMultiMonitorInfo (monitorCoordsUnclipped, false); jassert (monitorCoordsClipped.size() == monitorCoordsUnclipped.size()); if (oldClipped != monitorCoordsClipped || oldUnclipped != monitorCoordsUnclipped) { for (int i = ComponentPeer::getNumPeers(); --i >= 0;) { ComponentPeer* const p = ComponentPeer::getPeer (i); if (p != 0) p->handleScreenSizeChange(); } } } int Desktop::getNumDisplayMonitors() const throw() { return monitorCoordsClipped.size(); } const Rectangle Desktop::getDisplayMonitorCoordinates (const int index, const bool clippedToWorkArea) const throw() { return clippedToWorkArea ? monitorCoordsClipped [index] : monitorCoordsUnclipped [index]; } const RectangleList Desktop::getAllMonitorDisplayAreas (const bool clippedToWorkArea) const throw() { RectangleList rl; for (int i = 0; i < getNumDisplayMonitors(); ++i) rl.addWithoutMerging (getDisplayMonitorCoordinates (i, clippedToWorkArea)); return rl; } const Rectangle Desktop::getMainMonitorArea (const bool clippedToWorkArea) const throw() { return getDisplayMonitorCoordinates (0, clippedToWorkArea); } const Rectangle Desktop::getMonitorAreaContaining (const Point& position, const bool clippedToWorkArea) const { Rectangle best (getMainMonitorArea (clippedToWorkArea)); double bestDistance = 1.0e10; for (int i = getNumDisplayMonitors(); --i >= 0;) { const Rectangle rect (getDisplayMonitorCoordinates (i, clippedToWorkArea)); if (rect.contains (position)) return rect; const double distance = rect.getCentre().getDistanceFrom (position); if (distance < bestDistance) { bestDistance = distance; best = rect; } } return best; } int Desktop::getNumComponents() const throw() { return desktopComponents.size(); } Component* Desktop::getComponent (const int index) const throw() { return desktopComponents [index]; } Component* Desktop::findComponentAt (const Point& screenPosition) const { for (int i = desktopComponents.size(); --i >= 0;) { Component* const c = desktopComponents.getUnchecked(i); const Point relative (c->globalPositionToRelative (screenPosition)); if (c->contains (relative.getX(), relative.getY())) return c->getComponentAt (relative.getX(), relative.getY()); } return 0; } void Desktop::addDesktopComponent (Component* const c) { jassert (c != 0); jassert (! desktopComponents.contains (c)); desktopComponents.addIfNotAlreadyThere (c); } void Desktop::removeDesktopComponent (Component* const c) { desktopComponents.removeValue (c); } void Desktop::componentBroughtToFront (Component* const c) { const int index = desktopComponents.indexOf (c); jassert (index >= 0); if (index >= 0) { int newIndex = -1; if (! c->isAlwaysOnTop()) { newIndex = desktopComponents.size(); while (newIndex > 0 && desktopComponents.getUnchecked (newIndex - 1)->isAlwaysOnTop()) --newIndex; --newIndex; } desktopComponents.move (index, newIndex); } } const Point Desktop::getLastMouseDownPosition() throw() { return getInstance().getMainMouseSource().getLastMouseDownPosition(); } int Desktop::getMouseButtonClickCounter() throw() { return getInstance().mouseClickCounter; } void Desktop::incrementMouseClickCounter() throw() { ++mouseClickCounter; } int Desktop::getNumDraggingMouseSources() const throw() { int num = 0; for (int i = mouseSources.size(); --i >= 0;) if (mouseSources.getUnchecked(i)->isDragging()) ++num; return num; } MouseInputSource* Desktop::getDraggingMouseSource (int index) const throw() { int num = 0; for (int i = mouseSources.size(); --i >= 0;) { MouseInputSource* const mi = mouseSources.getUnchecked(i); if (mi->isDragging()) { if (index == num) return mi; ++num; } } return 0; } void Desktop::addFocusChangeListener (FocusChangeListener* const listener) { focusListeners.add (listener); } void Desktop::removeFocusChangeListener (FocusChangeListener* const listener) { focusListeners.remove (listener); } void Desktop::triggerFocusCallback() { triggerAsyncUpdate(); } void Desktop::handleAsyncUpdate() { Component* currentFocus = Component::getCurrentlyFocusedComponent(); focusListeners.call (&FocusChangeListener::globalFocusChanged, currentFocus); } void Desktop::addGlobalMouseListener (MouseListener* const listener) { mouseListeners.add (listener); resetTimer(); } void Desktop::removeGlobalMouseListener (MouseListener* const listener) { mouseListeners.remove (listener); resetTimer(); } void Desktop::timerCallback() { if (lastFakeMouseMove != getMousePosition()) sendMouseMove(); } void Desktop::sendMouseMove() { if (! mouseListeners.isEmpty()) { startTimer (20); lastFakeMouseMove = getMousePosition(); Component* const target = findComponentAt (lastFakeMouseMove); if (target != 0) { Component::BailOutChecker checker (target); const Point pos (target->globalPositionToRelative (lastFakeMouseMove)); const Time now (Time::getCurrentTime()); const MouseEvent me (getMainMouseSource(), pos, ModifierKeys::getCurrentModifiers(), target, target, now, pos, now, 0, false); if (me.mods.isAnyMouseButtonDown()) mouseListeners.callChecked (checker, &MouseListener::mouseDrag, me); else mouseListeners.callChecked (checker, &MouseListener::mouseMove, me); } } } void Desktop::resetTimer() { if (mouseListeners.size() == 0) stopTimer(); else startTimer (100); lastFakeMouseMove = getMousePosition(); } extern void juce_setKioskComponent (Component* kioskModeComponent, bool enableOrDisable, bool allowMenusAndBars); void Desktop::setKioskModeComponent (Component* componentToUse, const bool allowMenusAndBars) { if (kioskModeComponent != componentToUse) { // agh! Don't delete a component without first stopping it being the kiosk comp jassert (kioskModeComponent == 0 || kioskModeComponent->isValidComponent()); // agh! Don't remove a component from the desktop if it's the kiosk comp! jassert (kioskModeComponent == 0 || kioskModeComponent->isOnDesktop()); if (kioskModeComponent->isValidComponent()) { juce_setKioskComponent (kioskModeComponent, false, allowMenusAndBars); kioskModeComponent->setBounds (kioskComponentOriginalBounds); } kioskModeComponent = componentToUse; if (kioskModeComponent != 0) { jassert (kioskModeComponent->isValidComponent()); // Only components that are already on the desktop can be put into kiosk mode! jassert (kioskModeComponent->isOnDesktop()); kioskComponentOriginalBounds = kioskModeComponent->getBounds(); juce_setKioskComponent (kioskModeComponent, true, allowMenusAndBars); } } } END_JUCE_NAMESPACE /*** End of inlined file: juce_Desktop.cpp ***/ /*** Start of inlined file: juce_ModalComponentManager.cpp ***/ BEGIN_JUCE_NAMESPACE class ModalComponentManager::ModalItem : public ComponentListener { public: ModalItem (Component* const comp, Callback* const callback) : component (comp), returnValue (0), isActive (true), isDeleted (false) { if (callback != 0) callbacks.add (callback); jassert (comp != 0); component->addComponentListener (this); } ~ModalItem() { if (! isDeleted) component->removeComponentListener (this); } void componentBeingDeleted (Component&) { isDeleted = true; cancel(); } void componentVisibilityChanged (Component&) { if (! component->isShowing()) cancel(); } void componentParentHierarchyChanged (Component&) { if (! component->isShowing()) cancel(); } void cancel() { if (isActive) { isActive = false; ModalComponentManager::getInstance()->triggerAsyncUpdate(); } } Component* component; OwnedArray callbacks; int returnValue; bool isActive, isDeleted; private: ModalItem (const ModalItem&); ModalItem& operator= (const ModalItem&); }; ModalComponentManager::ModalComponentManager() { } ModalComponentManager::~ModalComponentManager() { clearSingletonInstance(); } juce_ImplementSingleton_SingleThreaded (ModalComponentManager); void ModalComponentManager::startModal (Component* component, Callback* callback) { if (component != 0) stack.add (new ModalItem (component, callback)); } void ModalComponentManager::attachCallback (Component* component, Callback* callback) { if (callback != 0) { ScopedPointer callbackDeleter (callback); for (int i = stack.size(); --i >= 0;) { ModalItem* const item = stack.getUnchecked(i); if (item->component == component) { item->callbacks.add (callback); callbackDeleter.release(); break; } } } } void ModalComponentManager::endModal (Component* component) { for (int i = stack.size(); --i >= 0;) { ModalItem* const item = stack.getUnchecked(i); if (item->component == component) item->cancel(); } } void ModalComponentManager::endModal (Component* component, int returnValue) { for (int i = stack.size(); --i >= 0;) { ModalItem* const item = stack.getUnchecked(i); if (item->component == component) { item->returnValue = returnValue; item->cancel(); } } } int ModalComponentManager::getNumModalComponents() const { int n = 0; for (int i = 0; i < stack.size(); ++i) if (stack.getUnchecked(i)->isActive) ++n; return n; } Component* ModalComponentManager::getModalComponent (const int index) const { int n = 0; for (int i = stack.size(); --i >= 0;) { const ModalItem* const item = stack.getUnchecked(i); if (item->isActive) if (n++ == index) return item->component; } return 0; } bool ModalComponentManager::isModal (Component* const comp) const { for (int i = stack.size(); --i >= 0;) { const ModalItem* const item = stack.getUnchecked(i); if (item->isActive && item->component == comp) return true; } return false; } bool ModalComponentManager::isFrontModalComponent (Component* const comp) const { return comp == getModalComponent (0); } void ModalComponentManager::handleAsyncUpdate() { for (int i = stack.size(); --i >= 0;) { const ModalItem* const item = stack.getUnchecked(i); if (! item->isActive) { for (int j = item->callbacks.size(); --j >= 0;) item->callbacks.getUnchecked(j)->modalStateFinished (item->returnValue); stack.remove (i); } } } class ModalComponentManager::ReturnValueRetriever : public ModalComponentManager::Callback { public: ReturnValueRetriever (int& value_, bool& finished_) : value (value_), finished (finished_) {} ~ReturnValueRetriever() {} void modalStateFinished (int returnValue) { finished = true; value = returnValue; } private: int& value; bool& finished; ReturnValueRetriever (const ReturnValueRetriever&); ReturnValueRetriever& operator= (const ReturnValueRetriever&); }; int ModalComponentManager::runEventLoopForCurrentComponent() { // This can only be run from the message thread! jassert (MessageManager::getInstance()->isThisTheMessageThread()); Component* currentlyModal = getModalComponent (0); if (currentlyModal == 0) return 0; Component::SafePointer prevFocused (Component::getCurrentlyFocusedComponent()); int returnValue = 0; bool finished = false; attachCallback (currentlyModal, new ReturnValueRetriever (returnValue, finished)); JUCE_TRY { while (! finished) { if (! MessageManager::getInstance()->runDispatchLoopUntil (20)) break; } } JUCE_CATCH_EXCEPTION if (prevFocused != 0) prevFocused->grabKeyboardFocus(); return returnValue; } END_JUCE_NAMESPACE /*** End of inlined file: juce_ModalComponentManager.cpp ***/ /*** Start of inlined file: juce_ArrowButton.cpp ***/ BEGIN_JUCE_NAMESPACE ArrowButton::ArrowButton (const String& name, float arrowDirectionInRadians, const Colour& arrowColour) : Button (name), colour (arrowColour) { path.lineTo (0.0f, 1.0f); path.lineTo (1.0f, 0.5f); path.closeSubPath(); path.applyTransform (AffineTransform::rotation (float_Pi * 2.0f * arrowDirectionInRadians, 0.5f, 0.5f)); setComponentEffect (&shadow); buttonStateChanged(); } ArrowButton::~ArrowButton() { } void ArrowButton::paintButton (Graphics& g, bool /*isMouseOverButton*/, bool /*isButtonDown*/) { g.setColour (colour); g.fillPath (path, path.getTransformToScaleToFit ((float) offset, (float) offset, (float) (getWidth() - 3), (float) (getHeight() - 3), false)); } void ArrowButton::buttonStateChanged() { offset = (isDown()) ? 1 : 0; shadow.setShadowProperties ((isDown()) ? 1.2f : 3.0f, 0.3f, -1, 0); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ArrowButton.cpp ***/ /*** Start of inlined file: juce_Button.cpp ***/ BEGIN_JUCE_NAMESPACE class Button::RepeatTimer : public Timer { public: RepeatTimer (Button& owner_) : owner (owner_) {} void timerCallback() { owner.repeatTimerCallback(); } juce_UseDebuggingNewOperator private: Button& owner; RepeatTimer (const RepeatTimer&); RepeatTimer& operator= (const RepeatTimer&); }; Button::Button (const String& name) : Component (name), text (name), buttonPressTime (0), lastTimeCallbackTime (0), commandManagerToUse (0), autoRepeatDelay (-1), autoRepeatSpeed (0), autoRepeatMinimumDelay (-1), radioGroupId (0), commandID (0), connectedEdgeFlags (0), buttonState (buttonNormal), lastToggleState (false), clickTogglesState (false), needsToRelease (false), needsRepainting (false), isKeyDown (false), triggerOnMouseDown (false), generateTooltip (false) { setWantsKeyboardFocus (true); isOn.addListener (this); } Button::~Button() { isOn.removeListener (this); if (commandManagerToUse != 0) commandManagerToUse->removeListener (this); repeatTimer = 0; clearShortcuts(); } void Button::setButtonText (const String& newText) { if (text != newText) { text = newText; repaint(); } } void Button::setTooltip (const String& newTooltip) { SettableTooltipClient::setTooltip (newTooltip); generateTooltip = false; } const String Button::getTooltip() { if (generateTooltip && commandManagerToUse != 0 && commandID != 0) { String tt (commandManagerToUse->getDescriptionOfCommand (commandID)); Array keyPresses (commandManagerToUse->getKeyMappings()->getKeyPressesAssignedToCommand (commandID)); for (int i = 0; i < keyPresses.size(); ++i) { const String key (keyPresses.getReference(i).getTextDescription()); tt << " ["; if (key.length() == 1) tt << TRANS("shortcut") << ": '" << key << "']"; else tt << key << ']'; } return tt; } return SettableTooltipClient::getTooltip(); } void Button::setConnectedEdges (const int connectedEdgeFlags_) { if (connectedEdgeFlags != connectedEdgeFlags_) { connectedEdgeFlags = connectedEdgeFlags_; repaint(); } } void Button::setToggleState (const bool shouldBeOn, const bool sendChangeNotification) { if (shouldBeOn != lastToggleState) { if (isOn != shouldBeOn) // this test means that if the value is void rather than explicitly set to isOn = shouldBeOn; // false, it won't be changed unless the required value is true. lastToggleState = shouldBeOn; repaint(); if (sendChangeNotification) { Component::SafePointer deletionWatcher (this); sendClickMessage (ModifierKeys()); if (deletionWatcher == 0) return; } if (lastToggleState) turnOffOtherButtonsInGroup (sendChangeNotification); } } void Button::setClickingTogglesState (const bool shouldToggle) throw() { clickTogglesState = shouldToggle; // if you've got clickTogglesState turned on, you shouldn't also connect the button // up to be a command invoker. Instead, your command handler must flip the state of whatever // it is that this button represents, and the button will update its state to reflect this // in the applicationCommandListChanged() method. jassert (commandManagerToUse == 0 || ! clickTogglesState); } bool Button::getClickingTogglesState() const throw() { return clickTogglesState; } void Button::valueChanged (Value& value) { if (value.refersToSameSourceAs (isOn)) setToggleState (isOn.getValue(), true); } void Button::setRadioGroupId (const int newGroupId) { if (radioGroupId != newGroupId) { radioGroupId = newGroupId; if (lastToggleState) turnOffOtherButtonsInGroup (true); } } void Button::turnOffOtherButtonsInGroup (const bool sendChangeNotification) { Component* const p = getParentComponent(); if (p != 0 && radioGroupId != 0) { Component::SafePointer deletionWatcher (this); for (int i = p->getNumChildComponents(); --i >= 0;) { Component* const c = p->getChildComponent (i); if (c != this) { Button* const b = dynamic_cast (c); if (b != 0 && b->getRadioGroupId() == radioGroupId) { b->setToggleState (false, sendChangeNotification); if (deletionWatcher == 0) return; } } } } } void Button::enablementChanged() { updateState (0); repaint(); } Button::ButtonState Button::updateState (const MouseEvent* const e) { ButtonState state = buttonNormal; if (isEnabled() && isVisible() && ! isCurrentlyBlockedByAnotherModalComponent()) { Point mousePos; if (e == 0) mousePos = getMouseXYRelative(); else mousePos = e->getEventRelativeTo (this).getPosition(); const bool over = reallyContains (mousePos.getX(), mousePos.getY(), true); const bool down = isMouseButtonDown(); if ((down && (over || (triggerOnMouseDown && buttonState == buttonDown))) || isKeyDown) state = buttonDown; else if (over) state = buttonOver; } setState (state); return state; } void Button::setState (const ButtonState newState) { if (buttonState != newState) { buttonState = newState; repaint(); if (buttonState == buttonDown) { buttonPressTime = Time::getApproximateMillisecondCounter(); lastTimeCallbackTime = buttonPressTime; } sendStateMessage(); } } bool Button::isDown() const throw() { return buttonState == buttonDown; } bool Button::isOver() const throw() { return buttonState != buttonNormal; } void Button::buttonStateChanged() { } uint32 Button::getMillisecondsSinceButtonDown() const throw() { const uint32 now = Time::getApproximateMillisecondCounter(); return now > buttonPressTime ? now - buttonPressTime : 0; } void Button::setTriggeredOnMouseDown (const bool isTriggeredOnMouseDown) throw() { triggerOnMouseDown = isTriggeredOnMouseDown; } void Button::clicked() { } void Button::clicked (const ModifierKeys& /*modifiers*/) { clicked(); } static const int clickMessageId = 0x2f3f4f99; void Button::triggerClick() { postCommandMessage (clickMessageId); } void Button::internalClickCallback (const ModifierKeys& modifiers) { if (clickTogglesState) setToggleState ((radioGroupId != 0) || ! lastToggleState, false); sendClickMessage (modifiers); } void Button::flashButtonState() { if (isEnabled()) { needsToRelease = true; setState (buttonDown); getRepeatTimer().startTimer (100); } } void Button::handleCommandMessage (int commandId) { if (commandId == clickMessageId) { if (isEnabled()) { flashButtonState(); internalClickCallback (ModifierKeys::getCurrentModifiers()); } } else { Component::handleCommandMessage (commandId); } } void Button::addButtonListener (Listener* const newListener) { buttonListeners.add (newListener); } void Button::removeButtonListener (Listener* const listener) { buttonListeners.remove (listener); } void Button::sendClickMessage (const ModifierKeys& modifiers) { Component::BailOutChecker checker (this); if (commandManagerToUse != 0 && commandID != 0) { ApplicationCommandTarget::InvocationInfo info (commandID); info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromButton; info.originatingComponent = this; commandManagerToUse->invoke (info, true); } clicked (modifiers); if (! checker.shouldBailOut()) buttonListeners.callChecked (checker, &ButtonListener::buttonClicked, this); // (can't use Button::Listener due to idiotic VC2005 bug) } void Button::sendStateMessage() { Component::BailOutChecker checker (this); buttonStateChanged(); if (! checker.shouldBailOut()) buttonListeners.callChecked (checker, &ButtonListener::buttonStateChanged, this); } void Button::paint (Graphics& g) { if (needsToRelease && isEnabled()) { needsToRelease = false; needsRepainting = true; } paintButton (g, isOver(), isDown()); } void Button::mouseEnter (const MouseEvent& e) { updateState (&e); } void Button::mouseExit (const MouseEvent& e) { updateState (&e); } void Button::mouseDown (const MouseEvent& e) { updateState (&e); if (isDown()) { if (autoRepeatDelay >= 0) getRepeatTimer().startTimer (autoRepeatDelay); if (triggerOnMouseDown) internalClickCallback (e.mods); } } void Button::mouseUp (const MouseEvent& e) { const bool wasDown = isDown(); updateState (&e); if (wasDown && isOver() && ! triggerOnMouseDown) internalClickCallback (e.mods); } void Button::mouseDrag (const MouseEvent& e) { const ButtonState oldState = buttonState; updateState (&e); if (autoRepeatDelay >= 0 && buttonState != oldState && isDown()) getRepeatTimer().startTimer (autoRepeatSpeed); } void Button::focusGained (FocusChangeType) { updateState (0); repaint(); } void Button::focusLost (FocusChangeType) { updateState (0); repaint(); } void Button::setVisible (bool shouldBeVisible) { if (shouldBeVisible != isVisible()) { Component::setVisible (shouldBeVisible); if (! shouldBeVisible) needsToRelease = false; updateState (0); } else { Component::setVisible (shouldBeVisible); } } void Button::parentHierarchyChanged() { Component* const newKeySource = (shortcuts.size() == 0) ? 0 : getTopLevelComponent(); if (newKeySource != keySource.getComponent()) { if (keySource != 0) keySource->removeKeyListener (this); keySource = newKeySource; if (keySource != 0) keySource->addKeyListener (this); } } void Button::setCommandToTrigger (ApplicationCommandManager* const commandManagerToUse_, const int commandID_, const bool generateTooltip_) { commandID = commandID_; generateTooltip = generateTooltip_; if (commandManagerToUse != commandManagerToUse_) { if (commandManagerToUse != 0) commandManagerToUse->removeListener (this); commandManagerToUse = commandManagerToUse_; if (commandManagerToUse != 0) commandManagerToUse->addListener (this); // if you've got clickTogglesState turned on, you shouldn't also connect the button // up to be a command invoker. Instead, your command handler must flip the state of whatever // it is that this button represents, and the button will update its state to reflect this // in the applicationCommandListChanged() method. jassert (commandManagerToUse == 0 || ! clickTogglesState); } if (commandManagerToUse != 0) applicationCommandListChanged(); else setEnabled (true); } void Button::applicationCommandInvoked (const ApplicationCommandTarget::InvocationInfo& info) { if (info.commandID == commandID && (info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) == 0) { flashButtonState(); } } void Button::applicationCommandListChanged() { if (commandManagerToUse != 0) { ApplicationCommandInfo info (0); ApplicationCommandTarget* const target = commandManagerToUse->getTargetForCommand (commandID, info); setEnabled (target != 0 && (info.flags & ApplicationCommandInfo::isDisabled) == 0); if (target != 0) setToggleState ((info.flags & ApplicationCommandInfo::isTicked) != 0, false); } } void Button::addShortcut (const KeyPress& key) { if (key.isValid()) { jassert (! isRegisteredForShortcut (key)); // already registered! shortcuts.add (key); parentHierarchyChanged(); } } void Button::clearShortcuts() { shortcuts.clear(); parentHierarchyChanged(); } bool Button::isShortcutPressed() const { if (! isCurrentlyBlockedByAnotherModalComponent()) { for (int i = shortcuts.size(); --i >= 0;) if (shortcuts.getReference(i).isCurrentlyDown()) return true; } return false; } bool Button::isRegisteredForShortcut (const KeyPress& key) const { for (int i = shortcuts.size(); --i >= 0;) if (key == shortcuts.getReference(i)) return true; return false; } bool Button::keyStateChanged (const bool, Component*) { if (! isEnabled()) return false; const bool wasDown = isKeyDown; isKeyDown = isShortcutPressed(); if (autoRepeatDelay >= 0 && (isKeyDown && ! wasDown)) getRepeatTimer().startTimer (autoRepeatDelay); updateState (0); if (isEnabled() && wasDown && ! isKeyDown) { internalClickCallback (ModifierKeys::getCurrentModifiers()); // (return immediately - this button may now have been deleted) return true; } return wasDown || isKeyDown; } bool Button::keyPressed (const KeyPress&, Component*) { // returning true will avoid forwarding events for keys that we're using as shortcuts return isShortcutPressed(); } bool Button::keyPressed (const KeyPress& key) { if (isEnabled() && key.isKeyCode (KeyPress::returnKey)) { triggerClick(); return true; } return false; } void Button::setRepeatSpeed (const int initialDelayMillisecs, const int repeatMillisecs, const int minimumDelayInMillisecs) throw() { autoRepeatDelay = initialDelayMillisecs; autoRepeatSpeed = repeatMillisecs; autoRepeatMinimumDelay = jmin (autoRepeatSpeed, minimumDelayInMillisecs); } void Button::repeatTimerCallback() { if (needsRepainting) { getRepeatTimer().stopTimer(); updateState (0); needsRepainting = false; } else if (autoRepeatSpeed > 0 && (isKeyDown || (updateState (0) == buttonDown))) { int repeatSpeed = autoRepeatSpeed; if (autoRepeatMinimumDelay >= 0) { double timeHeldDown = jmin (1.0, getMillisecondsSinceButtonDown() / 4000.0); timeHeldDown *= timeHeldDown; repeatSpeed = repeatSpeed + (int) (timeHeldDown * (autoRepeatMinimumDelay - repeatSpeed)); } repeatSpeed = jmax (1, repeatSpeed); getRepeatTimer().startTimer (repeatSpeed); const uint32 now = Time::getApproximateMillisecondCounter(); const int numTimesToCallback = (now > lastTimeCallbackTime) ? jmax (1, (int) (now - lastTimeCallbackTime) / repeatSpeed) : 1; lastTimeCallbackTime = now; Component::SafePointer deletionWatcher (this); for (int i = numTimesToCallback; --i >= 0;) { internalClickCallback (ModifierKeys::getCurrentModifiers()); if (deletionWatcher == 0 || ! isDown()) return; } } else if (! needsToRelease) { getRepeatTimer().stopTimer(); } } Button::RepeatTimer& Button::getRepeatTimer() { if (repeatTimer == 0) repeatTimer = new RepeatTimer (*this); return *repeatTimer; } END_JUCE_NAMESPACE /*** End of inlined file: juce_Button.cpp ***/ /*** Start of inlined file: juce_DrawableButton.cpp ***/ BEGIN_JUCE_NAMESPACE DrawableButton::DrawableButton (const String& name, const DrawableButton::ButtonStyle buttonStyle) : Button (name), style (buttonStyle), edgeIndent (3) { if (buttonStyle == ImageOnButtonBackground) { backgroundOff = Colour (0xffbbbbff); backgroundOn = Colour (0xff3333ff); } else { backgroundOff = Colours::transparentBlack; backgroundOn = Colour (0xaabbbbff); } } DrawableButton::~DrawableButton() { deleteImages(); } void DrawableButton::deleteImages() { } void DrawableButton::setImages (const Drawable* normal, const Drawable* over, const Drawable* down, const Drawable* disabled, const Drawable* normalOn, const Drawable* overOn, const Drawable* downOn, const Drawable* disabledOn) { deleteImages(); jassert (normal != 0); // you really need to give it at least a normal image.. if (normal != 0) normalImage = normal->createCopy(); if (over != 0) overImage = over->createCopy(); if (down != 0) downImage = down->createCopy(); if (disabled != 0) disabledImage = disabled->createCopy(); if (normalOn != 0) normalImageOn = normalOn->createCopy(); if (overOn != 0) overImageOn = overOn->createCopy(); if (downOn != 0) downImageOn = downOn->createCopy(); if (disabledOn != 0) disabledImageOn = disabledOn->createCopy(); repaint(); } void DrawableButton::setButtonStyle (const DrawableButton::ButtonStyle newStyle) { if (style != newStyle) { style = newStyle; repaint(); } } void DrawableButton::setBackgroundColours (const Colour& toggledOffColour, const Colour& toggledOnColour) { if (backgroundOff != toggledOffColour || backgroundOn != toggledOnColour) { backgroundOff = toggledOffColour; backgroundOn = toggledOnColour; repaint(); } } const Colour& DrawableButton::getBackgroundColour() const throw() { return getToggleState() ? backgroundOn : backgroundOff; } void DrawableButton::setEdgeIndent (const int numPixelsIndent) { edgeIndent = numPixelsIndent; repaint(); } void DrawableButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) { Rectangle imageSpace; if (style == ImageOnButtonBackground) { const int insetX = getWidth() / 4; const int insetY = getHeight() / 4; imageSpace.setBounds (insetX, insetY, getWidth() - insetX * 2, getHeight() - insetY * 2); getLookAndFeel().drawButtonBackground (g, *this, getBackgroundColour(), isMouseOverButton, isButtonDown); } else { g.fillAll (getBackgroundColour()); const int textH = (style == ImageAboveTextLabel) ? jmin (16, proportionOfHeight (0.25f)) : 0; const int indentX = jmin (edgeIndent, proportionOfWidth (0.3f)); const int indentY = jmin (edgeIndent, proportionOfHeight (0.3f)); imageSpace.setBounds (indentX, indentY, getWidth() - indentX * 2, getHeight() - indentY * 2 - textH); if (textH > 0) { g.setFont ((float) textH); g.setColour (findColour (DrawableButton::textColourId) .withMultipliedAlpha (isEnabled() ? 1.0f : 0.4f)); g.drawFittedText (getButtonText(), 2, getHeight() - textH - 1, getWidth() - 4, textH, Justification::centred, 1); } } g.setImageResamplingQuality (Graphics::mediumResamplingQuality); g.setOpacity (1.0f); const Drawable* imageToDraw = 0; if (isEnabled()) { imageToDraw = getCurrentImage(); } else { imageToDraw = getToggleState() ? disabledImageOn : disabledImage; if (imageToDraw == 0) { g.setOpacity (0.4f); imageToDraw = getNormalImage(); } } if (imageToDraw != 0) { if (style == ImageRaw) { imageToDraw->draw (g, 1.0f); } else { imageToDraw->drawWithin (g, imageSpace.getX(), imageSpace.getY(), imageSpace.getWidth(), imageSpace.getHeight(), RectanglePlacement::centred, 1.0f); } } } const Drawable* DrawableButton::getCurrentImage() const throw() { if (isDown()) return getDownImage(); if (isOver()) return getOverImage(); return getNormalImage(); } const Drawable* DrawableButton::getNormalImage() const throw() { return (getToggleState() && normalImageOn != 0) ? normalImageOn : normalImage; } const Drawable* DrawableButton::getOverImage() const throw() { const Drawable* d = normalImage; if (getToggleState()) { if (overImageOn != 0) d = overImageOn; else if (normalImageOn != 0) d = normalImageOn; else if (overImage != 0) d = overImage; } else { if (overImage != 0) d = overImage; } return d; } const Drawable* DrawableButton::getDownImage() const throw() { const Drawable* d = normalImage; if (getToggleState()) { if (downImageOn != 0) d = downImageOn; else if (overImageOn != 0) d = overImageOn; else if (normalImageOn != 0) d = normalImageOn; else if (downImage != 0) d = downImage; else d = getOverImage(); } else { if (downImage != 0) d = downImage; else d = getOverImage(); } return d; } END_JUCE_NAMESPACE /*** End of inlined file: juce_DrawableButton.cpp ***/ /*** Start of inlined file: juce_HyperlinkButton.cpp ***/ BEGIN_JUCE_NAMESPACE HyperlinkButton::HyperlinkButton (const String& linkText, const URL& linkURL) : Button (linkText), url (linkURL), font (14.0f, Font::underlined), resizeFont (true), justification (Justification::centred) { setMouseCursor (MouseCursor::PointingHandCursor); setTooltip (linkURL.toString (false)); } HyperlinkButton::~HyperlinkButton() { } void HyperlinkButton::setFont (const Font& newFont, const bool resizeToMatchComponentHeight, const Justification& justificationType) { font = newFont; resizeFont = resizeToMatchComponentHeight; justification = justificationType; repaint(); } void HyperlinkButton::setURL (const URL& newURL) throw() { url = newURL; setTooltip (newURL.toString (false)); } const Font HyperlinkButton::getFontToUse() const { Font f (font); if (resizeFont) f.setHeight (getHeight() * 0.7f); return f; } void HyperlinkButton::changeWidthToFitText() { setSize (getFontToUse().getStringWidth (getName()) + 6, getHeight()); } void HyperlinkButton::colourChanged() { repaint(); } void HyperlinkButton::clicked() { if (url.isWellFormed()) url.launchInDefaultBrowser(); } void HyperlinkButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) { const Colour textColour (findColour (textColourId)); if (isEnabled()) g.setColour ((isMouseOverButton) ? textColour.darker ((isButtonDown) ? 1.3f : 0.4f) : textColour); else g.setColour (textColour.withMultipliedAlpha (0.4f)); g.setFont (getFontToUse()); g.drawText (getButtonText(), 2, 0, getWidth() - 2, getHeight(), justification.getOnlyHorizontalFlags() | Justification::verticallyCentred, true); } END_JUCE_NAMESPACE /*** End of inlined file: juce_HyperlinkButton.cpp ***/ /*** Start of inlined file: juce_ImageButton.cpp ***/ BEGIN_JUCE_NAMESPACE 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() { } void ImageButton::setImages (const bool resizeButtonNowToFitThisImage, const bool rescaleImagesWhenButtonSizeChanges, const bool preserveImageProportions, const Image& normalImage_, const float imageOpacityWhenNormal, const Colour& overlayColourWhenNormal, const Image& overImage_, const float imageOpacityWhenOver, const Colour& overlayColourWhenOver, const Image& downImage_, const float imageOpacityWhenDown, const Colour& overlayColourWhenDown, const float hitTestAlphaThreshold) { normalImage = normalImage_; overImage = overImage_; downImage = downImage_; if (resizeButtonNowToFitThisImage && normalImage.isValid()) { 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, roundToInt (255.0f * hitTestAlphaThreshold)); repaint(); } const Image ImageButton::getCurrentImage() const { if (isDown() || getToggleState()) return getDownImage(); if (isOver()) return getOverImage(); return getNormalImage(); } const Image ImageButton::getNormalImage() const { return normalImage; } const Image ImageButton::getOverImage() const { return overImage.isValid() ? overImage : normalImage; } const Image ImageButton::getDownImage() const { return downImage.isValid() ? downImage : getOverImage(); } void ImageButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) { if (! isEnabled()) { isMouseOverButton = false; isButtonDown = false; } Image im (getCurrentImage()); if (im.isValid()) { 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 = roundToInt (imageH / imRatio); newH = imageH; } else { newW = imageW; newH = roundToInt (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 im (getCurrentImage()); return im.isNull() || (imageW > 0 && imageH > 0 && alphaThreshold < im.getPixelAt (((x - imageX) * im.getWidth()) / imageW, ((y - imageY) * im.getHeight()) / imageH).getAlpha()); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ImageButton.cpp ***/ /*** Start of inlined file: juce_ShapeButton.cpp ***/ BEGIN_JUCE_NAMESPACE ShapeButton::ShapeButton (const String& text_, const Colour& normalColour_, const Colour& overColour_, const Colour& downColour_) : Button (text_), normalColour (normalColour_), overColour (overColour_), downColour (downColour_), maintainShapeProportions (false), outlineWidth (0.0f) { } ShapeButton::~ShapeButton() { } void ShapeButton::setColours (const Colour& newNormalColour, const Colour& newOverColour, const Colour& newDownColour) { normalColour = newNormalColour; overColour = newOverColour; downColour = newDownColour; } void ShapeButton::setOutline (const Colour& newOutlineColour, const float newOutlineWidth) { outlineColour = newOutlineColour; outlineWidth = newOutlineWidth; } void ShapeButton::setShape (const Path& newShape, const bool resizeNowToFitThisShape, const bool maintainShapeProportions_, const bool hasShadow) { shape = newShape; maintainShapeProportions = maintainShapeProportions_; shadow.setShadowProperties (3.0f, 0.5f, 0, 0); setComponentEffect ((hasShadow) ? &shadow : 0); if (resizeNowToFitThisShape) { Rectangle bounds (shape.getBounds()); if (hasShadow) bounds.expand (4.0f, 4.0f); shape.applyTransform (AffineTransform::translation (-bounds.getX(), -bounds.getY())); setSize (1 + (int) (bounds.getWidth() + outlineWidth), 1 + (int) (bounds.getHeight() + outlineWidth)); } } void ShapeButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) { if (! isEnabled()) { isMouseOverButton = false; isButtonDown = false; } g.setColour ((isButtonDown) ? downColour : (isMouseOverButton) ? overColour : normalColour); int w = getWidth(); int h = getHeight(); if (getComponentEffect() != 0) { w -= 4; h -= 4; } const float offset = (outlineWidth * 0.5f) + (isButtonDown ? 1.5f : 0.0f); const AffineTransform trans (shape.getTransformToScaleToFit (offset, offset, w - offset - outlineWidth, h - offset - outlineWidth, maintainShapeProportions)); g.fillPath (shape, trans); if (outlineWidth > 0.0f) { g.setColour (outlineColour); g.strokePath (shape, PathStrokeType (outlineWidth), trans); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ShapeButton.cpp ***/ /*** Start of inlined file: juce_TextButton.cpp ***/ BEGIN_JUCE_NAMESPACE TextButton::TextButton (const String& name, const String& toolTip) : Button (name) { setTooltip (toolTip); } TextButton::~TextButton() { } void TextButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) { getLookAndFeel().drawButtonBackground (g, *this, findColour (getToggleState() ? buttonOnColourId : buttonColourId), isMouseOverButton, isButtonDown); getLookAndFeel().drawButtonText (g, *this, isMouseOverButton, isButtonDown); } void TextButton::colourChanged() { repaint(); } const Font TextButton::getFont() { return Font (jmin (15.0f, getHeight() * 0.6f)); } void TextButton::changeWidthToFitText (const int newHeight) { if (newHeight >= 0) setSize (jmax (1, getWidth()), newHeight); setSize (getFont().getStringWidth (getButtonText()) + getHeight(), getHeight()); } END_JUCE_NAMESPACE /*** End of inlined file: juce_TextButton.cpp ***/ /*** Start of inlined file: juce_ToggleButton.cpp ***/ BEGIN_JUCE_NAMESPACE ToggleButton::ToggleButton (const String& buttonText) : Button (buttonText) { setClickingTogglesState (true); } ToggleButton::~ToggleButton() { } void ToggleButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) { getLookAndFeel().drawToggleButton (g, *this, isMouseOverButton, isButtonDown); } void ToggleButton::changeWidthToFitText() { getLookAndFeel().changeToggleButtonWidthToFitText (*this); } void ToggleButton::colourChanged() { repaint(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ToggleButton.cpp ***/ /*** Start of inlined file: juce_ToolbarButton.cpp ***/ BEGIN_JUCE_NAMESPACE ToolbarButton::ToolbarButton (const int itemId_, const String& buttonText, Drawable* const normalImage_, Drawable* const toggledOnImage_) : ToolbarItemComponent (itemId_, buttonText, true), normalImage (normalImage_), toggledOnImage (toggledOnImage_) { jassert (normalImage_ != 0); } ToolbarButton::~ToolbarButton() { } bool ToolbarButton::getToolbarItemSizes (int toolbarDepth, bool /*isToolbarVertical*/, int& preferredSize, int& minSize, int& maxSize) { preferredSize = minSize = maxSize = toolbarDepth; return true; } void ToolbarButton::paintButtonArea (Graphics& g, int width, int height, bool /*isMouseOver*/, bool /*isMouseDown*/) { Drawable* d = normalImage; if (getToggleState() && toggledOnImage != 0) d = toggledOnImage; if (! isEnabled()) { Image im (Image::ARGB, width, height, true); { Graphics g2 (im); d->drawWithin (g2, 0, 0, width, height, RectanglePlacement::centred, 1.0f); } im.desaturate(); g.drawImageAt (im, 0, 0); } else { d->drawWithin (g, 0, 0, width, height, RectanglePlacement::centred, 1.0f); } } void ToolbarButton::contentAreaChanged (const Rectangle&) { } END_JUCE_NAMESPACE /*** End of inlined file: juce_ToolbarButton.cpp ***/ /*** Start of inlined file: juce_CodeDocument.cpp ***/ BEGIN_JUCE_NAMESPACE class CodeDocumentLine { public: CodeDocumentLine (const juce_wchar* const line_, const int lineLength_, const int numNewLineChars, const int lineStartInFile_) : line (line_, lineLength_), lineStartInFile (lineStartInFile_), lineLength (lineLength_), lineLengthWithoutNewLines (lineLength_ - numNewLineChars) { } ~CodeDocumentLine() { } static void createLines (Array & newLines, const String& text) { const juce_wchar* const t = text; int pos = 0; while (t [pos] != 0) { const int startOfLine = pos; int numNewLineChars = 0; while (t[pos] != 0) { if (t[pos] == '\r') { ++numNewLineChars; ++pos; if (t[pos] == '\n') { ++numNewLineChars; ++pos; } break; } if (t[pos] == '\n') { ++numNewLineChars; ++pos; break; } ++pos; } newLines.add (new CodeDocumentLine (t + startOfLine, pos - startOfLine, numNewLineChars, startOfLine)); } jassert (pos == text.length()); } bool endsWithLineBreak() const throw() { return lineLengthWithoutNewLines != lineLength; } void updateLength() throw() { lineLengthWithoutNewLines = lineLength = line.length(); while (lineLengthWithoutNewLines > 0 && (line [lineLengthWithoutNewLines - 1] == '\n' || line [lineLengthWithoutNewLines - 1] == '\r')) { --lineLengthWithoutNewLines; } } String line; int lineStartInFile, lineLength, lineLengthWithoutNewLines; }; CodeDocument::Iterator::Iterator (CodeDocument* const document_) : document (document_), currentLine (document_->lines[0]), line (0), position (0) { } CodeDocument::Iterator::Iterator (const CodeDocument::Iterator& other) : document (other.document), currentLine (other.currentLine), line (other.line), position (other.position) { } CodeDocument::Iterator& CodeDocument::Iterator::operator= (const CodeDocument::Iterator& other) throw() { document = other.document; currentLine = other.currentLine; line = other.line; position = other.position; return *this; } CodeDocument::Iterator::~Iterator() throw() { } juce_wchar CodeDocument::Iterator::nextChar() { if (currentLine == 0) return 0; jassert (currentLine == document->lines.getUnchecked (line)); const juce_wchar result = currentLine->line [position - currentLine->lineStartInFile]; if (++position >= currentLine->lineStartInFile + currentLine->lineLength) { ++line; currentLine = document->lines [line]; } return result; } void CodeDocument::Iterator::skip() { if (currentLine != 0) { jassert (currentLine == document->lines.getUnchecked (line)); if (++position >= currentLine->lineStartInFile + currentLine->lineLength) { ++line; currentLine = document->lines [line]; } } } void CodeDocument::Iterator::skipToEndOfLine() { if (currentLine != 0) { jassert (currentLine == document->lines.getUnchecked (line)); ++line; currentLine = document->lines [line]; if (currentLine != 0) position = currentLine->lineStartInFile; else position = document->getNumCharacters(); } } juce_wchar CodeDocument::Iterator::peekNextChar() const { if (currentLine == 0) return 0; jassert (currentLine == document->lines.getUnchecked (line)); return const_cast (currentLine->line) [position - currentLine->lineStartInFile]; } void CodeDocument::Iterator::skipWhitespace() { while (CharacterFunctions::isWhitespace (peekNextChar())) skip(); } bool CodeDocument::Iterator::isEOF() const throw() { return currentLine == 0; } CodeDocument::Position::Position() throw() : owner (0), characterPos (0), line (0), indexInLine (0), positionMaintained (false) { } CodeDocument::Position::Position (const CodeDocument* const ownerDocument, const int line_, const int indexInLine_) throw() : owner (const_cast (ownerDocument)), characterPos (0), line (line_), indexInLine (indexInLine_), positionMaintained (false) { setLineAndIndex (line_, indexInLine_); } CodeDocument::Position::Position (const CodeDocument* const ownerDocument, const int characterPos_) throw() : owner (const_cast (ownerDocument)), positionMaintained (false) { setPosition (characterPos_); } CodeDocument::Position::Position (const Position& other) throw() : owner (other.owner), characterPos (other.characterPos), line (other.line), indexInLine (other.indexInLine), positionMaintained (false) { jassert (*this == other); } CodeDocument::Position::~Position() { setPositionMaintained (false); } CodeDocument::Position& CodeDocument::Position::operator= (const Position& other) { if (this != &other) { const bool wasPositionMaintained = positionMaintained; if (owner != other.owner) setPositionMaintained (false); owner = other.owner; line = other.line; indexInLine = other.indexInLine; characterPos = other.characterPos; setPositionMaintained (wasPositionMaintained); jassert (*this == other); } return *this; } bool CodeDocument::Position::operator== (const Position& other) const throw() { jassert ((characterPos == other.characterPos) == (line == other.line && indexInLine == other.indexInLine)); return characterPos == other.characterPos && line == other.line && indexInLine == other.indexInLine && owner == other.owner; } bool CodeDocument::Position::operator!= (const Position& other) const throw() { return ! operator== (other); } void CodeDocument::Position::setLineAndIndex (const int newLine, const int newIndexInLine) { jassert (owner != 0); if (owner->lines.size() == 0) { line = 0; indexInLine = 0; characterPos = 0; } else { if (newLine >= owner->lines.size()) { line = owner->lines.size() - 1; CodeDocumentLine* const l = owner->lines.getUnchecked (line); jassert (l != 0); indexInLine = l->lineLengthWithoutNewLines; characterPos = l->lineStartInFile + indexInLine; } else { line = jmax (0, newLine); CodeDocumentLine* const l = owner->lines.getUnchecked (line); jassert (l != 0); if (l->lineLengthWithoutNewLines > 0) indexInLine = jlimit (0, l->lineLengthWithoutNewLines, newIndexInLine); else indexInLine = 0; characterPos = l->lineStartInFile + indexInLine; } } } void CodeDocument::Position::setPosition (const int newPosition) { jassert (owner != 0); line = 0; indexInLine = 0; characterPos = 0; if (newPosition > 0) { int lineStart = 0; int lineEnd = owner->lines.size(); for (;;) { if (lineEnd - lineStart < 4) { for (int i = lineStart; i < lineEnd; ++i) { CodeDocumentLine* const l = owner->lines.getUnchecked (i); int index = newPosition - l->lineStartInFile; if (index >= 0 && (index < l->lineLength || i == lineEnd - 1)) { line = i; indexInLine = jmin (l->lineLengthWithoutNewLines, index); characterPos = l->lineStartInFile + indexInLine; } } break; } else { const int midIndex = (lineStart + lineEnd + 1) / 2; CodeDocumentLine* const mid = owner->lines.getUnchecked (midIndex); if (newPosition >= mid->lineStartInFile) lineStart = midIndex; else lineEnd = midIndex; } } } } void CodeDocument::Position::moveBy (int characterDelta) { jassert (owner != 0); if (characterDelta == 1) { setPosition (getPosition()); // If moving right, make sure we don't get stuck between the \r and \n characters.. if (line < owner->lines.size()) { CodeDocumentLine* const l = owner->lines.getUnchecked (line); if (indexInLine + characterDelta < l->lineLength && indexInLine + characterDelta >= l->lineLengthWithoutNewLines + 1) ++characterDelta; } } setPosition (characterPos + characterDelta); } const CodeDocument::Position CodeDocument::Position::movedBy (const int characterDelta) const { CodeDocument::Position p (*this); p.moveBy (characterDelta); return p; } const CodeDocument::Position CodeDocument::Position::movedByLines (const int deltaLines) const { CodeDocument::Position p (*this); p.setLineAndIndex (getLineNumber() + deltaLines, getIndexInLine()); return p; } const juce_wchar CodeDocument::Position::getCharacter() const { const CodeDocumentLine* const l = owner->lines [line]; return l == 0 ? 0 : l->line [getIndexInLine()]; } const String CodeDocument::Position::getLineText() const { const CodeDocumentLine* const l = owner->lines [line]; return l == 0 ? String::empty : l->line; } void CodeDocument::Position::setPositionMaintained (const bool isMaintained) { if (isMaintained != positionMaintained) { positionMaintained = isMaintained; if (owner != 0) { if (isMaintained) { jassert (! owner->positionsToMaintain.contains (this)); owner->positionsToMaintain.add (this); } else { // If this happens, you may have deleted the document while there are Position objects that are still using it... jassert (owner->positionsToMaintain.contains (this)); owner->positionsToMaintain.removeValue (this); } } } } CodeDocument::CodeDocument() : undoManager (std::numeric_limits::max(), 10000), currentActionIndex (0), indexOfSavedState (-1), maximumLineLength (-1), newLineChars ("\r\n") { } CodeDocument::~CodeDocument() { } const String CodeDocument::getAllContent() const { return getTextBetween (Position (this, 0), Position (this, lines.size(), 0)); } const String CodeDocument::getTextBetween (const Position& start, const Position& end) const { if (end.getPosition() <= start.getPosition()) return String::empty; const int startLine = start.getLineNumber(); const int endLine = end.getLineNumber(); if (startLine == endLine) { CodeDocumentLine* const line = lines [startLine]; return (line == 0) ? String::empty : line->line.substring (start.getIndexInLine(), end.getIndexInLine()); } String result; result.preallocateStorage (end.getPosition() - start.getPosition() + 4); String::Concatenator concatenator (result); const int maxLine = jmin (lines.size() - 1, endLine); for (int i = jmax (0, startLine); i <= maxLine; ++i) { const CodeDocumentLine* line = lines.getUnchecked(i); int len = line->lineLength; if (i == startLine) { const int index = start.getIndexInLine(); concatenator.append (line->line.substring (index, len)); } else if (i == endLine) { len = end.getIndexInLine(); concatenator.append (line->line.substring (0, len)); } else { concatenator.append (line->line); } } return result; } int CodeDocument::getNumCharacters() const throw() { const CodeDocumentLine* const lastLine = lines.getLast(); return (lastLine == 0) ? 0 : lastLine->lineStartInFile + lastLine->lineLength; } const String CodeDocument::getLine (const int lineIndex) const throw() { const CodeDocumentLine* const line = lines [lineIndex]; return (line == 0) ? String::empty : line->line; } int CodeDocument::getMaximumLineLength() throw() { if (maximumLineLength < 0) { maximumLineLength = 0; for (int i = lines.size(); --i >= 0;) maximumLineLength = jmax (maximumLineLength, lines.getUnchecked(i)->lineLength); } return maximumLineLength; } void CodeDocument::deleteSection (const Position& startPosition, const Position& endPosition) { remove (startPosition.getPosition(), endPosition.getPosition(), true); } void CodeDocument::insertText (const Position& position, const String& text) { insert (text, position.getPosition(), true); } void CodeDocument::replaceAllContent (const String& newContent) { remove (0, getNumCharacters(), true); insert (newContent, 0, true); } bool CodeDocument::loadFromStream (InputStream& stream) { replaceAllContent (stream.readEntireStreamAsString()); setSavePoint(); clearUndoHistory(); return true; } bool CodeDocument::writeToStream (OutputStream& stream) { for (int i = 0; i < lines.size(); ++i) { String temp (lines.getUnchecked(i)->line); // use a copy to avoid bloating the memory footprint of the stored string. const char* utf8 = temp.toUTF8(); if (! stream.write (utf8, (int) strlen (utf8))) return false; } return true; } void CodeDocument::setNewLineCharacters (const String& newLine) throw() { jassert (newLine == "\r\n" || newLine == "\n" || newLine == "\r"); newLineChars = newLine; } void CodeDocument::newTransaction() { undoManager.beginNewTransaction (String::empty); } void CodeDocument::undo() { newTransaction(); undoManager.undo(); } void CodeDocument::redo() { undoManager.redo(); } void CodeDocument::clearUndoHistory() { undoManager.clearUndoHistory(); } void CodeDocument::setSavePoint() throw() { indexOfSavedState = currentActionIndex; } bool CodeDocument::hasChangedSinceSavePoint() const throw() { return currentActionIndex != indexOfSavedState; } static int getCodeCharacterCategory (const juce_wchar character) throw() { return (CharacterFunctions::isLetterOrDigit (character) || character == '_') ? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1); } const CodeDocument::Position CodeDocument::findWordBreakAfter (const Position& position) const throw() { Position p (position); const int maxDistance = 256; int i = 0; while (i < maxDistance && CharacterFunctions::isWhitespace (p.getCharacter()) && (i == 0 || (p.getCharacter() != '\n' && p.getCharacter() != '\r'))) { ++i; p.moveBy (1); } if (i == 0) { const int type = getCodeCharacterCategory (p.getCharacter()); while (i < maxDistance && type == getCodeCharacterCategory (p.getCharacter())) { ++i; p.moveBy (1); } while (i < maxDistance && CharacterFunctions::isWhitespace (p.getCharacter()) && (i == 0 || (p.getCharacter() != '\n' && p.getCharacter() != '\r'))) { ++i; p.moveBy (1); } } return p; } const CodeDocument::Position CodeDocument::findWordBreakBefore (const Position& position) const throw() { Position p (position); const int maxDistance = 256; int i = 0; bool stoppedAtLineStart = false; while (i < maxDistance) { const juce_wchar c = p.movedBy (-1).getCharacter(); if (c == '\r' || c == '\n') { stoppedAtLineStart = true; if (i > 0) break; } if (! CharacterFunctions::isWhitespace (c)) break; p.moveBy (-1); ++i; } if (i < maxDistance && ! stoppedAtLineStart) { const int type = getCodeCharacterCategory (p.movedBy (-1).getCharacter()); while (i < maxDistance && type == getCodeCharacterCategory (p.movedBy (-1).getCharacter())) { p.moveBy (-1); ++i; } } return p; } void CodeDocument::checkLastLineStatus() { while (lines.size() > 0 && lines.getLast()->lineLength == 0 && (lines.size() == 1 || ! lines.getUnchecked (lines.size() - 2)->endsWithLineBreak())) { // remove any empty lines at the end if the preceding line doesn't end in a newline. lines.removeLast(); } const CodeDocumentLine* const lastLine = lines.getLast(); if (lastLine != 0 && lastLine->endsWithLineBreak()) { // check that there's an empty line at the end if the preceding one ends in a newline.. lines.add (new CodeDocumentLine (String::empty, 0, 0, lastLine->lineStartInFile + lastLine->lineLength)); } } void CodeDocument::addListener (CodeDocument::Listener* const listener) throw() { listeners.add (listener); } void CodeDocument::removeListener (CodeDocument::Listener* const listener) throw() { listeners.remove (listener); } void CodeDocument::sendListenerChangeMessage (const int startLine, const int endLine) { Position startPos (this, startLine, 0); Position endPos (this, endLine, 0); listeners.call (&CodeDocument::Listener::codeDocumentChanged, startPos, endPos); } class CodeDocumentInsertAction : public UndoableAction { CodeDocument& owner; const String text; int insertPos; CodeDocumentInsertAction (const CodeDocumentInsertAction&); CodeDocumentInsertAction& operator= (const CodeDocumentInsertAction&); public: CodeDocumentInsertAction (CodeDocument& owner_, const String& text_, const int insertPos_) throw() : owner (owner_), text (text_), insertPos (insertPos_) { } ~CodeDocumentInsertAction() {} bool perform() { owner.currentActionIndex++; owner.insert (text, insertPos, false); return true; } bool undo() { owner.currentActionIndex--; owner.remove (insertPos, insertPos + text.length(), false); return true; } int getSizeInUnits() { return text.length() + 32; } }; void CodeDocument::insert (const String& text, const int insertPos, const bool undoable) { if (text.isEmpty()) return; if (undoable) { undoManager.perform (new CodeDocumentInsertAction (*this, text, insertPos)); } else { Position pos (this, insertPos); const int firstAffectedLine = pos.getLineNumber(); int lastAffectedLine = firstAffectedLine + 1; CodeDocumentLine* const firstLine = lines [firstAffectedLine]; String textInsideOriginalLine (text); if (firstLine != 0) { const int index = pos.getIndexInLine(); textInsideOriginalLine = firstLine->line.substring (0, index) + textInsideOriginalLine + firstLine->line.substring (index); } maximumLineLength = -1; Array newLines; CodeDocumentLine::createLines (newLines, textInsideOriginalLine); jassert (newLines.size() > 0); CodeDocumentLine* const newFirstLine = newLines.getUnchecked (0); newFirstLine->lineStartInFile = firstLine != 0 ? firstLine->lineStartInFile : 0; lines.set (firstAffectedLine, newFirstLine); if (newLines.size() > 1) { for (int i = 1; i < newLines.size(); ++i) { CodeDocumentLine* const l = newLines.getUnchecked (i); lines.insert (firstAffectedLine + i, l); } lastAffectedLine = lines.size(); } int i, lineStart = newFirstLine->lineStartInFile; for (i = firstAffectedLine; i < lines.size(); ++i) { CodeDocumentLine* const l = lines.getUnchecked (i); l->lineStartInFile = lineStart; lineStart += l->lineLength; } checkLastLineStatus(); const int newTextLength = text.length(); for (i = 0; i < positionsToMaintain.size(); ++i) { CodeDocument::Position* const p = positionsToMaintain.getUnchecked(i); if (p->getPosition() >= insertPos) p->setPosition (p->getPosition() + newTextLength); } sendListenerChangeMessage (firstAffectedLine, lastAffectedLine); } } class CodeDocumentDeleteAction : public UndoableAction { CodeDocument& owner; int startPos, endPos; String removedText; CodeDocumentDeleteAction (const CodeDocumentDeleteAction&); CodeDocumentDeleteAction& operator= (const CodeDocumentDeleteAction&); public: CodeDocumentDeleteAction (CodeDocument& owner_, const int startPos_, const int endPos_) throw() : owner (owner_), startPos (startPos_), endPos (endPos_) { removedText = owner.getTextBetween (CodeDocument::Position (&owner, startPos), CodeDocument::Position (&owner, endPos)); } ~CodeDocumentDeleteAction() {} bool perform() { owner.currentActionIndex++; owner.remove (startPos, endPos, false); return true; } bool undo() { owner.currentActionIndex--; owner.insert (removedText, startPos, false); return true; } int getSizeInUnits() { return removedText.length() + 32; } }; void CodeDocument::remove (const int startPos, const int endPos, const bool undoable) { if (endPos <= startPos) return; if (undoable) { undoManager.perform (new CodeDocumentDeleteAction (*this, startPos, endPos)); } else { Position startPosition (this, startPos); Position endPosition (this, endPos); maximumLineLength = -1; const int firstAffectedLine = startPosition.getLineNumber(); const int endLine = endPosition.getLineNumber(); int lastAffectedLine = firstAffectedLine + 1; CodeDocumentLine* const firstLine = lines.getUnchecked (firstAffectedLine); if (firstAffectedLine == endLine) { firstLine->line = firstLine->line.substring (0, startPosition.getIndexInLine()) + firstLine->line.substring (endPosition.getIndexInLine()); firstLine->updateLength(); } else { lastAffectedLine = lines.size(); CodeDocumentLine* const lastLine = lines.getUnchecked (endLine); jassert (lastLine != 0); firstLine->line = firstLine->line.substring (0, startPosition.getIndexInLine()) + lastLine->line.substring (endPosition.getIndexInLine()); firstLine->updateLength(); int numLinesToRemove = endLine - firstAffectedLine; lines.removeRange (firstAffectedLine + 1, numLinesToRemove); } int i; for (i = firstAffectedLine + 1; i < lines.size(); ++i) { CodeDocumentLine* const l = lines.getUnchecked (i); const CodeDocumentLine* const previousLine = lines.getUnchecked (i - 1); l->lineStartInFile = previousLine->lineStartInFile + previousLine->lineLength; } checkLastLineStatus(); const int totalChars = getNumCharacters(); for (i = 0; i < positionsToMaintain.size(); ++i) { CodeDocument::Position* p = positionsToMaintain.getUnchecked(i); if (p->getPosition() > startPosition.getPosition()) p->setPosition (jmax (startPos, p->getPosition() + startPos - endPos)); if (p->getPosition() > totalChars) p->setPosition (totalChars); } sendListenerChangeMessage (firstAffectedLine, lastAffectedLine); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_CodeDocument.cpp ***/ /*** Start of inlined file: juce_CodeEditorComponent.cpp ***/ BEGIN_JUCE_NAMESPACE class CodeEditorComponent::CaretComponent : public Component, public Timer { public: CaretComponent (CodeEditorComponent& owner_) : owner (owner_) { setAlwaysOnTop (true); setInterceptsMouseClicks (false, false); } ~CaretComponent() { } void paint (Graphics& g) { g.fillAll (findColour (CodeEditorComponent::caretColourId)); } void timerCallback() { setVisible (shouldBeShown() && ! isVisible()); } void updatePosition() { startTimer (400); setVisible (shouldBeShown()); setBounds (owner.getCharacterBounds (owner.getCaretPos()).withWidth (2)); } private: CodeEditorComponent& owner; CaretComponent (const CaretComponent&); CaretComponent& operator= (const CaretComponent&); bool shouldBeShown() const { return owner.hasKeyboardFocus (true); } }; class CodeEditorComponent::CodeEditorLine { public: CodeEditorLine() throw() : highlightColumnStart (0), highlightColumnEnd (0) { } ~CodeEditorLine() throw() { } bool update (CodeDocument& document, int lineNum, CodeDocument::Iterator& source, CodeTokeniser* analyser, const int spacesPerTab, const CodeDocument::Position& selectionStart, const CodeDocument::Position& selectionEnd) { Array newTokens; newTokens.ensureStorageAllocated (8); if (analyser == 0) { newTokens.add (SyntaxToken (document.getLine (lineNum), -1)); } else if (lineNum < document.getNumLines()) { const CodeDocument::Position pos (&document, lineNum, 0); createTokens (pos.getPosition(), pos.getLineText(), source, analyser, newTokens); } replaceTabsWithSpaces (newTokens, spacesPerTab); int newHighlightStart = 0; int newHighlightEnd = 0; if (selectionStart.getLineNumber() <= lineNum && selectionEnd.getLineNumber() >= lineNum) { const String line (document.getLine (lineNum)); CodeDocument::Position lineStart (&document, lineNum, 0), lineEnd (&document, lineNum + 1, 0); newHighlightStart = indexToColumn (jmax (0, selectionStart.getPosition() - lineStart.getPosition()), line, spacesPerTab); newHighlightEnd = indexToColumn (jmin (lineEnd.getPosition() - lineStart.getPosition(), selectionEnd.getPosition() - lineStart.getPosition()), line, spacesPerTab); } if (newHighlightStart != highlightColumnStart || newHighlightEnd != highlightColumnEnd) { highlightColumnStart = newHighlightStart; highlightColumnEnd = newHighlightEnd; } else { if (tokens.size() == newTokens.size()) { bool allTheSame = true; for (int i = newTokens.size(); --i >= 0;) { if (tokens.getReference(i) != newTokens.getReference(i)) { allTheSame = false; break; } } if (allTheSame) return false; } } tokens.swapWithArray (newTokens); return true; } void draw (CodeEditorComponent& owner, Graphics& g, const Font& font, float x, const int y, const int baselineOffset, const int lineHeight, const Colour& highlightColour) const { if (highlightColumnStart < highlightColumnEnd) { g.setColour (highlightColour); g.fillRect (roundToInt (x + highlightColumnStart * owner.getCharWidth()), y, roundToInt ((highlightColumnEnd - highlightColumnStart) * owner.getCharWidth()), lineHeight); } int lastType = std::numeric_limits::min(); for (int i = 0; i < tokens.size(); ++i) { SyntaxToken& token = tokens.getReference(i); if (lastType != token.tokenType) { lastType = token.tokenType; g.setColour (owner.getColourForTokenType (lastType)); } g.drawSingleLineText (token.text, roundToInt (x), y + baselineOffset); if (i < tokens.size() - 1) { if (token.width < 0) token.width = font.getStringWidthFloat (token.text); x += token.width; } } } private: struct SyntaxToken { String text; int tokenType; float width; SyntaxToken (const String& text_, const int type) throw() : text (text_), tokenType (type), width (-1.0f) { } bool operator!= (const SyntaxToken& other) const throw() { return text != other.text || tokenType != other.tokenType; } }; Array tokens; int highlightColumnStart, highlightColumnEnd; static void createTokens (int startPosition, const String& lineText, CodeDocument::Iterator& source, CodeTokeniser* analyser, Array & newTokens) { CodeDocument::Iterator lastIterator (source); const int lineLength = lineText.length(); for (;;) { int tokenType = analyser->readNextToken (source); int tokenStart = lastIterator.getPosition(); int tokenEnd = source.getPosition(); if (tokenEnd <= tokenStart) break; tokenEnd -= startPosition; if (tokenEnd > 0) { tokenStart -= startPosition; newTokens.add (SyntaxToken (lineText.substring (jmax (0, tokenStart), tokenEnd), tokenType)); if (tokenEnd >= lineLength) break; } lastIterator = source; } source = lastIterator; } static void replaceTabsWithSpaces (Array & tokens, const int spacesPerTab) { int x = 0; for (int i = 0; i < tokens.size(); ++i) { SyntaxToken& t = tokens.getReference(i); for (;;) { int tabPos = t.text.indexOfChar ('\t'); if (tabPos < 0) break; const int spacesNeeded = spacesPerTab - ((tabPos + x) % spacesPerTab); t.text = t.text.replaceSection (tabPos, 1, String::repeatedString (" ", spacesNeeded)); } x += t.text.length(); } } int indexToColumn (int index, const String& line, int spacesPerTab) const throw() { jassert (index <= line.length()); int col = 0; for (int i = 0; i < index; ++i) { if (line[i] != '\t') ++col; else col += spacesPerTab - (col % spacesPerTab); } return col; } }; CodeEditorComponent::CodeEditorComponent (CodeDocument& document_, CodeTokeniser* const codeTokeniser_) : document (document_), firstLineOnScreen (0), gutter (5), spacesPerTab (4), lineHeight (0), linesOnScreen (0), columnsOnScreen (0), scrollbarThickness (16), columnToTryToMaintain (-1), useSpacesForTabs (false), xOffset (0), codeTokeniser (codeTokeniser_) { caretPos = CodeDocument::Position (&document_, 0, 0); caretPos.setPositionMaintained (true); selectionStart = CodeDocument::Position (&document_, 0, 0); selectionStart.setPositionMaintained (true); selectionEnd = CodeDocument::Position (&document_, 0, 0); selectionEnd.setPositionMaintained (true); setOpaque (true); setMouseCursor (MouseCursor (MouseCursor::IBeamCursor)); setWantsKeyboardFocus (true); addAndMakeVisible (verticalScrollBar = new ScrollBar (true)); verticalScrollBar->setSingleStepSize (1.0); addAndMakeVisible (horizontalScrollBar = new ScrollBar (false)); horizontalScrollBar->setSingleStepSize (1.0); addAndMakeVisible (caret = new CaretComponent (*this)); Font f (12.0f); f.setTypefaceName (Font::getDefaultMonospacedFontName()); setFont (f); resetToDefaultColours(); verticalScrollBar->addListener (this); horizontalScrollBar->addListener (this); document.addListener (this); } CodeEditorComponent::~CodeEditorComponent() { document.removeListener (this); deleteAllChildren(); } void CodeEditorComponent::loadContent (const String& newContent) { clearCachedIterators (0); document.replaceAllContent (newContent); document.clearUndoHistory(); document.setSavePoint(); caretPos.setPosition (0); selectionStart.setPosition (0); selectionEnd.setPosition (0); scrollToLine (0); } bool CodeEditorComponent::isTextInputActive() const { return true; } void CodeEditorComponent::codeDocumentChanged (const CodeDocument::Position& affectedTextStart, const CodeDocument::Position& affectedTextEnd) { clearCachedIterators (affectedTextStart.getLineNumber()); triggerAsyncUpdate(); caret->updatePosition(); columnToTryToMaintain = -1; if (affectedTextEnd.getPosition() >= selectionStart.getPosition() && affectedTextStart.getPosition() <= selectionEnd.getPosition()) deselectAll(); if (caretPos.getPosition() > affectedTextEnd.getPosition() || caretPos.getPosition() < affectedTextStart.getPosition()) moveCaretTo (affectedTextStart, false); updateScrollBars(); } void CodeEditorComponent::resized() { linesOnScreen = (getHeight() - scrollbarThickness) / lineHeight; columnsOnScreen = (int) ((getWidth() - scrollbarThickness) / charWidth); lines.clear(); rebuildLineTokens(); caret->updatePosition(); verticalScrollBar->setBounds (getWidth() - scrollbarThickness, 0, scrollbarThickness, getHeight() - scrollbarThickness); horizontalScrollBar->setBounds (gutter, getHeight() - scrollbarThickness, getWidth() - scrollbarThickness - gutter, scrollbarThickness); updateScrollBars(); } void CodeEditorComponent::paint (Graphics& g) { handleUpdateNowIfNeeded(); g.fillAll (findColour (CodeEditorComponent::backgroundColourId)); g.reduceClipRegion (gutter, 0, verticalScrollBar->getX() - gutter, horizontalScrollBar->getY()); g.setFont (font); const int baselineOffset = (int) font.getAscent(); const Colour defaultColour (findColour (CodeEditorComponent::defaultTextColourId)); const Colour highlightColour (findColour (CodeEditorComponent::highlightColourId)); const Rectangle clip (g.getClipBounds()); const int firstLineToDraw = jmax (0, clip.getY() / lineHeight); const int lastLineToDraw = jmin (lines.size(), clip.getBottom() / lineHeight + 1); for (int j = firstLineToDraw; j < lastLineToDraw; ++j) { lines.getUnchecked(j)->draw (*this, g, font, (float) (gutter - xOffset * charWidth), lineHeight * j, baselineOffset, lineHeight, highlightColour); } } void CodeEditorComponent::setScrollbarThickness (const int thickness) { if (scrollbarThickness != thickness) { scrollbarThickness = thickness; resized(); } } void CodeEditorComponent::handleAsyncUpdate() { rebuildLineTokens(); } void CodeEditorComponent::rebuildLineTokens() { cancelPendingUpdate(); const int numNeeded = linesOnScreen + 1; int minLineToRepaint = numNeeded; int maxLineToRepaint = 0; if (numNeeded != lines.size()) { lines.clear(); for (int i = numNeeded; --i >= 0;) lines.add (new CodeEditorLine()); minLineToRepaint = 0; maxLineToRepaint = numNeeded; } jassert (numNeeded == lines.size()); CodeDocument::Iterator source (&document); getIteratorForPosition (CodeDocument::Position (&document, firstLineOnScreen, 0).getPosition(), source); for (int i = 0; i < numNeeded; ++i) { CodeEditorLine* const line = lines.getUnchecked(i); if (line->update (document, firstLineOnScreen + i, source, codeTokeniser, spacesPerTab, selectionStart, selectionEnd)) { minLineToRepaint = jmin (minLineToRepaint, i); maxLineToRepaint = jmax (maxLineToRepaint, i); } } if (minLineToRepaint <= maxLineToRepaint) { repaint (gutter, lineHeight * minLineToRepaint - 1, verticalScrollBar->getX() - gutter, lineHeight * (1 + maxLineToRepaint - minLineToRepaint) + 2); } } void CodeEditorComponent::moveCaretTo (const CodeDocument::Position& newPos, const bool highlighting) { caretPos = newPos; columnToTryToMaintain = -1; if (highlighting) { if (dragType == notDragging) { if (abs (caretPos.getPosition() - selectionStart.getPosition()) < abs (caretPos.getPosition() - selectionEnd.getPosition())) dragType = draggingSelectionStart; else dragType = draggingSelectionEnd; } if (dragType == draggingSelectionStart) { selectionStart = caretPos; if (selectionEnd.getPosition() < selectionStart.getPosition()) { const CodeDocument::Position temp (selectionStart); selectionStart = selectionEnd; selectionEnd = temp; dragType = draggingSelectionEnd; } } else { selectionEnd = caretPos; if (selectionEnd.getPosition() < selectionStart.getPosition()) { const CodeDocument::Position temp (selectionStart); selectionStart = selectionEnd; selectionEnd = temp; dragType = draggingSelectionStart; } } triggerAsyncUpdate(); } else { deselectAll(); } caret->updatePosition(); scrollToKeepCaretOnScreen(); updateScrollBars(); } void CodeEditorComponent::deselectAll() { if (selectionStart != selectionEnd) triggerAsyncUpdate(); selectionStart = caretPos; selectionEnd = caretPos; } void CodeEditorComponent::updateScrollBars() { verticalScrollBar->setRangeLimits (0, jmax (document.getNumLines(), firstLineOnScreen + linesOnScreen)); verticalScrollBar->setCurrentRange (firstLineOnScreen, linesOnScreen); horizontalScrollBar->setRangeLimits (0, jmax ((double) document.getMaximumLineLength(), xOffset + columnsOnScreen)); horizontalScrollBar->setCurrentRange (xOffset, columnsOnScreen); } void CodeEditorComponent::scrollToLineInternal (int newFirstLineOnScreen) { newFirstLineOnScreen = jlimit (0, jmax (0, document.getNumLines() - 1), newFirstLineOnScreen); if (newFirstLineOnScreen != firstLineOnScreen) { firstLineOnScreen = newFirstLineOnScreen; caret->updatePosition(); updateCachedIterators (firstLineOnScreen); triggerAsyncUpdate(); } } void CodeEditorComponent::scrollToColumnInternal (double column) { const double newOffset = jlimit (0.0, document.getMaximumLineLength() + 3.0, column); if (xOffset != newOffset) { xOffset = newOffset; caret->updatePosition(); repaint(); } } void CodeEditorComponent::scrollToLine (int newFirstLineOnScreen) { scrollToLineInternal (newFirstLineOnScreen); updateScrollBars(); } void CodeEditorComponent::scrollToColumn (int newFirstColumnOnScreen) { scrollToColumnInternal (newFirstColumnOnScreen); updateScrollBars(); } void CodeEditorComponent::scrollBy (int deltaLines) { scrollToLine (firstLineOnScreen + deltaLines); } void CodeEditorComponent::scrollToKeepCaretOnScreen() { if (caretPos.getLineNumber() < firstLineOnScreen) scrollBy (caretPos.getLineNumber() - firstLineOnScreen); else if (caretPos.getLineNumber() >= firstLineOnScreen + linesOnScreen) scrollBy (caretPos.getLineNumber() - (firstLineOnScreen + linesOnScreen - 1)); const int column = indexToColumn (caretPos.getLineNumber(), caretPos.getIndexInLine()); if (column >= xOffset + columnsOnScreen - 1) scrollToColumn (column + 1 - columnsOnScreen); else if (column < xOffset) scrollToColumn (column); } const Rectangle CodeEditorComponent::getCharacterBounds (const CodeDocument::Position& pos) const { return Rectangle (roundToInt ((gutter - xOffset * charWidth) + indexToColumn (pos.getLineNumber(), pos.getIndexInLine()) * charWidth), (pos.getLineNumber() - firstLineOnScreen) * lineHeight, roundToInt (charWidth), lineHeight); } const CodeDocument::Position CodeEditorComponent::getPositionAt (int x, int y) { const int line = y / lineHeight + firstLineOnScreen; const int column = roundToInt ((x - (gutter - xOffset * charWidth)) / charWidth); const int index = columnToIndex (line, column); return CodeDocument::Position (&document, line, index); } void CodeEditorComponent::insertTextAtCaret (const String& newText) { document.deleteSection (selectionStart, selectionEnd); if (newText.isNotEmpty()) document.insertText (caretPos, newText); scrollToKeepCaretOnScreen(); } void CodeEditorComponent::insertTabAtCaret() { if (CharacterFunctions::isWhitespace (caretPos.getCharacter()) && caretPos.getLineNumber() == caretPos.movedBy (1).getLineNumber()) { moveCaretTo (document.findWordBreakAfter (caretPos), false); } if (useSpacesForTabs) { const int caretCol = indexToColumn (caretPos.getLineNumber(), caretPos.getIndexInLine()); const int spacesNeeded = spacesPerTab - (caretCol % spacesPerTab); insertTextAtCaret (String::repeatedString (" ", spacesNeeded)); } else { insertTextAtCaret ("\t"); } } void CodeEditorComponent::cut() { insertTextAtCaret (String::empty); } void CodeEditorComponent::copy() { newTransaction(); const String selection (document.getTextBetween (selectionStart, selectionEnd)); if (selection.isNotEmpty()) SystemClipboard::copyTextToClipboard (selection); } void CodeEditorComponent::copyThenCut() { copy(); cut(); newTransaction(); } void CodeEditorComponent::paste() { newTransaction(); const String clip (SystemClipboard::getTextFromClipboard()); if (clip.isNotEmpty()) insertTextAtCaret (clip); newTransaction(); } void CodeEditorComponent::cursorLeft (const bool moveInWholeWordSteps, const bool selecting) { newTransaction(); if (moveInWholeWordSteps) moveCaretTo (document.findWordBreakBefore (caretPos), selecting); else moveCaretTo (caretPos.movedBy (-1), selecting); } void CodeEditorComponent::cursorRight (const bool moveInWholeWordSteps, const bool selecting) { newTransaction(); if (moveInWholeWordSteps) moveCaretTo (document.findWordBreakAfter (caretPos), selecting); else moveCaretTo (caretPos.movedBy (1), selecting); } void CodeEditorComponent::moveLineDelta (const int delta, const bool selecting) { CodeDocument::Position pos (caretPos); const int newLineNum = pos.getLineNumber() + delta; if (columnToTryToMaintain < 0) columnToTryToMaintain = indexToColumn (pos.getLineNumber(), pos.getIndexInLine()); pos.setLineAndIndex (newLineNum, columnToIndex (newLineNum, columnToTryToMaintain)); const int colToMaintain = columnToTryToMaintain; moveCaretTo (pos, selecting); columnToTryToMaintain = colToMaintain; } void CodeEditorComponent::cursorDown (const bool selecting) { newTransaction(); if (caretPos.getLineNumber() == document.getNumLines() - 1) moveCaretTo (CodeDocument::Position (&document, std::numeric_limits::max(), std::numeric_limits::max()), selecting); else moveLineDelta (1, selecting); } void CodeEditorComponent::cursorUp (const bool selecting) { newTransaction(); if (caretPos.getLineNumber() == 0) moveCaretTo (CodeDocument::Position (&document, 0, 0), selecting); else moveLineDelta (-1, selecting); } void CodeEditorComponent::pageDown (const bool selecting) { newTransaction(); scrollBy (jlimit (0, linesOnScreen, 1 + document.getNumLines() - firstLineOnScreen - linesOnScreen)); moveLineDelta (linesOnScreen, selecting); } void CodeEditorComponent::pageUp (const bool selecting) { newTransaction(); scrollBy (-linesOnScreen); moveLineDelta (-linesOnScreen, selecting); } void CodeEditorComponent::scrollUp() { newTransaction(); scrollBy (1); if (caretPos.getLineNumber() < firstLineOnScreen) moveLineDelta (1, false); } void CodeEditorComponent::scrollDown() { newTransaction(); scrollBy (-1); if (caretPos.getLineNumber() >= firstLineOnScreen + linesOnScreen) moveLineDelta (-1, false); } void CodeEditorComponent::goToStartOfDocument (const bool selecting) { newTransaction(); moveCaretTo (CodeDocument::Position (&document, 0, 0), selecting); } static int findFirstNonWhitespaceChar (const String& line) throw() { const int len = line.length(); for (int i = 0; i < len; ++i) if (! CharacterFunctions::isWhitespace (line [i])) return i; return 0; } void CodeEditorComponent::goToStartOfLine (const bool selecting) { newTransaction(); int index = findFirstNonWhitespaceChar (caretPos.getLineText()); if (index >= caretPos.getIndexInLine() && caretPos.getIndexInLine() > 0) index = 0; moveCaretTo (CodeDocument::Position (&document, caretPos.getLineNumber(), index), selecting); } void CodeEditorComponent::goToEndOfDocument (const bool selecting) { newTransaction(); moveCaretTo (CodeDocument::Position (&document, std::numeric_limits::max(), std::numeric_limits::max()), selecting); } void CodeEditorComponent::goToEndOfLine (const bool selecting) { newTransaction(); moveCaretTo (CodeDocument::Position (&document, caretPos.getLineNumber(), std::numeric_limits::max()), selecting); } void CodeEditorComponent::backspace (const bool moveInWholeWordSteps) { if (moveInWholeWordSteps) { cut(); // in case something is already highlighted moveCaretTo (document.findWordBreakBefore (caretPos), true); } else { if (selectionStart == selectionEnd) selectionStart.moveBy (-1); } cut(); } void CodeEditorComponent::deleteForward (const bool moveInWholeWordSteps) { if (moveInWholeWordSteps) { cut(); // in case something is already highlighted moveCaretTo (document.findWordBreakAfter (caretPos), true); } else { if (selectionStart == selectionEnd) selectionEnd.moveBy (1); else newTransaction(); } cut(); } void CodeEditorComponent::selectAll() { newTransaction(); moveCaretTo (CodeDocument::Position (&document, std::numeric_limits::max(), std::numeric_limits::max()), false); moveCaretTo (CodeDocument::Position (&document, 0, 0), true); } void CodeEditorComponent::undo() { document.undo(); scrollToKeepCaretOnScreen(); } void CodeEditorComponent::redo() { document.redo(); scrollToKeepCaretOnScreen(); } void CodeEditorComponent::newTransaction() { document.newTransaction(); startTimer (600); } void CodeEditorComponent::timerCallback() { newTransaction(); } const Range CodeEditorComponent::getHighlightedRegion() const { return Range (selectionStart.getPosition(), selectionEnd.getPosition()); } void CodeEditorComponent::setHighlightedRegion (const Range& newRange) { moveCaretTo (CodeDocument::Position (&document, newRange.getStart()), false); moveCaretTo (CodeDocument::Position (&document, newRange.getEnd()), true); } const String CodeEditorComponent::getTextInRange (const Range& range) const { return document.getTextBetween (CodeDocument::Position (&document, range.getStart()), CodeDocument::Position (&document, range.getEnd())); } bool CodeEditorComponent::keyPressed (const KeyPress& key) { const bool moveInWholeWordSteps = key.getModifiers().isCtrlDown() || key.getModifiers().isAltDown(); const bool shiftDown = key.getModifiers().isShiftDown(); if (key.isKeyCode (KeyPress::leftKey)) { cursorLeft (moveInWholeWordSteps, shiftDown); } else if (key.isKeyCode (KeyPress::rightKey)) { cursorRight (moveInWholeWordSteps, shiftDown); } else if (key.isKeyCode (KeyPress::upKey)) { if (key.getModifiers().isCtrlDown() && ! shiftDown) scrollDown(); #if JUCE_MAC else if (key.getModifiers().isCommandDown()) goToStartOfDocument (shiftDown); #endif else cursorUp (shiftDown); } else if (key.isKeyCode (KeyPress::downKey)) { if (key.getModifiers().isCtrlDown() && ! shiftDown) scrollUp(); #if JUCE_MAC else if (key.getModifiers().isCommandDown()) goToEndOfDocument (shiftDown); #endif else cursorDown (shiftDown); } else if (key.isKeyCode (KeyPress::pageDownKey)) { pageDown (shiftDown); } else if (key.isKeyCode (KeyPress::pageUpKey)) { pageUp (shiftDown); } else if (key.isKeyCode (KeyPress::homeKey)) { if (moveInWholeWordSteps) goToStartOfDocument (shiftDown); else goToStartOfLine (shiftDown); } else if (key.isKeyCode (KeyPress::endKey)) { if (moveInWholeWordSteps) goToEndOfDocument (shiftDown); else goToEndOfLine (shiftDown); } else if (key.isKeyCode (KeyPress::backspaceKey)) { backspace (moveInWholeWordSteps); } else if (key.isKeyCode (KeyPress::deleteKey)) { deleteForward (moveInWholeWordSteps); } else if (key == KeyPress ('c', ModifierKeys::commandModifier, 0)) { copy(); } else if (key == KeyPress ('x', ModifierKeys::commandModifier, 0)) { copyThenCut(); } else if (key == KeyPress ('v', ModifierKeys::commandModifier, 0)) { paste(); } else if (key == KeyPress ('z', ModifierKeys::commandModifier, 0)) { undo(); } else if (key == KeyPress ('y', ModifierKeys::commandModifier, 0) || key == KeyPress ('z', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0)) { redo(); } else if (key == KeyPress ('a', ModifierKeys::commandModifier, 0)) { selectAll(); } else if (key == KeyPress::tabKey || key.getTextCharacter() == '\t') { insertTabAtCaret(); } else if (key == KeyPress::returnKey) { newTransaction(); insertTextAtCaret (document.getNewLineCharacters()); } else if (key.isKeyCode (KeyPress::escapeKey)) { newTransaction(); } else if (key.getTextCharacter() >= ' ') { insertTextAtCaret (String::charToString (key.getTextCharacter())); } else { return false; } return true; } void CodeEditorComponent::mouseDown (const MouseEvent& e) { newTransaction(); dragType = notDragging; if (! e.mods.isPopupMenu()) { beginDragAutoRepeat (100); moveCaretTo (getPositionAt (e.x, e.y), e.mods.isShiftDown()); } else { /*PopupMenu m; addPopupMenuItems (m, &e); const int result = m.show(); if (result != 0) performPopupMenuAction (result); */ } } void CodeEditorComponent::mouseDrag (const MouseEvent& e) { if (! e.mods.isPopupMenu()) moveCaretTo (getPositionAt (e.x, e.y), true); } void CodeEditorComponent::mouseUp (const MouseEvent&) { newTransaction(); beginDragAutoRepeat (0); dragType = notDragging; } void CodeEditorComponent::mouseDoubleClick (const MouseEvent& e) { CodeDocument::Position tokenStart (getPositionAt (e.x, e.y)); CodeDocument::Position tokenEnd (tokenStart); if (e.getNumberOfClicks() > 2) { tokenStart.setLineAndIndex (tokenStart.getLineNumber(), 0); tokenEnd.setLineAndIndex (tokenStart.getLineNumber() + 1, 0); } else { while (CharacterFunctions::isLetterOrDigit (tokenEnd.getCharacter())) tokenEnd.moveBy (1); tokenStart = tokenEnd; while (tokenStart.getIndexInLine() > 0 && CharacterFunctions::isLetterOrDigit (tokenStart.movedBy (-1).getCharacter())) tokenStart.moveBy (-1); } moveCaretTo (tokenEnd, false); moveCaretTo (tokenStart, true); } void CodeEditorComponent::mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY) { if ((verticalScrollBar->isVisible() && wheelIncrementY != 0) || (horizontalScrollBar->isVisible() && wheelIncrementX != 0)) { verticalScrollBar->mouseWheelMove (e, 0, wheelIncrementY); horizontalScrollBar->mouseWheelMove (e, wheelIncrementX, 0); } else { Component::mouseWheelMove (e, wheelIncrementX, wheelIncrementY); } } void CodeEditorComponent::scrollBarMoved (ScrollBar* scrollBarThatHasMoved, double newRangeStart) { if (scrollBarThatHasMoved == verticalScrollBar) scrollToLineInternal ((int) newRangeStart); else scrollToColumnInternal (newRangeStart); } void CodeEditorComponent::focusGained (FocusChangeType) { caret->updatePosition(); } void CodeEditorComponent::focusLost (FocusChangeType) { caret->updatePosition(); } void CodeEditorComponent::setTabSize (const int numSpaces, const bool insertSpaces) { useSpacesForTabs = insertSpaces; if (spacesPerTab != numSpaces) { spacesPerTab = numSpaces; triggerAsyncUpdate(); } } int CodeEditorComponent::indexToColumn (int lineNum, int index) const throw() { const String line (document.getLine (lineNum)); jassert (index <= line.length()); int col = 0; for (int i = 0; i < index; ++i) { if (line[i] != '\t') ++col; else col += getTabSize() - (col % getTabSize()); } return col; } int CodeEditorComponent::columnToIndex (int lineNum, int column) const throw() { const String line (document.getLine (lineNum)); const int lineLength = line.length(); int i, col = 0; for (i = 0; i < lineLength; ++i) { if (line[i] != '\t') ++col; else col += getTabSize() - (col % getTabSize()); if (col > column) break; } return i; } void CodeEditorComponent::setFont (const Font& newFont) { font = newFont; charWidth = font.getStringWidthFloat ("0"); lineHeight = roundToInt (font.getHeight()); resized(); } void CodeEditorComponent::resetToDefaultColours() { coloursForTokenCategories.clear(); if (codeTokeniser != 0) { for (int i = codeTokeniser->getTokenTypes().size(); --i >= 0;) setColourForTokenType (i, codeTokeniser->getDefaultColour (i)); } } void CodeEditorComponent::setColourForTokenType (const int tokenType, const Colour& colour) { jassert (tokenType < 256); while (coloursForTokenCategories.size() < tokenType) coloursForTokenCategories.add (Colours::black); coloursForTokenCategories.set (tokenType, colour); repaint(); } const Colour CodeEditorComponent::getColourForTokenType (const int tokenType) const { if (((unsigned int) tokenType) >= (unsigned int) coloursForTokenCategories.size()) return findColour (CodeEditorComponent::defaultTextColourId); return coloursForTokenCategories.getReference (tokenType); } void CodeEditorComponent::clearCachedIterators (const int firstLineToBeInvalid) { int i; for (i = cachedIterators.size(); --i >= 0;) if (cachedIterators.getUnchecked (i)->getLine() < firstLineToBeInvalid) break; cachedIterators.removeRange (jmax (0, i - 1), cachedIterators.size()); } void CodeEditorComponent::updateCachedIterators (int maxLineNum) { const int maxNumCachedPositions = 5000; const int linesBetweenCachedSources = jmax (10, document.getNumLines() / maxNumCachedPositions); if (cachedIterators.size() == 0) cachedIterators.add (new CodeDocument::Iterator (&document)); if (codeTokeniser == 0) return; for (;;) { CodeDocument::Iterator* last = cachedIterators.getLast(); if (last->getLine() >= maxLineNum) break; CodeDocument::Iterator* t = new CodeDocument::Iterator (*last); cachedIterators.add (t); const int targetLine = last->getLine() + linesBetweenCachedSources; for (;;) { codeTokeniser->readNextToken (*t); if (t->getLine() >= targetLine) break; if (t->isEOF()) return; } } } void CodeEditorComponent::getIteratorForPosition (int position, CodeDocument::Iterator& source) { if (codeTokeniser == 0) return; for (int i = cachedIterators.size(); --i >= 0;) { CodeDocument::Iterator* t = cachedIterators.getUnchecked (i); if (t->getPosition() <= position) { source = *t; break; } } while (source.getPosition() < position) { const CodeDocument::Iterator original (source); codeTokeniser->readNextToken (source); if (source.getPosition() > position || source.isEOF()) { source = original; break; } } } END_JUCE_NAMESPACE /*** End of inlined file: juce_CodeEditorComponent.cpp ***/ /*** Start of inlined file: juce_CPlusPlusCodeTokeniser.cpp ***/ BEGIN_JUCE_NAMESPACE CPlusPlusCodeTokeniser::CPlusPlusCodeTokeniser() { } CPlusPlusCodeTokeniser::~CPlusPlusCodeTokeniser() { } namespace CppTokeniser { static bool isIdentifierStart (const juce_wchar c) throw() { return CharacterFunctions::isLetter (c) || c == '_' || c == '@'; } static bool isIdentifierBody (const juce_wchar c) throw() { return CharacterFunctions::isLetterOrDigit (c) || c == '_' || c == '@'; } static bool isReservedKeyword (const juce_wchar* const token, const int tokenLength) throw() { static const juce_wchar* const keywords2Char[] = { JUCE_T("if"), JUCE_T("do"), JUCE_T("or"), JUCE_T("id"), 0 }; static const juce_wchar* const keywords3Char[] = { JUCE_T("for"), JUCE_T("int"), JUCE_T("new"), JUCE_T("try"), JUCE_T("xor"), JUCE_T("and"), JUCE_T("asm"), JUCE_T("not"), 0 }; static const juce_wchar* const keywords4Char[] = { JUCE_T("bool"), JUCE_T("void"), JUCE_T("this"), JUCE_T("true"), JUCE_T("long"), JUCE_T("else"), JUCE_T("char"), JUCE_T("enum"), JUCE_T("case"), JUCE_T("goto"), JUCE_T("auto"), 0 }; static const juce_wchar* const keywords5Char[] = { JUCE_T("while"), JUCE_T("bitor"), JUCE_T("break"), JUCE_T("catch"), JUCE_T("class"), JUCE_T("compl"), JUCE_T("const"), JUCE_T("false"), JUCE_T("float"), JUCE_T("short"), JUCE_T("throw"), JUCE_T("union"), JUCE_T("using"), JUCE_T("or_eq"), 0 }; static const juce_wchar* const keywords6Char[] = { JUCE_T("return"), JUCE_T("struct"), JUCE_T("and_eq"), JUCE_T("bitand"), JUCE_T("delete"), JUCE_T("double"), JUCE_T("extern"), JUCE_T("friend"), JUCE_T("inline"), JUCE_T("not_eq"), JUCE_T("public"), JUCE_T("sizeof"), JUCE_T("static"), JUCE_T("signed"), JUCE_T("switch"), JUCE_T("typeid"), JUCE_T("wchar_t"), JUCE_T("xor_eq"), 0}; static const juce_wchar* const keywordsOther[] = { JUCE_T("const_cast"), JUCE_T("continue"), JUCE_T("default"), JUCE_T("explicit"), JUCE_T("mutable"), JUCE_T("namespace"), JUCE_T("operator"), JUCE_T("private"), JUCE_T("protected"), JUCE_T("register"), JUCE_T("reinterpret_cast"), JUCE_T("static_cast"), JUCE_T("template"), JUCE_T("typedef"), JUCE_T("typename"), JUCE_T("unsigned"), JUCE_T("virtual"), JUCE_T("volatile"), JUCE_T("@implementation"), JUCE_T("@interface"), JUCE_T("@end"), JUCE_T("@synthesize"), JUCE_T("@dynamic"), JUCE_T("@public"), JUCE_T("@private"), JUCE_T("@property"), JUCE_T("@protected"), JUCE_T("@class"), 0 }; const juce_wchar* const* k; switch (tokenLength) { case 2: k = keywords2Char; break; case 3: k = keywords3Char; break; case 4: k = keywords4Char; break; case 5: k = keywords5Char; break; case 6: k = keywords6Char; break; default: if (tokenLength < 2 || tokenLength > 16) return false; k = keywordsOther; break; } int i = 0; while (k[i] != 0) { if (k[i][0] == token[0] && CharacterFunctions::compare (k[i], token) == 0) return true; ++i; } return false; } static int parseIdentifier (CodeDocument::Iterator& source) throw() { int tokenLength = 0; juce_wchar possibleIdentifier [19]; while (isIdentifierBody (source.peekNextChar())) { const juce_wchar c = source.nextChar(); if (tokenLength < numElementsInArray (possibleIdentifier) - 1) possibleIdentifier [tokenLength] = c; ++tokenLength; } if (tokenLength > 1 && tokenLength <= 16) { possibleIdentifier [tokenLength] = 0; if (isReservedKeyword (possibleIdentifier, tokenLength)) return CPlusPlusCodeTokeniser::tokenType_builtInKeyword; } return CPlusPlusCodeTokeniser::tokenType_identifier; } static bool skipNumberSuffix (CodeDocument::Iterator& source) { const juce_wchar c = source.peekNextChar(); if (c == 'l' || c == 'L' || c == 'u' || c == 'U') source.skip(); if (CharacterFunctions::isLetterOrDigit (source.peekNextChar())) return false; return true; } static bool isHexDigit (const juce_wchar c) throw() { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } static bool parseHexLiteral (CodeDocument::Iterator& source) throw() { if (source.nextChar() != '0') return false; juce_wchar c = source.nextChar(); if (c != 'x' && c != 'X') return false; int numDigits = 0; while (isHexDigit (source.peekNextChar())) { ++numDigits; source.skip(); } if (numDigits == 0) return false; return skipNumberSuffix (source); } static bool isOctalDigit (const juce_wchar c) throw() { return c >= '0' && c <= '7'; } static bool parseOctalLiteral (CodeDocument::Iterator& source) throw() { if (source.nextChar() != '0') return false; if (! isOctalDigit (source.nextChar())) return false; while (isOctalDigit (source.peekNextChar())) source.skip(); return skipNumberSuffix (source); } static bool isDecimalDigit (const juce_wchar c) throw() { return c >= '0' && c <= '9'; } static bool parseDecimalLiteral (CodeDocument::Iterator& source) throw() { int numChars = 0; while (isDecimalDigit (source.peekNextChar())) { ++numChars; source.skip(); } if (numChars == 0) return false; return skipNumberSuffix (source); } static bool parseFloatLiteral (CodeDocument::Iterator& source) throw() { int numDigits = 0; while (isDecimalDigit (source.peekNextChar())) { source.skip(); ++numDigits; } const bool hasPoint = (source.peekNextChar() == '.'); if (hasPoint) { source.skip(); while (isDecimalDigit (source.peekNextChar())) { source.skip(); ++numDigits; } } if (numDigits == 0) return false; juce_wchar c = source.peekNextChar(); const bool hasExponent = (c == 'e' || c == 'E'); if (hasExponent) { source.skip(); c = source.peekNextChar(); if (c == '+' || c == '-') source.skip(); int numExpDigits = 0; while (isDecimalDigit (source.peekNextChar())) { source.skip(); ++numExpDigits; } if (numExpDigits == 0) return false; } c = source.peekNextChar(); if (c == 'f' || c == 'F') source.skip(); else if (! (hasExponent || hasPoint)) return false; return true; } static int parseNumber (CodeDocument::Iterator& source) { const CodeDocument::Iterator original (source); if (parseFloatLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_floatLiteral; source = original; if (parseHexLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_integerLiteral; source = original; if (parseOctalLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_integerLiteral; source = original; if (parseDecimalLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_integerLiteral; source = original; source.skip(); return CPlusPlusCodeTokeniser::tokenType_error; } static void skipQuotedString (CodeDocument::Iterator& source) throw() { const juce_wchar quote = source.nextChar(); for (;;) { const juce_wchar c = source.nextChar(); if (c == quote || c == 0) break; if (c == '\\') source.skip(); } } static void skipComment (CodeDocument::Iterator& source) throw() { bool lastWasStar = false; for (;;) { const juce_wchar c = source.nextChar(); if (c == 0 || (c == '/' && lastWasStar)) break; lastWasStar = (c == '*'); } } } int CPlusPlusCodeTokeniser::readNextToken (CodeDocument::Iterator& source) { int result = tokenType_error; source.skipWhitespace(); juce_wchar firstChar = source.peekNextChar(); switch (firstChar) { case 0: source.skip(); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': result = CppTokeniser::parseNumber (source); break; case '.': result = CppTokeniser::parseNumber (source); if (result == tokenType_error) result = tokenType_punctuation; break; case ',': case ';': case ':': source.skip(); result = tokenType_punctuation; break; case '(': case ')': case '{': case '}': case '[': case ']': source.skip(); result = tokenType_bracket; break; case '"': case '\'': CppTokeniser::skipQuotedString (source); result = tokenType_stringLiteral; break; case '+': result = tokenType_operator; source.skip(); if (source.peekNextChar() == '+') source.skip(); else if (source.peekNextChar() == '=') source.skip(); break; case '-': source.skip(); result = CppTokeniser::parseNumber (source); if (result == tokenType_error) { result = tokenType_operator; if (source.peekNextChar() == '-') source.skip(); else if (source.peekNextChar() == '=') source.skip(); } break; case '*': case '%': case '=': case '!': result = tokenType_operator; source.skip(); if (source.peekNextChar() == '=') source.skip(); break; case '/': result = tokenType_operator; source.skip(); if (source.peekNextChar() == '=') { source.skip(); } else if (source.peekNextChar() == '/') { result = tokenType_comment; source.skipToEndOfLine(); } else if (source.peekNextChar() == '*') { source.skip(); result = tokenType_comment; CppTokeniser::skipComment (source); } break; case '?': case '~': source.skip(); result = tokenType_operator; break; case '<': source.skip(); result = tokenType_operator; if (source.peekNextChar() == '=') { source.skip(); } else if (source.peekNextChar() == '<') { source.skip(); if (source.peekNextChar() == '=') source.skip(); } break; case '>': source.skip(); result = tokenType_operator; if (source.peekNextChar() == '=') { source.skip(); } else if (source.peekNextChar() == '<') { source.skip(); if (source.peekNextChar() == '=') source.skip(); } break; case '|': source.skip(); result = tokenType_operator; if (source.peekNextChar() == '=') { source.skip(); } else if (source.peekNextChar() == '|') { source.skip(); if (source.peekNextChar() == '=') source.skip(); } break; case '&': source.skip(); result = tokenType_operator; if (source.peekNextChar() == '=') { source.skip(); } else if (source.peekNextChar() == '&') { source.skip(); if (source.peekNextChar() == '=') source.skip(); } break; case '^': source.skip(); result = tokenType_operator; if (source.peekNextChar() == '=') { source.skip(); } else if (source.peekNextChar() == '^') { source.skip(); if (source.peekNextChar() == '=') source.skip(); } break; case '#': result = tokenType_preprocessor; source.skipToEndOfLine(); break; default: if (CppTokeniser::isIdentifierStart (firstChar)) result = CppTokeniser::parseIdentifier (source); else source.skip(); break; } return result; } const StringArray CPlusPlusCodeTokeniser::getTokenTypes() { const char* const types[] = { "Error", "Comment", "C++ keyword", "Identifier", "Integer literal", "Float literal", "String literal", "Operator", "Bracket", "Punctuation", "Preprocessor line", 0 }; return StringArray (types); } const Colour CPlusPlusCodeTokeniser::getDefaultColour (const int tokenType) { const uint32 colours[] = { 0xffcc0000, // error 0xff00aa00, // comment 0xff0000cc, // keyword 0xff000000, // identifier 0xff880000, // int literal 0xff885500, // float literal 0xff990099, // string literal 0xff225500, // operator 0xff000055, // bracket 0xff004400, // punctuation 0xff660000 // preprocessor }; if (tokenType >= 0 && tokenType < numElementsInArray (colours)) return Colour (colours [tokenType]); return Colours::black; } bool CPlusPlusCodeTokeniser::isReservedKeyword (const String& token) throw() { return CppTokeniser::isReservedKeyword (token, token.length()); } END_JUCE_NAMESPACE /*** End of inlined file: juce_CPlusPlusCodeTokeniser.cpp ***/ /*** Start of inlined file: juce_ComboBox.cpp ***/ BEGIN_JUCE_NAMESPACE ComboBox::ComboBox (const String& name) : Component (name), lastCurrentId (0), isButtonDown (false), separatorPending (false), menuActive (false), label (0) { noChoicesMessage = TRANS("(no choices)"); setRepaintsOnMouseActivity (true); lookAndFeelChanged(); currentId.addListener (this); } ComboBox::~ComboBox() { currentId.removeListener (this); if (menuActive) PopupMenu::dismissAllActiveMenus(); label = 0; deleteAllChildren(); } void ComboBox::setEditableText (const bool isEditable) { if (label->isEditableOnSingleClick() != isEditable || label->isEditableOnDoubleClick() != isEditable) { label->setEditable (isEditable, isEditable, false); setWantsKeyboardFocus (! isEditable); resized(); } } bool ComboBox::isTextEditable() const throw() { return label->isEditable(); } void ComboBox::setJustificationType (const Justification& justification) { label->setJustificationType (justification); } const Justification ComboBox::getJustificationType() const throw() { return label->getJustificationType(); } void ComboBox::setTooltip (const String& newTooltip) { SettableTooltipClient::setTooltip (newTooltip); label->setTooltip (newTooltip); } void ComboBox::addItem (const String& newItemText, const int newItemId) { // you can't add empty strings to the list.. jassert (newItemText.isNotEmpty()); // IDs must be non-zero, as zero is used to indicate a lack of selecion. jassert (newItemId != 0); // you shouldn't use duplicate item IDs! jassert (getItemForId (newItemId) == 0); if (newItemText.isNotEmpty() && newItemId != 0) { if (separatorPending) { separatorPending = false; ItemInfo* const item = new ItemInfo(); item->itemId = 0; item->isEnabled = false; item->isHeading = false; items.add (item); } ItemInfo* const item = new ItemInfo(); item->name = newItemText; item->itemId = newItemId; item->isEnabled = true; item->isHeading = false; items.add (item); } } void ComboBox::addSeparator() { separatorPending = (items.size() > 0); } void ComboBox::addSectionHeading (const String& headingName) { // you can't add empty strings to the list.. jassert (headingName.isNotEmpty()); if (headingName.isNotEmpty()) { if (separatorPending) { separatorPending = false; ItemInfo* const item = new ItemInfo(); item->itemId = 0; item->isEnabled = false; item->isHeading = false; items.add (item); } ItemInfo* const item = new ItemInfo(); item->name = headingName; item->itemId = 0; item->isEnabled = true; item->isHeading = true; items.add (item); } } void ComboBox::setItemEnabled (const int itemId, const bool shouldBeEnabled) { ItemInfo* const item = getItemForId (itemId); if (item != 0) item->isEnabled = shouldBeEnabled; } void ComboBox::changeItemText (const int itemId, const String& newText) { ItemInfo* const item = getItemForId (itemId); jassert (item != 0); if (item != 0) item->name = newText; } void ComboBox::clear (const bool dontSendChangeMessage) { items.clear(); separatorPending = false; if (! label->isEditable()) setSelectedItemIndex (-1, dontSendChangeMessage); } bool ComboBox::ItemInfo::isSeparator() const throw() { return name.isEmpty(); } bool ComboBox::ItemInfo::isRealItem() const throw() { return ! (isHeading || name.isEmpty()); } ComboBox::ItemInfo* ComboBox::getItemForId (const int itemId) const throw() { if (itemId != 0) { for (int i = items.size(); --i >= 0;) if (items.getUnchecked(i)->itemId == itemId) return items.getUnchecked(i); } return 0; } ComboBox::ItemInfo* ComboBox::getItemForIndex (const int index) const throw() { int n = 0; for (int i = 0; i < items.size(); ++i) { ItemInfo* const item = items.getUnchecked(i); if (item->isRealItem()) if (n++ == index) return item; } return 0; } int ComboBox::getNumItems() const throw() { int n = 0; for (int i = items.size(); --i >= 0;) if (items.getUnchecked(i)->isRealItem()) ++n; return n; } const String ComboBox::getItemText (const int index) const { const ItemInfo* const item = getItemForIndex (index); if (item != 0) return item->name; return String::empty; } int ComboBox::getItemId (const int index) const throw() { const ItemInfo* const item = getItemForIndex (index); return (item != 0) ? item->itemId : 0; } int ComboBox::indexOfItemId (const int itemId) const throw() { int n = 0; for (int i = 0; i < items.size(); ++i) { const ItemInfo* const item = items.getUnchecked(i); if (item->isRealItem()) { if (item->itemId == itemId) return n; ++n; } } return -1; } int ComboBox::getSelectedItemIndex() const { int index = indexOfItemId (currentId.getValue()); if (getText() != getItemText (index)) index = -1; return index; } void ComboBox::setSelectedItemIndex (const int index, const bool dontSendChangeMessage) { setSelectedId (getItemId (index), dontSendChangeMessage); } int ComboBox::getSelectedId() const throw() { const ItemInfo* const item = getItemForId (currentId.getValue()); return (item != 0 && getText() == item->name) ? item->itemId : 0; } void ComboBox::setSelectedId (const int newItemId, const bool dontSendChangeMessage) { const ItemInfo* const item = getItemForId (newItemId); const String newItemText (item != 0 ? item->name : String::empty); if (lastCurrentId != newItemId || label->getText() != newItemText) { if (! dontSendChangeMessage) triggerAsyncUpdate(); label->setText (newItemText, false); lastCurrentId = newItemId; currentId = newItemId; repaint(); // for the benefit of the 'none selected' text } } void ComboBox::valueChanged (Value&) { if (lastCurrentId != (int) currentId.getValue()) setSelectedId (currentId.getValue(), false); } const String ComboBox::getText() const { return label->getText(); } void ComboBox::setText (const String& newText, const bool dontSendChangeMessage) { for (int i = items.size(); --i >= 0;) { const ItemInfo* const item = items.getUnchecked(i); if (item->isRealItem() && item->name == newText) { setSelectedId (item->itemId, dontSendChangeMessage); return; } } lastCurrentId = 0; currentId = 0; if (label->getText() != newText) { label->setText (newText, false); if (! dontSendChangeMessage) triggerAsyncUpdate(); } repaint(); } void ComboBox::showEditor() { jassert (isTextEditable()); // you probably shouldn't do this to a non-editable combo box? label->showEditor(); } void ComboBox::setTextWhenNothingSelected (const String& newMessage) { if (textWhenNothingSelected != newMessage) { textWhenNothingSelected = newMessage; repaint(); } } const String ComboBox::getTextWhenNothingSelected() const { return textWhenNothingSelected; } void ComboBox::setTextWhenNoChoicesAvailable (const String& newMessage) { noChoicesMessage = newMessage; } const String ComboBox::getTextWhenNoChoicesAvailable() const { return noChoicesMessage; } void ComboBox::paint (Graphics& g) { getLookAndFeel().drawComboBox (g, getWidth(), getHeight(), isButtonDown, label->getRight(), 0, getWidth() - label->getRight(), getHeight(), *this); if (textWhenNothingSelected.isNotEmpty() && label->getText().isEmpty() && ! label->isBeingEdited()) { g.setColour (findColour (textColourId).withMultipliedAlpha (0.5f)); g.setFont (label->getFont()); g.drawFittedText (textWhenNothingSelected, label->getX() + 2, label->getY() + 1, label->getWidth() - 4, label->getHeight() - 2, label->getJustificationType(), jmax (1, (int) (label->getHeight() / label->getFont().getHeight()))); } } void ComboBox::resized() { if (getHeight() > 0 && getWidth() > 0) getLookAndFeel().positionComboBoxText (*this, *label); } void ComboBox::enablementChanged() { repaint(); } void ComboBox::lookAndFeelChanged() { repaint(); Label* const newLabel = getLookAndFeel().createComboBoxTextBox (*this); if (label != 0) { newLabel->setEditable (label->isEditable()); newLabel->setJustificationType (label->getJustificationType()); newLabel->setTooltip (label->getTooltip()); newLabel->setText (label->getText(), false); } label = newLabel; addAndMakeVisible (newLabel); newLabel->addListener (this); newLabel->addMouseListener (this, false); newLabel->setColour (Label::backgroundColourId, Colours::transparentBlack); newLabel->setColour (Label::textColourId, findColour (ComboBox::textColourId)); newLabel->setColour (TextEditor::textColourId, findColour (ComboBox::textColourId)); newLabel->setColour (TextEditor::backgroundColourId, Colours::transparentBlack); newLabel->setColour (TextEditor::highlightColourId, findColour (TextEditor::highlightColourId)); newLabel->setColour (TextEditor::outlineColourId, Colours::transparentBlack); resized(); } void ComboBox::colourChanged() { lookAndFeelChanged(); } bool ComboBox::keyPressed (const KeyPress& key) { bool used = false; if (key.isKeyCode (KeyPress::upKey) || key.isKeyCode (KeyPress::leftKey)) { setSelectedItemIndex (jmax (0, getSelectedItemIndex() - 1)); used = true; } else if (key.isKeyCode (KeyPress::downKey) || key.isKeyCode (KeyPress::rightKey)) { setSelectedItemIndex (jmin (getSelectedItemIndex() + 1, getNumItems() - 1)); used = true; } else if (key.isKeyCode (KeyPress::returnKey)) { showPopup(); used = true; } return used; } bool ComboBox::keyStateChanged (const bool isKeyDown) { // only forward key events that aren't used by this component return isKeyDown && (KeyPress::isKeyCurrentlyDown (KeyPress::upKey) || KeyPress::isKeyCurrentlyDown (KeyPress::leftKey) || KeyPress::isKeyCurrentlyDown (KeyPress::downKey) || KeyPress::isKeyCurrentlyDown (KeyPress::rightKey)); } void ComboBox::focusGained (FocusChangeType) { repaint(); } void ComboBox::focusLost (FocusChangeType) { repaint(); } void ComboBox::labelTextChanged (Label*) { triggerAsyncUpdate(); } class ComboBox::Callback : public ModalComponentManager::Callback { public: Callback (ComboBox* const box_) : box (box_) { } void modalStateFinished (int returnValue) { if (box != 0) { box->menuActive = false; if (returnValue != 0) box->setSelectedId (returnValue); } } private: Component::SafePointer box; Callback (const Callback&); Callback& operator= (const Callback&); }; void ComboBox::showPopup() { if (! menuActive) { const int selectedId = getSelectedId(); PopupMenu menu; menu.setLookAndFeel (&getLookAndFeel()); for (int i = 0; i < items.size(); ++i) { const ItemInfo* const item = items.getUnchecked(i); if (item->isSeparator()) menu.addSeparator(); else if (item->isHeading) menu.addSectionHeader (item->name); else menu.addItem (item->itemId, item->name, item->isEnabled, item->itemId == selectedId); } if (items.size() == 0) menu.addItem (1, noChoicesMessage, false); menuActive = true; menu.showAt (this, selectedId, getWidth(), 1, jlimit (12, 24, getHeight()), new Callback (this)); } } void ComboBox::mouseDown (const MouseEvent& e) { beginDragAutoRepeat (300); isButtonDown = isEnabled(); if (isButtonDown && (e.eventComponent == this || ! label->isEditable())) { showPopup(); } } void ComboBox::mouseDrag (const MouseEvent& e) { beginDragAutoRepeat (50); if (isButtonDown && ! e.mouseWasClicked()) showPopup(); } void ComboBox::mouseUp (const MouseEvent& e2) { if (isButtonDown) { isButtonDown = false; repaint(); const MouseEvent e (e2.getEventRelativeTo (this)); if (reallyContains (e.x, e.y, true) && (e2.eventComponent == this || ! label->isEditable())) { showPopup(); } } } void ComboBox::addListener (Listener* const listener) { listeners.add (listener); } void ComboBox::removeListener (Listener* const listener) { listeners.remove (listener); } void ComboBox::handleAsyncUpdate() { Component::BailOutChecker checker (this); listeners.callChecked (checker, &ComboBoxListener::comboBoxChanged, this); // (can't use ComboBox::Listener due to idiotic VC2005 bug) } END_JUCE_NAMESPACE /*** End of inlined file: juce_ComboBox.cpp ***/ /*** Start of inlined file: juce_Label.cpp ***/ BEGIN_JUCE_NAMESPACE Label::Label (const String& componentName, const String& labelText) : Component (componentName), textValue (labelText), lastTextValue (labelText), font (15.0f), justification (Justification::centredLeft), ownerComponent (0), horizontalBorderSize (5), verticalBorderSize (1), minimumHorizontalScale (0.7f), editSingleClick (false), editDoubleClick (false), lossOfFocusDiscardsChanges (false) { setColour (TextEditor::textColourId, Colours::black); setColour (TextEditor::backgroundColourId, Colours::transparentBlack); setColour (TextEditor::outlineColourId, Colours::transparentBlack); textValue.addListener (this); } Label::~Label() { textValue.removeListener (this); if (ownerComponent != 0) ownerComponent->removeComponentListener (this); editor = 0; } void Label::setText (const String& newText, const bool broadcastChangeMessage) { hideEditor (true); if (lastTextValue != newText) { lastTextValue = newText; textValue = newText; repaint(); textWasChanged(); if (ownerComponent != 0) componentMovedOrResized (*ownerComponent, true, true); if (broadcastChangeMessage) callChangeListeners(); } } const String Label::getText (const bool returnActiveEditorContents) const { return (returnActiveEditorContents && isBeingEdited()) ? editor->getText() : textValue.toString(); } void Label::valueChanged (Value&) { if (lastTextValue != textValue.toString()) setText (textValue.toString(), true); } void Label::setFont (const Font& newFont) { if (font != newFont) { font = newFont; repaint(); } } const Font& Label::getFont() const throw() { return font; } void Label::setEditable (const bool editOnSingleClick, const bool editOnDoubleClick, const bool lossOfFocusDiscardsChanges_) { editSingleClick = editOnSingleClick; editDoubleClick = editOnDoubleClick; lossOfFocusDiscardsChanges = lossOfFocusDiscardsChanges_; setWantsKeyboardFocus (editOnSingleClick || editOnDoubleClick); setFocusContainer (editOnSingleClick || editOnDoubleClick); } void Label::setJustificationType (const Justification& newJustification) { if (justification != newJustification) { justification = newJustification; repaint(); } } void Label::setBorderSize (int h, int v) { if (horizontalBorderSize != h || verticalBorderSize != v) { horizontalBorderSize = h; verticalBorderSize = v; repaint(); } } Component* Label::getAttachedComponent() const { return static_cast (ownerComponent); } void Label::attachToComponent (Component* owner, const bool onLeft) { if (ownerComponent != 0) ownerComponent->removeComponentListener (this); ownerComponent = owner; leftOfOwnerComp = onLeft; if (ownerComponent != 0) { setVisible (owner->isVisible()); ownerComponent->addComponentListener (this); componentParentHierarchyChanged (*ownerComponent); componentMovedOrResized (*ownerComponent, true, true); } } void Label::componentMovedOrResized (Component& component, bool /*wasMoved*/, bool /*wasResized*/) { if (leftOfOwnerComp) { setSize (jmin (getFont().getStringWidth (textValue.toString()) + 8, component.getX()), component.getHeight()); setTopRightPosition (component.getX(), component.getY()); } else { setSize (component.getWidth(), 8 + roundToInt (getFont().getHeight())); setTopLeftPosition (component.getX(), component.getY() - getHeight()); } } void Label::componentParentHierarchyChanged (Component& component) { if (component.getParentComponent() != 0) component.getParentComponent()->addChildComponent (this); } void Label::componentVisibilityChanged (Component& component) { setVisible (component.isVisible()); } void Label::textWasEdited() { } void Label::textWasChanged() { } void Label::showEditor() { if (editor == 0) { addAndMakeVisible (editor = createEditorComponent()); editor->setText (getText(), false); editor->addListener (this); editor->grabKeyboardFocus(); editor->setHighlightedRegion (Range (0, textValue.toString().length())); editor->addListener (this); resized(); repaint(); editorShown (editor); enterModalState (false); editor->grabKeyboardFocus(); } } void Label::editorShown (TextEditor* /*editorComponent*/) { } void Label::editorAboutToBeHidden (TextEditor* /*editorComponent*/) { } bool Label::updateFromTextEditorContents() { jassert (editor != 0); const String newText (editor->getText()); if (textValue.toString() != newText) { lastTextValue = newText; textValue = newText; repaint(); textWasChanged(); if (ownerComponent != 0) componentMovedOrResized (*ownerComponent, true, true); return true; } return false; } void Label::hideEditor (const bool discardCurrentEditorContents) { if (editor != 0) { Component::SafePointer deletionChecker (this); editorAboutToBeHidden (editor); const bool changed = (! discardCurrentEditorContents) && updateFromTextEditorContents(); editor = 0; repaint(); if (changed) textWasEdited(); if (deletionChecker != 0) exitModalState (0); if (changed && deletionChecker != 0) callChangeListeners(); } } void Label::inputAttemptWhenModal() { if (editor != 0) { if (lossOfFocusDiscardsChanges) textEditorEscapeKeyPressed (*editor); else textEditorReturnKeyPressed (*editor); } } bool Label::isBeingEdited() const throw() { return editor != 0; } TextEditor* Label::createEditorComponent() { TextEditor* const ed = new TextEditor (getName()); ed->setFont (font); // copy these colours from our own settings.. const int cols[] = { TextEditor::backgroundColourId, TextEditor::textColourId, TextEditor::highlightColourId, TextEditor::highlightedTextColourId, TextEditor::caretColourId, TextEditor::outlineColourId, TextEditor::focusedOutlineColourId, TextEditor::shadowColourId }; for (int i = 0; i < numElementsInArray (cols); ++i) ed->setColour (cols[i], findColour (cols[i])); return ed; } void Label::paint (Graphics& g) { getLookAndFeel().drawLabel (g, *this); } void Label::mouseUp (const MouseEvent& e) { if (editSingleClick && e.mouseWasClicked() && contains (e.x, e.y) && ! e.mods.isPopupMenu()) { showEditor(); } } void Label::mouseDoubleClick (const MouseEvent& e) { if (editDoubleClick && ! e.mods.isPopupMenu()) showEditor(); } void Label::resized() { if (editor != 0) editor->setBoundsInset (BorderSize (0)); } void Label::focusGained (FocusChangeType cause) { if (editSingleClick && cause == focusChangedByTabKey) showEditor(); } void Label::enablementChanged() { repaint(); } void Label::colourChanged() { repaint(); } void Label::setMinimumHorizontalScale (const float newScale) { if (minimumHorizontalScale != newScale) { minimumHorizontalScale = newScale; repaint(); } } // We'll use a custom focus traverser here to make sure focus goes from the // text editor to another component rather than back to the label itself. class LabelKeyboardFocusTraverser : public KeyboardFocusTraverser { public: LabelKeyboardFocusTraverser() {} Component* getNextComponent (Component* current) { return KeyboardFocusTraverser::getNextComponent (dynamic_cast (current) != 0 ? current->getParentComponent() : current); } Component* getPreviousComponent (Component* current) { return KeyboardFocusTraverser::getPreviousComponent (dynamic_cast (current) != 0 ? current->getParentComponent() : current); } }; KeyboardFocusTraverser* Label::createFocusTraverser() { return new LabelKeyboardFocusTraverser(); } void Label::addListener (Listener* const listener) { listeners.add (listener); } void Label::removeListener (Listener* const listener) { listeners.remove (listener); } void Label::callChangeListeners() { Component::BailOutChecker checker (this); listeners.callChecked (checker, &LabelListener::labelTextChanged, this); // (can't use Label::Listener due to idiotic VC2005 bug) } void Label::textEditorTextChanged (TextEditor& ed) { if (editor != 0) { jassert (&ed == editor); if (! (hasKeyboardFocus (true) || isCurrentlyBlockedByAnotherModalComponent())) { if (lossOfFocusDiscardsChanges) textEditorEscapeKeyPressed (ed); else textEditorReturnKeyPressed (ed); } } } void Label::textEditorReturnKeyPressed (TextEditor& ed) { if (editor != 0) { jassert (&ed == editor); (void) ed; const bool changed = updateFromTextEditorContents(); hideEditor (true); if (changed) { Component::SafePointer deletionChecker (this); textWasEdited(); if (deletionChecker != 0) callChangeListeners(); } } } void Label::textEditorEscapeKeyPressed (TextEditor& ed) { if (editor != 0) { jassert (&ed == editor); (void) ed; editor->setText (textValue.toString(), false); hideEditor (true); } } void Label::textEditorFocusLost (TextEditor& ed) { textEditorTextChanged (ed); } END_JUCE_NAMESPACE /*** End of inlined file: juce_Label.cpp ***/ /*** Start of inlined file: juce_ListBox.cpp ***/ BEGIN_JUCE_NAMESPACE class ListBoxRowComponent : public Component, public TooltipClient { public: ListBoxRowComponent (ListBox& owner_) : owner (owner_), row (-1), selected (false), isDragging (false) { } ~ListBoxRowComponent() { deleteAllChildren(); } void paint (Graphics& g) { if (owner.getModel() != 0) owner.getModel()->paintListBoxItem (row, g, getWidth(), getHeight(), selected); } void update (const int row_, const bool selected_) { if (row != row_ || selected != selected_) { repaint(); row = row_; selected = selected_; } if (owner.getModel() != 0) { Component* const customComp = owner.getModel()->refreshComponentForRow (row_, selected_, getChildComponent (0)); if (customComp != 0) { addAndMakeVisible (customComp); customComp->setBounds (getLocalBounds()); for (int i = getNumChildComponents(); --i >= 0;) if (getChildComponent (i) != customComp) delete getChildComponent (i); } else { deleteAllChildren(); } } } void mouseDown (const MouseEvent& e) { isDragging = false; selectRowOnMouseUp = false; if (isEnabled()) { if (! selected) { owner.selectRowsBasedOnModifierKeys (row, e.mods); if (owner.getModel() != 0) owner.getModel()->listBoxItemClicked (row, e); } else { selectRowOnMouseUp = true; } } } void mouseUp (const MouseEvent& e) { if (isEnabled() && selectRowOnMouseUp && ! isDragging) { owner.selectRowsBasedOnModifierKeys (row, e.mods); if (owner.getModel() != 0) owner.getModel()->listBoxItemClicked (row, e); } } void mouseDoubleClick (const MouseEvent& e) { if (owner.getModel() != 0 && isEnabled()) owner.getModel()->listBoxItemDoubleClicked (row, e); } void mouseDrag (const MouseEvent& e) { if (isEnabled() && owner.getModel() != 0 && ! (e.mouseWasClicked() || isDragging)) { const SparseSet selectedRows (owner.getSelectedRows()); if (selectedRows.size() > 0) { const String dragDescription (owner.getModel()->getDragSourceDescription (selectedRows)); if (dragDescription.isNotEmpty()) { isDragging = true; owner.startDragAndDrop (e, dragDescription); } } } } void resized() { if (getNumChildComponents() > 0) getChildComponent(0)->setBounds (getLocalBounds()); } const String getTooltip() { if (owner.getModel() != 0) return owner.getModel()->getTooltipForRow (row); return String::empty; } juce_UseDebuggingNewOperator bool neededFlag; private: ListBox& owner; int row; bool selected, isDragging, selectRowOnMouseUp; ListBoxRowComponent (const ListBoxRowComponent&); ListBoxRowComponent& operator= (const ListBoxRowComponent&); }; class ListViewport : public Viewport { public: int firstIndex, firstWholeIndex, lastWholeIndex; bool hasUpdated; ListViewport (ListBox& owner_) : owner (owner_) { setWantsKeyboardFocus (false); setViewedComponent (new Component()); getViewedComponent()->addMouseListener (this, false); getViewedComponent()->setWantsKeyboardFocus (false); } ~ListViewport() { getViewedComponent()->removeMouseListener (this); getViewedComponent()->deleteAllChildren(); } ListBoxRowComponent* getComponentForRow (const int row) const throw() { return static_cast (getViewedComponent()->getChildComponent (row % jmax (1, getViewedComponent()->getNumChildComponents()))); } int getRowNumberOfComponent (Component* const rowComponent) const throw() { const int index = getIndexOfChildComponent (rowComponent); const int num = getViewedComponent()->getNumChildComponents(); for (int i = num; --i >= 0;) if (((firstIndex + i) % jmax (1, num)) == index) return firstIndex + i; return -1; } Component* getComponentForRowIfOnscreen (const int row) const throw() { return (row >= firstIndex && row < firstIndex + getViewedComponent()->getNumChildComponents()) ? getComponentForRow (row) : 0; } void visibleAreaChanged (int, int, int, int) { updateVisibleArea (true); if (owner.getModel() != 0) owner.getModel()->listWasScrolled(); } void updateVisibleArea (const bool makeSureItUpdatesContent) { hasUpdated = false; const int newX = getViewedComponent()->getX(); int newY = getViewedComponent()->getY(); const int newW = jmax (owner.minimumRowWidth, getMaximumVisibleWidth()); const int newH = owner.totalItems * owner.getRowHeight(); if (newY + newH < getMaximumVisibleHeight() && newH > getMaximumVisibleHeight()) newY = getMaximumVisibleHeight() - newH; getViewedComponent()->setBounds (newX, newY, newW, newH); if (makeSureItUpdatesContent && ! hasUpdated) updateContents(); } void updateContents() { hasUpdated = true; const int rowHeight = owner.getRowHeight(); if (rowHeight > 0) { const int y = getViewPositionY(); const int w = getViewedComponent()->getWidth(); const int numNeeded = 2 + getMaximumVisibleHeight() / rowHeight; while (numNeeded > getViewedComponent()->getNumChildComponents()) getViewedComponent()->addAndMakeVisible (new ListBoxRowComponent (owner)); jassert (numNeeded >= 0); while (numNeeded < getViewedComponent()->getNumChildComponents()) { Component* const rowToRemove = getViewedComponent()->getChildComponent (getViewedComponent()->getNumChildComponents() - 1); delete rowToRemove; } firstIndex = y / rowHeight; firstWholeIndex = (y + rowHeight - 1) / rowHeight; lastWholeIndex = (y + getMaximumVisibleHeight() - 1) / rowHeight; for (int i = 0; i < numNeeded; ++i) { const int row = i + firstIndex; ListBoxRowComponent* const rowComp = getComponentForRow (row); if (rowComp != 0) { rowComp->setBounds (0, row * rowHeight, w, rowHeight); rowComp->update (row, owner.isRowSelected (row)); } } } if (owner.headerComponent != 0) owner.headerComponent->setBounds (owner.outlineThickness + getViewedComponent()->getX(), owner.outlineThickness, jmax (owner.getWidth() - owner.outlineThickness * 2, getViewedComponent()->getWidth()), owner.headerComponent->getHeight()); } void paint (Graphics& g) { if (isOpaque()) g.fillAll (owner.findColour (ListBox::backgroundColourId)); } bool keyPressed (const KeyPress& key) { if (key.isKeyCode (KeyPress::upKey) || key.isKeyCode (KeyPress::downKey) || key.isKeyCode (KeyPress::pageUpKey) || key.isKeyCode (KeyPress::pageDownKey) || key.isKeyCode (KeyPress::homeKey) || key.isKeyCode (KeyPress::endKey)) { // we want to avoid these keypresses going to the viewport, and instead allow // them to pass up to our listbox.. return false; } return Viewport::keyPressed (key); } juce_UseDebuggingNewOperator private: ListBox& owner; ListViewport (const ListViewport&); ListViewport& operator= (const ListViewport&); }; ListBox::ListBox (const String& name, ListBoxModel* const model_) : Component (name), model (model_), totalItems (0), rowHeight (22), minimumRowWidth (0), outlineThickness (0), lastRowSelected (-1), mouseMoveSelects (false), multipleSelection (false), hasDoneInitialUpdate (false) { addAndMakeVisible (viewport = new ListViewport (*this)); setWantsKeyboardFocus (true); colourChanged(); } ListBox::~ListBox() { headerComponent = 0; viewport = 0; } void ListBox::setModel (ListBoxModel* const newModel) { if (model != newModel) { model = newModel; updateContent(); } } void ListBox::setMultipleSelectionEnabled (bool b) { multipleSelection = b; } void ListBox::setMouseMoveSelectsRows (bool b) { mouseMoveSelects = b; if (b) addMouseListener (this, true); } void ListBox::paint (Graphics& g) { if (! hasDoneInitialUpdate) updateContent(); g.fillAll (findColour (backgroundColourId)); } void ListBox::paintOverChildren (Graphics& g) { if (outlineThickness > 0) { g.setColour (findColour (outlineColourId)); g.drawRect (0, 0, getWidth(), getHeight(), outlineThickness); } } void ListBox::resized() { viewport->setBoundsInset (BorderSize (outlineThickness + ((headerComponent != 0) ? headerComponent->getHeight() : 0), outlineThickness, outlineThickness, outlineThickness)); viewport->setSingleStepSizes (20, getRowHeight()); viewport->updateVisibleArea (false); } void ListBox::visibilityChanged() { viewport->updateVisibleArea (true); } Viewport* ListBox::getViewport() const throw() { return viewport; } void ListBox::updateContent() { hasDoneInitialUpdate = true; totalItems = (model != 0) ? model->getNumRows() : 0; bool selectionChanged = false; if (selected [selected.size() - 1] >= totalItems) { selected.removeRange (Range (totalItems, std::numeric_limits::max())); lastRowSelected = getSelectedRow (0); selectionChanged = true; } viewport->updateVisibleArea (isVisible()); viewport->resized(); if (selectionChanged && model != 0) model->selectedRowsChanged (lastRowSelected); } void ListBox::selectRow (const int row, bool dontScroll, bool deselectOthersFirst) { selectRowInternal (row, dontScroll, deselectOthersFirst, false); } void ListBox::selectRowInternal (const int row, bool dontScroll, bool deselectOthersFirst, bool isMouseClick) { if (! multipleSelection) deselectOthersFirst = true; if ((! isRowSelected (row)) || (deselectOthersFirst && getNumSelectedRows() > 1)) { if (((unsigned int) row) < (unsigned int) totalItems) { if (deselectOthersFirst) selected.clear(); selected.addRange (Range (row, row + 1)); if (getHeight() == 0 || getWidth() == 0) dontScroll = true; viewport->hasUpdated = false; if (row < viewport->firstWholeIndex && ! dontScroll) { viewport->setViewPosition (viewport->getViewPositionX(), row * getRowHeight()); } else if (row >= viewport->lastWholeIndex && ! dontScroll) { const int rowsOnScreen = viewport->lastWholeIndex - viewport->firstWholeIndex; if (row >= lastRowSelected + rowsOnScreen && rowsOnScreen < totalItems - 1 && ! isMouseClick) { viewport->setViewPosition (viewport->getViewPositionX(), jlimit (0, jmax (0, totalItems - rowsOnScreen), row) * getRowHeight()); } else { viewport->setViewPosition (viewport->getViewPositionX(), jmax (0, (row + 1) * getRowHeight() - viewport->getMaximumVisibleHeight())); } } if (! viewport->hasUpdated) viewport->updateContents(); lastRowSelected = row; model->selectedRowsChanged (row); } else { if (deselectOthersFirst) deselectAllRows(); } } } void ListBox::deselectRow (const int row) { if (selected.contains (row)) { selected.removeRange (Range (row, row + 1)); if (row == lastRowSelected) lastRowSelected = getSelectedRow (0); viewport->updateContents(); model->selectedRowsChanged (lastRowSelected); } } void ListBox::setSelectedRows (const SparseSet& setOfRowsToBeSelected, const bool sendNotificationEventToModel) { selected = setOfRowsToBeSelected; selected.removeRange (Range (totalItems, std::numeric_limits::max())); if (! isRowSelected (lastRowSelected)) lastRowSelected = getSelectedRow (0); viewport->updateContents(); if ((model != 0) && sendNotificationEventToModel) model->selectedRowsChanged (lastRowSelected); } const SparseSet ListBox::getSelectedRows() const { return selected; } void ListBox::selectRangeOfRows (int firstRow, int lastRow) { if (multipleSelection && (firstRow != lastRow)) { const int numRows = totalItems - 1; firstRow = jlimit (0, jmax (0, numRows), firstRow); lastRow = jlimit (0, jmax (0, numRows), lastRow); selected.addRange (Range (jmin (firstRow, lastRow), jmax (firstRow, lastRow) + 1)); selected.removeRange (Range (lastRow, lastRow + 1)); } selectRowInternal (lastRow, false, false, true); } void ListBox::flipRowSelection (const int row) { if (isRowSelected (row)) deselectRow (row); else selectRowInternal (row, false, false, true); } void ListBox::deselectAllRows() { if (! selected.isEmpty()) { selected.clear(); lastRowSelected = -1; viewport->updateContents(); if (model != 0) model->selectedRowsChanged (lastRowSelected); } } void ListBox::selectRowsBasedOnModifierKeys (const int row, const ModifierKeys& mods) { if (multipleSelection && mods.isCommandDown()) { flipRowSelection (row); } else if (multipleSelection && mods.isShiftDown() && lastRowSelected >= 0) { selectRangeOfRows (lastRowSelected, row); } else if ((! mods.isPopupMenu()) || ! isRowSelected (row)) { selectRowInternal (row, false, true, true); } } int ListBox::getNumSelectedRows() const { return selected.size(); } int ListBox::getSelectedRow (const int index) const { return (((unsigned int) index) < (unsigned int) selected.size()) ? selected [index] : -1; } bool ListBox::isRowSelected (const int row) const { return selected.contains (row); } int ListBox::getLastRowSelected() const { return (isRowSelected (lastRowSelected)) ? lastRowSelected : -1; } int ListBox::getRowContainingPosition (const int x, const int y) const throw() { if (((unsigned int) x) < (unsigned int) getWidth()) { const int row = (viewport->getViewPositionY() + y - viewport->getY()) / rowHeight; if (((unsigned int) row) < (unsigned int) totalItems) return row; } return -1; } int ListBox::getInsertionIndexForPosition (const int x, const int y) const throw() { if (((unsigned int) x) < (unsigned int) getWidth()) { const int row = (viewport->getViewPositionY() + y + rowHeight / 2 - viewport->getY()) / rowHeight; return jlimit (0, totalItems, row); } return -1; } Component* ListBox::getComponentForRowNumber (const int row) const throw() { Component* const listRowComp = viewport->getComponentForRowIfOnscreen (row); return listRowComp != 0 ? listRowComp->getChildComponent (0) : 0; } int ListBox::getRowNumberOfComponent (Component* const rowComponent) const throw() { return viewport->getRowNumberOfComponent (rowComponent); } const Rectangle ListBox::getRowPosition (const int rowNumber, const bool relativeToComponentTopLeft) const throw() { int y = viewport->getY() + rowHeight * rowNumber; if (relativeToComponentTopLeft) y -= viewport->getViewPositionY(); return Rectangle (viewport->getX(), y, viewport->getViewedComponent()->getWidth(), rowHeight); } void ListBox::setVerticalPosition (const double proportion) { const int offscreen = viewport->getViewedComponent()->getHeight() - viewport->getHeight(); viewport->setViewPosition (viewport->getViewPositionX(), jmax (0, roundToInt (proportion * offscreen))); } double ListBox::getVerticalPosition() const { const int offscreen = viewport->getViewedComponent()->getHeight() - viewport->getHeight(); return (offscreen > 0) ? viewport->getViewPositionY() / (double) offscreen : 0; } int ListBox::getVisibleRowWidth() const throw() { return viewport->getViewWidth(); } void ListBox::scrollToEnsureRowIsOnscreen (const int row) { if (row < viewport->firstWholeIndex) { viewport->setViewPosition (viewport->getViewPositionX(), row * getRowHeight()); } else if (row >= viewport->lastWholeIndex) { viewport->setViewPosition (viewport->getViewPositionX(), jmax (0, (row + 1) * getRowHeight() - viewport->getMaximumVisibleHeight())); } } bool ListBox::keyPressed (const KeyPress& key) { const int numVisibleRows = viewport->getHeight() / getRowHeight(); const bool multiple = multipleSelection && (lastRowSelected >= 0) && (key.getModifiers().isShiftDown() || key.getModifiers().isCtrlDown() || key.getModifiers().isCommandDown()); if (key.isKeyCode (KeyPress::upKey)) { if (multiple) selectRangeOfRows (lastRowSelected, lastRowSelected - 1); else selectRow (jmax (0, lastRowSelected - 1)); } else if (key.isKeyCode (KeyPress::returnKey) && isRowSelected (lastRowSelected)) { if (model != 0) model->returnKeyPressed (lastRowSelected); } else if (key.isKeyCode (KeyPress::pageUpKey)) { if (multiple) selectRangeOfRows (lastRowSelected, lastRowSelected - numVisibleRows); else selectRow (jmax (0, jmax (0, lastRowSelected) - numVisibleRows)); } else if (key.isKeyCode (KeyPress::pageDownKey)) { if (multiple) selectRangeOfRows (lastRowSelected, lastRowSelected + numVisibleRows); else selectRow (jmin (totalItems - 1, jmax (0, lastRowSelected) + numVisibleRows)); } else if (key.isKeyCode (KeyPress::homeKey)) { if (multiple && key.getModifiers().isShiftDown()) selectRangeOfRows (lastRowSelected, 0); else selectRow (0); } else if (key.isKeyCode (KeyPress::endKey)) { if (multiple && key.getModifiers().isShiftDown()) selectRangeOfRows (lastRowSelected, totalItems - 1); else selectRow (totalItems - 1); } else if (key.isKeyCode (KeyPress::downKey)) { if (multiple) selectRangeOfRows (lastRowSelected, lastRowSelected + 1); else selectRow (jmin (totalItems - 1, jmax (0, lastRowSelected) + 1)); } else if ((key.isKeyCode (KeyPress::deleteKey) || key.isKeyCode (KeyPress::backspaceKey)) && isRowSelected (lastRowSelected)) { if (model != 0) model->deleteKeyPressed (lastRowSelected); } else if (multiple && key == KeyPress ('a', ModifierKeys::commandModifier, 0)) { selectRangeOfRows (0, std::numeric_limits::max()); } else { return false; } return true; } bool ListBox::keyStateChanged (const bool isKeyDown) { return isKeyDown && (KeyPress::isKeyCurrentlyDown (KeyPress::upKey) || KeyPress::isKeyCurrentlyDown (KeyPress::pageUpKey) || KeyPress::isKeyCurrentlyDown (KeyPress::downKey) || KeyPress::isKeyCurrentlyDown (KeyPress::pageDownKey) || KeyPress::isKeyCurrentlyDown (KeyPress::homeKey) || KeyPress::isKeyCurrentlyDown (KeyPress::endKey) || KeyPress::isKeyCurrentlyDown (KeyPress::returnKey)); } void ListBox::mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY) { getHorizontalScrollBar()->mouseWheelMove (e, wheelIncrementX, 0); getVerticalScrollBar()->mouseWheelMove (e, 0, wheelIncrementY); } void ListBox::mouseMove (const MouseEvent& e) { if (mouseMoveSelects) { const MouseEvent e2 (e.getEventRelativeTo (this)); selectRow (getRowContainingPosition (e2.x, e2.y), true); } } void ListBox::mouseExit (const MouseEvent& e) { mouseMove (e); } void ListBox::mouseUp (const MouseEvent& e) { if (e.mouseWasClicked() && model != 0) model->backgroundClicked(); } void ListBox::setRowHeight (const int newHeight) { rowHeight = jmax (1, newHeight); viewport->setSingleStepSizes (20, rowHeight); updateContent(); } int ListBox::getNumRowsOnScreen() const throw() { return viewport->getMaximumVisibleHeight() / rowHeight; } void ListBox::setMinimumContentWidth (const int newMinimumWidth) { minimumRowWidth = newMinimumWidth; updateContent(); } int ListBox::getVisibleContentWidth() const throw() { return viewport->getMaximumVisibleWidth(); } ScrollBar* ListBox::getVerticalScrollBar() const throw() { return viewport->getVerticalScrollBar(); } ScrollBar* ListBox::getHorizontalScrollBar() const throw() { return viewport->getHorizontalScrollBar(); } void ListBox::colourChanged() { setOpaque (findColour (backgroundColourId).isOpaque()); viewport->setOpaque (isOpaque()); repaint(); } void ListBox::setOutlineThickness (const int outlineThickness_) { outlineThickness = outlineThickness_; resized(); } void ListBox::setHeaderComponent (Component* const newHeaderComponent) { if (newHeaderComponent != headerComponent) { headerComponent = newHeaderComponent; addAndMakeVisible (newHeaderComponent); ListBox::resized(); } } void ListBox::repaintRow (const int rowNumber) throw() { repaint (getRowPosition (rowNumber, true)); } const Image ListBox::createSnapshotOfSelectedRows (int& imageX, int& imageY) { Rectangle imageArea; const int firstRow = getRowContainingPosition (0, 0); int i; for (i = getNumRowsOnScreen() + 2; --i >= 0;) { Component* rowComp = viewport->getComponentForRowIfOnscreen (firstRow + i); if (rowComp != 0 && isRowSelected (firstRow + i)) { const Point pos (rowComp->relativePositionToOtherComponent (this, Point())); const Rectangle rowRect (pos.getX(), pos.getY(), rowComp->getWidth(), rowComp->getHeight()); imageArea = imageArea.getUnion (rowRect); } } imageArea = imageArea.getIntersection (getLocalBounds()); imageX = imageArea.getX(); imageY = imageArea.getY(); Image snapshot (Image::ARGB, imageArea.getWidth(), imageArea.getHeight(), true, Image::NativeImage); for (i = getNumRowsOnScreen() + 2; --i >= 0;) { Component* rowComp = viewport->getComponentForRowIfOnscreen (firstRow + i); if (rowComp != 0 && isRowSelected (firstRow + i)) { const Point pos (rowComp->relativePositionToOtherComponent (this, Point())); Graphics g (snapshot); g.setOrigin (pos.getX() - imageX, pos.getY() - imageY); if (g.reduceClipRegion (0, 0, rowComp->getWidth(), rowComp->getHeight())) rowComp->paintEntireComponent (g); } } return snapshot; } void ListBox::startDragAndDrop (const MouseEvent& e, const String& dragDescription) { DragAndDropContainer* const dragContainer = DragAndDropContainer::findParentDragContainerFor (this); if (dragContainer != 0) { int x, y; Image dragImage (createSnapshotOfSelectedRows (x, y)); dragImage.multiplyAllAlphas (0.6f); MouseEvent e2 (e.getEventRelativeTo (this)); const Point p (x - e2.x, y - e2.y); dragContainer->startDragging (dragDescription, this, dragImage, true, &p); } else { // to be able to do a drag-and-drop operation, the listbox needs to // be inside a component which is also a DragAndDropContainer. jassertfalse; } } Component* ListBoxModel::refreshComponentForRow (int, bool, Component* existingComponentToUpdate) { (void) existingComponentToUpdate; jassert (existingComponentToUpdate == 0); // indicates a failure in the code the recycles the components return 0; } void ListBoxModel::listBoxItemClicked (int, const MouseEvent&) { } void ListBoxModel::listBoxItemDoubleClicked (int, const MouseEvent&) { } void ListBoxModel::backgroundClicked() { } void ListBoxModel::selectedRowsChanged (int) { } void ListBoxModel::deleteKeyPressed (int) { } void ListBoxModel::returnKeyPressed (int) { } void ListBoxModel::listWasScrolled() { } const String ListBoxModel::getDragSourceDescription (const SparseSet&) { return String::empty; } const String ListBoxModel::getTooltipForRow (int) { return String::empty; } END_JUCE_NAMESPACE /*** End of inlined file: juce_ListBox.cpp ***/ /*** Start of inlined file: juce_ProgressBar.cpp ***/ BEGIN_JUCE_NAMESPACE ProgressBar::ProgressBar (double& progress_) : progress (progress_), displayPercentage (true), lastCallbackTime (0) { currentValue = jlimit (0.0, 1.0, progress); } ProgressBar::~ProgressBar() { } void ProgressBar::setPercentageDisplay (const bool shouldDisplayPercentage) { displayPercentage = shouldDisplayPercentage; repaint(); } void ProgressBar::setTextToDisplay (const String& text) { displayPercentage = false; displayedMessage = text; } void ProgressBar::lookAndFeelChanged() { setOpaque (findColour (backgroundColourId).isOpaque()); } void ProgressBar::colourChanged() { lookAndFeelChanged(); } void ProgressBar::paint (Graphics& g) { String text; if (displayPercentage) { if (currentValue >= 0 && currentValue <= 1.0) text << roundToInt (currentValue * 100.0) << '%'; } else { text = displayedMessage; } getLookAndFeel().drawProgressBar (g, *this, getWidth(), getHeight(), currentValue, text); } void ProgressBar::visibilityChanged() { if (isVisible()) startTimer (30); else stopTimer(); } void ProgressBar::timerCallback() { double newProgress = progress; const uint32 now = Time::getMillisecondCounter(); const int timeSinceLastCallback = (int) (now - lastCallbackTime); lastCallbackTime = now; if (currentValue != newProgress || newProgress < 0 || newProgress >= 1.0 || currentMessage != displayedMessage) { if (currentValue < newProgress && newProgress >= 0 && newProgress < 1.0 && currentValue >= 0 && currentValue < 1.0) { newProgress = jmin (currentValue + 0.0008 * timeSinceLastCallback, newProgress); } currentValue = newProgress; currentMessage = displayedMessage; repaint(); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ProgressBar.cpp ***/ /*** Start of inlined file: juce_Slider.cpp ***/ BEGIN_JUCE_NAMESPACE 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&); SliderPopupDisplayComponent& operator= (const SliderPopupDisplayComponent&); }; Slider::Slider (const String& name) : Component (name), lastCurrentValue (0), lastValueMin (0), lastValueMax (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(); currentValue.addListener (this); valueMin.addListener (this); valueMax.addListener (this); } Slider::~Slider() { currentValue.removeListener (this); valueMin.removeListener (this); valueMax.removeListener (this); popupDisplay = 0; deleteAllChildren(); } void Slider::handleAsyncUpdate() { cancelPendingUpdate(); Component::BailOutChecker checker (this); listeners.callChecked (checker, &SliderListener::sliderValueChanged, this); // (can't use Slider::Listener due to idiotic VC2005 bug) } void Slider::sendDragStart() { startedDragging(); Component::BailOutChecker checker (this); listeners.callChecked (checker, &SliderListener::sliderDragStarted, this); } void Slider::sendDragEnd() { stoppedDragging(); sliderBeingDragged = -1; Component::BailOutChecker checker (this); listeners.callChecked (checker, &SliderListener::sliderDragEnded, this); } void Slider::addListener (Listener* const listener) { listeners.add (listener); } void Slider::removeListener (Listener* const listener) { listeners.remove (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) { isVelocityBased = velBased; } void Slider::setVelocityModeParameters (const double sensitivity, const int threshold, const double offset, const bool userCanPressKeyToSwapMode) { jassert (threshold >= 0); jassert (sensitivity > 0); jassert (offset >= 0); velocityModeSensitivity = sensitivity; velocityModeOffset = offset; velocityModeThreshold = threshold; userKeyOverridesVelocity = userCanPressKeyToSwapMode; } void Slider::setSkewFactor (const double factor) { skewFactor = factor; } void Slider::setSkewFactorFromMidPoint (const double sliderValueToShowAtMidPoint) { 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) { if (textBoxPos != newPosition || editableText != (! isReadOnly) || textBoxWidth != textEntryBoxWidth || textBoxHeight != textEntryBoxHeight) { textBoxPos = newPosition; editableText = ! isReadOnly; textBoxWidth = textEntryBoxWidth; textBoxHeight = textEntryBoxHeight; repaint(); lookAndFeelChanged(); } } void Slider::setTextBoxIsEditable (const bool shouldBeEditable) { 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) { sendChangeOnlyOnRelease = onlyNotifyOnRelease; } void Slider::setSliderSnapsToMousePosition (const bool shouldSnapToMouse) { snapsToMousePos = shouldSnapToMouse; } void Slider::setPopupDisplayEnabled (const bool enabled, Component* const parentComponentToUse) { popupDisplayEnabled = enabled; parentForPopupDisplay = parentComponentToUse; } void Slider::colourChanged() { lookAndFeelChanged(); } void Slider::lookAndFeelChanged() { const String previousTextBoxContent (valueBox != 0 ? valueBox->getText() : getTextFromValue (currentValue.getValue())); 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); } incButton->setTooltip (getTooltip()); decButton->setTooltip (getTooltip()); } 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 (getValue(), false, false); } else { setMinValue (getMinValue(), false, false); setMaxValue (getMaxValue(), false, false); } updateText(); } } void Slider::triggerChangeMessage (const bool synchronous) { if (synchronous) handleAsyncUpdate(); else triggerAsyncUpdate(); valueChanged(); } void Slider::valueChanged (Value& value) { if (value.refersToSameSourceAs (currentValue)) { if (style != TwoValueHorizontal && style != TwoValueVertical) setValue (currentValue.getValue(), false, false); } else if (value.refersToSameSourceAs (valueMin)) setMinValue (valueMin.getValue(), false, false, true); else if (value.refersToSameSourceAs (valueMax)) setMaxValue (valueMax.getValue(), false, false, true); } double Slider::getValue() const { // 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.getValue(); } 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 ((double) valueMin.getValue() <= (double) valueMax.getValue()); newValue = jlimit ((double) valueMin.getValue(), (double) valueMax.getValue(), newValue); } if (newValue != lastCurrentValue) { if (valueBox != 0) valueBox->hideEditor (true); lastCurrentValue = newValue; currentValue = newValue; updateText(); repaint(); if (popupDisplay != 0) { static_cast (static_cast (popupDisplay)) ->updatePosition (getTextFromValue (newValue)); popupDisplay->repaint(); } if (sendUpdateMessage) triggerChangeMessage (sendMessageSynchronously); } } double Slider::getMinValue() const { // 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.getValue(); } double Slider::getMaxValue() const { // 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.getValue(); } 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 > (double) valueMax.getValue()) setMaxValue (newValue, sendUpdateMessage, sendMessageSynchronously); newValue = jmin ((double) valueMax.getValue(), newValue); } else { if (allowNudgingOfOtherValues && newValue > lastCurrentValue) setValue (newValue, sendUpdateMessage, sendMessageSynchronously); newValue = jmin (lastCurrentValue, newValue); } if (lastValueMin != newValue) { lastValueMin = newValue; valueMin = newValue; repaint(); if (popupDisplay != 0) { static_cast (static_cast (popupDisplay)) ->updatePosition (getTextFromValue (newValue)); 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 < (double) valueMin.getValue()) setMinValue (newValue, sendUpdateMessage, sendMessageSynchronously); newValue = jmax ((double) valueMin.getValue(), newValue); } else { if (allowNudgingOfOtherValues && newValue < lastCurrentValue) setValue (newValue, sendUpdateMessage, sendMessageSynchronously); newValue = jmax (lastCurrentValue, newValue); } if (lastValueMax != newValue) { lastValueMax = newValue; valueMax = newValue; repaint(); if (popupDisplay != 0) { static_cast (static_cast (popupDisplay)) ->updatePosition (getTextFromValue (valueMax.getValue())); popupDisplay->repaint(); } if (sendUpdateMessage) triggerChangeMessage (sendMessageSynchronously); } } void Slider::setDoubleClickReturnValue (const bool isDoubleClickEnabled, const double valueToSetOnDoubleClick) { doubleClickToValue = isDoubleClickEnabled; doubleClickReturnValue = valueToSetOnDoubleClick; } double Slider::getDoubleClickReturnValue (bool& isEnabled_) const { isEnabled_ = doubleClickToValue; return doubleClickReturnValue; } void Slider::updateText() { if (valueBox != 0) valueBox->setText (getTextFromValue (currentValue.getValue()), false); } void Slider::setTextValueSuffix (const String& suffix) { if (textSuffix != suffix) { textSuffix = suffix; updateText(); } } const String Slider::getTextValueSuffix() const { return textSuffix; } const String Slider::getTextFromValue (double v) { if (getNumDecimalPlacesToDisplay() > 0) return String (v, getNumDecimalPlacesToDisplay()) + getTextValueSuffix(); else return String (roundToInt (v)) + getTextValueSuffix(); } 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.substring (1).trimStart(); return t.initialSectionContainingOnly ("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_) { menuEnabled = menuEnabled_; } void Slider::setScrollWheelEnabled (const bool enabled) { scrollWheelEnabled = enabled; } void Slider::labelTextChanged (Label* label) { const double newValue = snapValue (getValueFromText (label->getText()), false); if (newValue != (double) currentValue.getValue()) { 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 { if (interval > 0) value = minimum + interval * std::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 { return style == LinearHorizontal || style == LinearBar || style == TwoValueHorizontal || style == ThreeValueHorizontal; } bool Slider::isVertical() const { return style == LinearVertical || style == TwoValueVertical || style == ThreeValueVertical; } bool Slider::incDecDragDirectionIsHorizontal() const { 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 (lastCurrentValue); 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 (lastCurrentValue), getLinearSliderPos (lastValueMin), getLinearSliderPos (lastValueMax), 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 (getLocalBounds()); } else { if (textBoxPos == NoTextBox) { sliderRect = getLocalBounds(); } 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.setLookAndFeel (&getLookAndFeel()); 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 = std::abs (getLinearSliderPos (currentValue.getValue()) - mousePos); const float minPosDistance = std::abs (getLinearSliderPos (valueMin.getValue()) - 0.1f - mousePos); const float maxPosDistance = std::abs (getLinearSliderPos (valueMax.getValue()) + 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 = (double) valueMax.getValue() - (double) valueMin.getValue(); lastAngle = rotaryStart + (rotaryEnd - rotaryStart) * valueToProportionOfLength (currentValue.getValue()); valueWhenLastDragged = ((sliderBeingDragged == 2) ? valueMax : ((sliderBeingDragged == 1) ? valueMin : currentValue)).getValue(); 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 != (double) currentValue.getValue()) triggerChangeMessage (false); sendDragEnd(); popupDisplay = 0; if (style == IncDecButtons) { incButton->setState (Button::buttonNormal); decButton->setState (Button::buttonNormal); } } } void Slider::restoreMouseIfHidden() { if (mouseWasHidden) { mouseWasHidden = false; for (int i = Desktop::getInstance().getNumMouseSources(); --i >= 0;) Desktop::getInstance().getMouseSource(i)->enableUnboundedMouseMovement (false); const double pos = (sliderBeingDragged == 2) ? getMaxValue() : ((sliderBeingDragged == 1) ? getMinValue() : (double) currentValue.getValue()); Point mousePos; if (style == RotaryHorizontalDrag || style == RotaryVerticalDrag) { mousePos = Desktop::getLastMouseDownPosition(); if (style == RotaryHorizontalDrag) { const double posDiff = valueToProportionOfLength (pos) - valueToProportionOfLength (valueOnMouseDown); mousePos += Point (roundToInt (pixelsForFullDragExtent * posDiff), 0); } else { const double posDiff = valueToProportionOfLength (valueOnMouseDown) - valueToProportionOfLength (pos); mousePos += Point (0, roundToInt (pixelsForFullDragExtent * posDiff)); } } else { const int pixelPos = (int) getLinearSliderPos (pos); mousePos = relativePositionToGlobal (Point (isHorizontal() ? pixelPos : (getWidth() / 2), isVertical() ? pixelPos : (getHeight() / 2))); } Desktop::setMousePosition (mousePos); } } 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 (std::abs (a1 - a2), std::abs (a1 + double_Pi * 2.0 - a2), std::abs (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 = std::atan2 ((double) dx, (double) -dy); while (angle < 0.0) angle += double_Pi * 2.0; if (rotaryStop && ! e.mouseWasClicked()) { if (std::abs (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 + std::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.source.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 = (double) valueMax.getValue() - (double) valueMin.getValue(); } else { jassert (sliderBeingDragged == 2); setMaxValue (snapValue (valueWhenLastDragged, true), ! sendChangeOnlyOnRelease, false, true); if (e.mods.isShiftDown()) setMinValue (getMaxValue() - minMaxDiff, false, false, true); else minMaxDiff = (double) valueMax.getValue() - (double) valueMin.getValue(); } 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 && ! e.mods.isAnyMouseButtonDown()) { if (valueBox != 0) valueBox->hideEditor (false); const double value = (double) currentValue.getValue(); const double proportionDelta = (wheelIncrementX != 0 ? -wheelIncrementX : wheelIncrementY) * 0.15f; const double currentPos = valueToProportionOfLength (value); const double newValue = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + proportionDelta)); double delta = (newValue != value) ? jmax (std::abs (newValue - value), interval) : 0; if (value > newValue) delta = -delta; sendDragStart(); setValue (snapValue (value + delta, false), true, true); sendDragEnd(); } } else { Component::mouseWheelMove (e, wheelIncrementX, wheelIncrementY); } } void SliderListener::sliderDragStarted (Slider*) // (can't write Slider::Listener due to idiotic VC2005 bug) { } void SliderListener::sliderDragEnded (Slider*) { } END_JUCE_NAMESPACE /*** End of inlined file: juce_Slider.cpp ***/ /*** Start of inlined file: juce_TableHeaderComponent.cpp ***/ BEGIN_JUCE_NAMESPACE class DragOverlayComp : public Component { public: DragOverlayComp (const Image& image_) : image (image_) { image.duplicateIfShared(); image.multiplyAllAlphas (0.8f); setAlwaysOnTop (true); } ~DragOverlayComp() { } void paint (Graphics& g) { g.drawImageAt (image, 0, 0); } private: Image image; DragOverlayComp (const DragOverlayComp&); DragOverlayComp& operator= (const DragOverlayComp&); }; TableHeaderComponent::TableHeaderComponent() : columnsChanged (false), columnsResized (false), sortChanged (false), menuActive (true), stretchToFit (false), columnIdBeingResized (0), columnIdBeingDragged (0), columnIdUnderMouse (0), lastDeliberateWidth (0) { } TableHeaderComponent::~TableHeaderComponent() { dragOverlayComp = 0; } void TableHeaderComponent::setPopupMenuActive (const bool hasMenu) { menuActive = hasMenu; } bool TableHeaderComponent::isPopupMenuActive() const { return menuActive; } int TableHeaderComponent::getNumColumns (const bool onlyCountVisibleColumns) const { if (onlyCountVisibleColumns) { int num = 0; for (int i = columns.size(); --i >= 0;) if (columns.getUnchecked(i)->isVisible()) ++num; return num; } else { return columns.size(); } } const String TableHeaderComponent::getColumnName (const int columnId) const { const ColumnInfo* const ci = getInfoForId (columnId); return ci != 0 ? ci->name : String::empty; } void TableHeaderComponent::setColumnName (const int columnId, const String& newName) { ColumnInfo* const ci = getInfoForId (columnId); if (ci != 0 && ci->name != newName) { ci->name = newName; sendColumnsChanged(); } } void TableHeaderComponent::addColumn (const String& columnName, const int columnId, const int width, const int minimumWidth, const int maximumWidth, const int propertyFlags, const int insertIndex) { // can't have a duplicate or null ID! jassert (columnId != 0 && getIndexOfColumnId (columnId, false) < 0); jassert (width > 0); ColumnInfo* const ci = new ColumnInfo(); ci->name = columnName; ci->id = columnId; ci->width = width; ci->lastDeliberateWidth = width; ci->minimumWidth = minimumWidth; ci->maximumWidth = maximumWidth; if (ci->maximumWidth < 0) ci->maximumWidth = std::numeric_limits::max(); jassert (ci->maximumWidth >= ci->minimumWidth); ci->propertyFlags = propertyFlags; columns.insert (insertIndex, ci); sendColumnsChanged(); } void TableHeaderComponent::removeColumn (const int columnIdToRemove) { const int index = getIndexOfColumnId (columnIdToRemove, false); if (index >= 0) { columns.remove (index); sortChanged = true; sendColumnsChanged(); } } void TableHeaderComponent::removeAllColumns() { if (columns.size() > 0) { columns.clear(); sendColumnsChanged(); } } void TableHeaderComponent::moveColumn (const int columnId, int newIndex) { const int currentIndex = getIndexOfColumnId (columnId, false); newIndex = visibleIndexToTotalIndex (newIndex); if (columns [currentIndex] != 0 && currentIndex != newIndex) { columns.move (currentIndex, newIndex); sendColumnsChanged(); } } int TableHeaderComponent::getColumnWidth (const int columnId) const { const ColumnInfo* const ci = getInfoForId (columnId); return ci != 0 ? ci->width : 0; } void TableHeaderComponent::setColumnWidth (const int columnId, const int newWidth) { ColumnInfo* const ci = getInfoForId (columnId); if (ci != 0 && ci->width != newWidth) { const int numColumns = getNumColumns (true); ci->lastDeliberateWidth = ci->width = jlimit (ci->minimumWidth, ci->maximumWidth, newWidth); if (stretchToFit) { const int index = getIndexOfColumnId (columnId, true) + 1; if (((unsigned int) index) < (unsigned int) numColumns) { const int x = getColumnPosition (index).getX(); if (lastDeliberateWidth == 0) lastDeliberateWidth = getTotalWidth(); resizeColumnsToFit (visibleIndexToTotalIndex (index), lastDeliberateWidth - x); } } repaint(); columnsResized = true; triggerAsyncUpdate(); } } int TableHeaderComponent::getIndexOfColumnId (const int columnId, const bool onlyCountVisibleColumns) const { int n = 0; for (int i = 0; i < columns.size(); ++i) { if ((! onlyCountVisibleColumns) || columns.getUnchecked(i)->isVisible()) { if (columns.getUnchecked(i)->id == columnId) return n; ++n; } } return -1; } int TableHeaderComponent::getColumnIdOfIndex (int index, const bool onlyCountVisibleColumns) const { if (onlyCountVisibleColumns) index = visibleIndexToTotalIndex (index); const ColumnInfo* const ci = columns [index]; return (ci != 0) ? ci->id : 0; } const Rectangle TableHeaderComponent::getColumnPosition (const int index) const { int x = 0, width = 0, n = 0; for (int i = 0; i < columns.size(); ++i) { x += width; if (columns.getUnchecked(i)->isVisible()) { width = columns.getUnchecked(i)->width; if (n++ == index) break; } else { width = 0; } } return Rectangle (x, 0, width, getHeight()); } int TableHeaderComponent::getColumnIdAtX (const int xToFind) const { if (xToFind >= 0) { int x = 0; for (int i = 0; i < columns.size(); ++i) { const ColumnInfo* const ci = columns.getUnchecked(i); if (ci->isVisible()) { x += ci->width; if (xToFind < x) return ci->id; } } } return 0; } int TableHeaderComponent::getTotalWidth() const { int w = 0; for (int i = columns.size(); --i >= 0;) if (columns.getUnchecked(i)->isVisible()) w += columns.getUnchecked(i)->width; return w; } void TableHeaderComponent::setStretchToFitActive (const bool shouldStretchToFit) { stretchToFit = shouldStretchToFit; lastDeliberateWidth = getTotalWidth(); resized(); } bool TableHeaderComponent::isStretchToFitActive() const { return stretchToFit; } void TableHeaderComponent::resizeAllColumnsToFit (int targetTotalWidth) { if (stretchToFit && getWidth() > 0 && columnIdBeingResized == 0 && columnIdBeingDragged == 0) { lastDeliberateWidth = targetTotalWidth; resizeColumnsToFit (0, targetTotalWidth); } } void TableHeaderComponent::resizeColumnsToFit (int firstColumnIndex, int targetTotalWidth) { targetTotalWidth = jmax (targetTotalWidth, 0); StretchableObjectResizer sor; int i; for (i = firstColumnIndex; i < columns.size(); ++i) { ColumnInfo* const ci = columns.getUnchecked(i); if (ci->isVisible()) sor.addItem (ci->lastDeliberateWidth, ci->minimumWidth, ci->maximumWidth); } sor.resizeToFit (targetTotalWidth); int visIndex = 0; for (i = firstColumnIndex; i < columns.size(); ++i) { ColumnInfo* const ci = columns.getUnchecked(i); if (ci->isVisible()) { const int newWidth = jlimit (ci->minimumWidth, ci->maximumWidth, (int) std::floor (sor.getItemSize (visIndex++))); if (newWidth != ci->width) { ci->width = newWidth; repaint(); columnsResized = true; triggerAsyncUpdate(); } } } } void TableHeaderComponent::setColumnVisible (const int columnId, const bool shouldBeVisible) { ColumnInfo* const ci = getInfoForId (columnId); if (ci != 0 && shouldBeVisible != ci->isVisible()) { if (shouldBeVisible) ci->propertyFlags |= visible; else ci->propertyFlags &= ~visible; sendColumnsChanged(); resized(); } } bool TableHeaderComponent::isColumnVisible (const int columnId) const { const ColumnInfo* const ci = getInfoForId (columnId); return ci != 0 && ci->isVisible(); } void TableHeaderComponent::setSortColumnId (const int columnId, const bool sortForwards) { if (getSortColumnId() != columnId || isSortedForwards() != sortForwards) { for (int i = columns.size(); --i >= 0;) columns.getUnchecked(i)->propertyFlags &= ~(sortedForwards | sortedBackwards); ColumnInfo* const ci = getInfoForId (columnId); if (ci != 0) ci->propertyFlags |= (sortForwards ? sortedForwards : sortedBackwards); reSortTable(); } } int TableHeaderComponent::getSortColumnId() const { for (int i = columns.size(); --i >= 0;) if ((columns.getUnchecked(i)->propertyFlags & (sortedForwards | sortedBackwards)) != 0) return columns.getUnchecked(i)->id; return 0; } bool TableHeaderComponent::isSortedForwards() const { for (int i = columns.size(); --i >= 0;) if ((columns.getUnchecked(i)->propertyFlags & (sortedForwards | sortedBackwards)) != 0) return (columns.getUnchecked(i)->propertyFlags & sortedForwards) != 0; return true; } void TableHeaderComponent::reSortTable() { sortChanged = true; repaint(); triggerAsyncUpdate(); } const String TableHeaderComponent::toString() const { String s; XmlElement doc ("TABLELAYOUT"); doc.setAttribute ("sortedCol", getSortColumnId()); doc.setAttribute ("sortForwards", isSortedForwards()); for (int i = 0; i < columns.size(); ++i) { const ColumnInfo* const ci = columns.getUnchecked (i); XmlElement* const e = doc.createNewChildElement ("COLUMN"); e->setAttribute ("id", ci->id); e->setAttribute ("visible", ci->isVisible()); e->setAttribute ("width", ci->width); } return doc.createDocument (String::empty, true, false); } void TableHeaderComponent::restoreFromString (const String& storedVersion) { XmlDocument doc (storedVersion); ScopedPointer storedXml (doc.getDocumentElement()); int index = 0; if (storedXml != 0 && storedXml->hasTagName ("TABLELAYOUT")) { forEachXmlChildElement (*storedXml, col) { const int tabId = col->getIntAttribute ("id"); ColumnInfo* const ci = getInfoForId (tabId); if (ci != 0) { columns.move (columns.indexOf (ci), index); ci->width = col->getIntAttribute ("width"); setColumnVisible (tabId, col->getBoolAttribute ("visible")); } ++index; } columnsResized = true; sendColumnsChanged(); setSortColumnId (storedXml->getIntAttribute ("sortedCol"), storedXml->getBoolAttribute ("sortForwards", true)); } } void TableHeaderComponent::addListener (Listener* const newListener) { listeners.addIfNotAlreadyThere (newListener); } void TableHeaderComponent::removeListener (Listener* const listenerToRemove) { listeners.removeValue (listenerToRemove); } void TableHeaderComponent::columnClicked (int columnId, const ModifierKeys& mods) { const ColumnInfo* const ci = getInfoForId (columnId); if (ci != 0 && (ci->propertyFlags & sortable) != 0 && ! mods.isPopupMenu()) setSortColumnId (columnId, (ci->propertyFlags & sortedForwards) == 0); } void TableHeaderComponent::addMenuItems (PopupMenu& menu, const int /*columnIdClicked*/) { for (int i = 0; i < columns.size(); ++i) { const ColumnInfo* const ci = columns.getUnchecked(i); if ((ci->propertyFlags & appearsOnColumnMenu) != 0) menu.addItem (ci->id, ci->name, (ci->propertyFlags & (sortedForwards | sortedBackwards)) == 0, isColumnVisible (ci->id)); } } void TableHeaderComponent::reactToMenuItem (const int menuReturnId, const int /*columnIdClicked*/) { if (getIndexOfColumnId (menuReturnId, false) >= 0) setColumnVisible (menuReturnId, ! isColumnVisible (menuReturnId)); } void TableHeaderComponent::paint (Graphics& g) { LookAndFeel& lf = getLookAndFeel(); lf.drawTableHeaderBackground (g, *this); const Rectangle clip (g.getClipBounds()); int x = 0; for (int i = 0; i < columns.size(); ++i) { const ColumnInfo* const ci = columns.getUnchecked(i); if (ci->isVisible()) { if (x + ci->width > clip.getX() && (ci->id != columnIdBeingDragged || dragOverlayComp == 0 || ! dragOverlayComp->isVisible())) { g.saveState(); g.setOrigin (x, 0); g.reduceClipRegion (0, 0, ci->width, getHeight()); lf.drawTableHeaderColumn (g, ci->name, ci->id, ci->width, getHeight(), ci->id == columnIdUnderMouse, ci->id == columnIdUnderMouse && isMouseButtonDown(), ci->propertyFlags); g.restoreState(); } x += ci->width; if (x >= clip.getRight()) break; } } } void TableHeaderComponent::resized() { } void TableHeaderComponent::mouseMove (const MouseEvent& e) { updateColumnUnderMouse (e.x, e.y); } void TableHeaderComponent::mouseEnter (const MouseEvent& e) { updateColumnUnderMouse (e.x, e.y); } void TableHeaderComponent::mouseExit (const MouseEvent& e) { updateColumnUnderMouse (e.x, e.y); } void TableHeaderComponent::mouseDown (const MouseEvent& e) { repaint(); columnIdBeingResized = 0; columnIdBeingDragged = 0; if (columnIdUnderMouse != 0) { draggingColumnOffset = e.x - getColumnPosition (getIndexOfColumnId (columnIdUnderMouse, true)).getX(); if (e.mods.isPopupMenu()) columnClicked (columnIdUnderMouse, e.mods); } if (menuActive && e.mods.isPopupMenu()) showColumnChooserMenu (columnIdUnderMouse); } void TableHeaderComponent::mouseDrag (const MouseEvent& e) { if (columnIdBeingResized == 0 && columnIdBeingDragged == 0 && ! (e.mouseWasClicked() || e.mods.isPopupMenu())) { dragOverlayComp = 0; columnIdBeingResized = getResizeDraggerAt (e.getMouseDownX()); if (columnIdBeingResized != 0) { const ColumnInfo* const ci = getInfoForId (columnIdBeingResized); initialColumnWidth = ci->width; } else { beginDrag (e); } } if (columnIdBeingResized != 0) { const ColumnInfo* const ci = getInfoForId (columnIdBeingResized); if (ci != 0) { int w = jlimit (ci->minimumWidth, ci->maximumWidth, initialColumnWidth + e.getDistanceFromDragStartX()); if (stretchToFit) { // prevent us dragging a column too far right if we're in stretch-to-fit mode int minWidthOnRight = 0; for (int i = getIndexOfColumnId (columnIdBeingResized, false) + 1; i < columns.size(); ++i) if (columns.getUnchecked (i)->isVisible()) minWidthOnRight += columns.getUnchecked (i)->minimumWidth; const Rectangle currentPos (getColumnPosition (getIndexOfColumnId (columnIdBeingResized, true))); w = jmax (ci->minimumWidth, jmin (w, getWidth() - minWidthOnRight - currentPos.getX())); } setColumnWidth (columnIdBeingResized, w); } } else if (columnIdBeingDragged != 0) { if (e.y >= -50 && e.y < getHeight() + 50) { if (dragOverlayComp != 0) { dragOverlayComp->setVisible (true); dragOverlayComp->setBounds (jlimit (0, jmax (0, getTotalWidth() - dragOverlayComp->getWidth()), e.x - draggingColumnOffset), 0, dragOverlayComp->getWidth(), getHeight()); for (int i = columns.size(); --i >= 0;) { const int currentIndex = getIndexOfColumnId (columnIdBeingDragged, true); int newIndex = currentIndex; if (newIndex > 0) { // if the previous column isn't draggable, we can't move our column // past it, because that'd change the undraggable column's position.. const ColumnInfo* const previous = columns.getUnchecked (newIndex - 1); if ((previous->propertyFlags & draggable) != 0) { const int leftOfPrevious = getColumnPosition (newIndex - 1).getX(); const int rightOfCurrent = getColumnPosition (newIndex).getRight(); if (abs (dragOverlayComp->getX() - leftOfPrevious) < abs (dragOverlayComp->getRight() - rightOfCurrent)) { --newIndex; } } } if (newIndex < columns.size() - 1) { // if the next column isn't draggable, we can't move our column // past it, because that'd change the undraggable column's position.. const ColumnInfo* const nextCol = columns.getUnchecked (newIndex + 1); if ((nextCol->propertyFlags & draggable) != 0) { const int leftOfCurrent = getColumnPosition (newIndex).getX(); const int rightOfNext = getColumnPosition (newIndex + 1).getRight(); if (abs (dragOverlayComp->getX() - leftOfCurrent) > abs (dragOverlayComp->getRight() - rightOfNext)) { ++newIndex; } } } if (newIndex != currentIndex) moveColumn (columnIdBeingDragged, newIndex); else break; } } } else { endDrag (draggingColumnOriginalIndex); } } } void TableHeaderComponent::beginDrag (const MouseEvent& e) { if (columnIdBeingDragged == 0) { columnIdBeingDragged = getColumnIdAtX (e.getMouseDownX()); const ColumnInfo* const ci = getInfoForId (columnIdBeingDragged); if (ci == 0 || (ci->propertyFlags & draggable) == 0) { columnIdBeingDragged = 0; } else { draggingColumnOriginalIndex = getIndexOfColumnId (columnIdBeingDragged, true); const Rectangle columnRect (getColumnPosition (draggingColumnOriginalIndex)); const int temp = columnIdBeingDragged; columnIdBeingDragged = 0; addAndMakeVisible (dragOverlayComp = new DragOverlayComp (createComponentSnapshot (columnRect, false))); columnIdBeingDragged = temp; dragOverlayComp->setBounds (columnRect); for (int i = listeners.size(); --i >= 0;) { listeners.getUnchecked(i)->tableColumnDraggingChanged (this, columnIdBeingDragged); i = jmin (i, listeners.size() - 1); } } } } void TableHeaderComponent::endDrag (const int finalIndex) { if (columnIdBeingDragged != 0) { moveColumn (columnIdBeingDragged, finalIndex); columnIdBeingDragged = 0; repaint(); for (int i = listeners.size(); --i >= 0;) { listeners.getUnchecked(i)->tableColumnDraggingChanged (this, 0); i = jmin (i, listeners.size() - 1); } } } void TableHeaderComponent::mouseUp (const MouseEvent& e) { mouseDrag (e); for (int i = columns.size(); --i >= 0;) if (columns.getUnchecked (i)->isVisible()) columns.getUnchecked (i)->lastDeliberateWidth = columns.getUnchecked (i)->width; columnIdBeingResized = 0; repaint(); endDrag (getIndexOfColumnId (columnIdBeingDragged, true)); updateColumnUnderMouse (e.x, e.y); if (columnIdUnderMouse != 0 && e.mouseWasClicked() && ! e.mods.isPopupMenu()) columnClicked (columnIdUnderMouse, e.mods); dragOverlayComp = 0; } const MouseCursor TableHeaderComponent::getMouseCursor() { if (columnIdBeingResized != 0 || (getResizeDraggerAt (getMouseXYRelative().getX()) != 0 && ! isMouseButtonDown())) return MouseCursor (MouseCursor::LeftRightResizeCursor); return Component::getMouseCursor(); } bool TableHeaderComponent::ColumnInfo::isVisible() const { return (propertyFlags & TableHeaderComponent::visible) != 0; } TableHeaderComponent::ColumnInfo* TableHeaderComponent::getInfoForId (const int id) const { for (int i = columns.size(); --i >= 0;) if (columns.getUnchecked(i)->id == id) return columns.getUnchecked(i); return 0; } int TableHeaderComponent::visibleIndexToTotalIndex (const int visibleIndex) const { int n = 0; for (int i = 0; i < columns.size(); ++i) { if (columns.getUnchecked(i)->isVisible()) { if (n == visibleIndex) return i; ++n; } } return -1; } void TableHeaderComponent::sendColumnsChanged() { if (stretchToFit && lastDeliberateWidth > 0) resizeAllColumnsToFit (lastDeliberateWidth); repaint(); columnsChanged = true; triggerAsyncUpdate(); } void TableHeaderComponent::handleAsyncUpdate() { const bool changed = columnsChanged || sortChanged; const bool sized = columnsResized || changed; const bool sorted = sortChanged; columnsChanged = false; columnsResized = false; sortChanged = false; if (sorted) { for (int i = listeners.size(); --i >= 0;) { listeners.getUnchecked(i)->tableSortOrderChanged (this); i = jmin (i, listeners.size() - 1); } } if (changed) { for (int i = listeners.size(); --i >= 0;) { listeners.getUnchecked(i)->tableColumnsChanged (this); i = jmin (i, listeners.size() - 1); } } if (sized) { for (int i = listeners.size(); --i >= 0;) { listeners.getUnchecked(i)->tableColumnsResized (this); i = jmin (i, listeners.size() - 1); } } } int TableHeaderComponent::getResizeDraggerAt (const int mouseX) const { if (((unsigned int) mouseX) < (unsigned int) getWidth()) { const int draggableDistance = 3; int x = 0; for (int i = 0; i < columns.size(); ++i) { const ColumnInfo* const ci = columns.getUnchecked(i); if (ci->isVisible()) { if (abs (mouseX - (x + ci->width)) <= draggableDistance && (ci->propertyFlags & resizable) != 0) return ci->id; x += ci->width; } } } return 0; } void TableHeaderComponent::updateColumnUnderMouse (int x, int y) { const int newCol = (reallyContains (x, y, true) && getResizeDraggerAt (x) == 0) ? getColumnIdAtX (x) : 0; if (newCol != columnIdUnderMouse) { columnIdUnderMouse = newCol; repaint(); } } void TableHeaderComponent::showColumnChooserMenu (const int columnIdClicked) { PopupMenu m; addMenuItems (m, columnIdClicked); if (m.getNumItems() > 0) { m.setLookAndFeel (&getLookAndFeel()); const int result = m.show(); if (result != 0) reactToMenuItem (result, columnIdClicked); } } void TableHeaderComponent::Listener::tableColumnDraggingChanged (TableHeaderComponent*, int) { } END_JUCE_NAMESPACE /*** End of inlined file: juce_TableHeaderComponent.cpp ***/ /*** Start of inlined file: juce_TableListBox.cpp ***/ BEGIN_JUCE_NAMESPACE static const char* const tableColumnPropertyTag = "_tableColumnID"; class TableListRowComp : public Component, public TooltipClient { public: TableListRowComp (TableListBox& owner_) : owner (owner_), row (-1), isSelected (false) { } ~TableListRowComp() { deleteAllChildren(); } void paint (Graphics& g) { TableListBoxModel* const model = owner.getModel(); if (model != 0) { const TableHeaderComponent* const header = owner.getHeader(); model->paintRowBackground (g, row, getWidth(), getHeight(), isSelected); const int numColumns = header->getNumColumns (true); for (int i = 0; i < numColumns; ++i) { if (! columnsWithComponents [i]) { const int columnId = header->getColumnIdOfIndex (i, true); Rectangle columnRect (header->getColumnPosition (i)); columnRect.setSize (columnRect.getWidth(), getHeight()); g.saveState(); g.reduceClipRegion (columnRect); g.setOrigin (columnRect.getX(), 0); model->paintCell (g, row, columnId, columnRect.getWidth(), columnRect.getHeight(), isSelected); g.restoreState(); } } } } void update (const int newRow, const bool isNowSelected) { if (newRow != row || isNowSelected != isSelected) { row = newRow; isSelected = isNowSelected; repaint(); deleteAllChildren(); } if (row < owner.getNumRows()) { jassert (row >= 0); const Identifier tagPropertyName ("_tableLastUseNum"); const int newTag = Random::getSystemRandom().nextInt(); const TableHeaderComponent* const header = owner.getHeader(); const int numColumns = header->getNumColumns (true); columnsWithComponents.clear(); if (owner.getModel() != 0) { for (int i = 0; i < numColumns; ++i) { const int columnId = header->getColumnIdOfIndex (i, true); Component* const newComp = owner.getModel()->refreshComponentForCell (row, columnId, isSelected, findChildComponentForColumn (columnId)); if (newComp != 0) { addAndMakeVisible (newComp); newComp->getProperties().set (tagPropertyName, newTag); newComp->getProperties().set (tableColumnPropertyTag, columnId); const Rectangle columnRect (header->getColumnPosition (i)); newComp->setBounds (columnRect.getX(), 0, columnRect.getWidth(), getHeight()); columnsWithComponents.setBit (i); } } } for (int i = getNumChildComponents(); --i >= 0;) { Component* const c = getChildComponent (i); if ((int) c->getProperties() [tagPropertyName] != newTag) delete c; } } else { columnsWithComponents.clear(); deleteAllChildren(); } } void resized() { for (int i = getNumChildComponents(); --i >= 0;) { Component* const c = getChildComponent (i); const int columnId = c->getProperties() [tableColumnPropertyTag]; if (columnId != 0) { const Rectangle columnRect (owner.getHeader()->getColumnPosition (owner.getHeader()->getIndexOfColumnId (columnId, true))); c->setBounds (columnRect.getX(), 0, columnRect.getWidth(), getHeight()); } } } void mouseDown (const MouseEvent& e) { isDragging = false; selectRowOnMouseUp = false; if (isEnabled()) { if (! isSelected) { owner.selectRowsBasedOnModifierKeys (row, e.mods); const int columnId = owner.getHeader()->getColumnIdAtX (e.x); if (columnId != 0 && owner.getModel() != 0) owner.getModel()->cellClicked (row, columnId, e); } else { selectRowOnMouseUp = true; } } } void mouseDrag (const MouseEvent& e) { if (isEnabled() && owner.getModel() != 0 && ! (e.mouseWasClicked() || isDragging)) { const SparseSet selectedRows (owner.getSelectedRows()); if (selectedRows.size() > 0) { const String dragDescription (owner.getModel()->getDragSourceDescription (selectedRows)); if (dragDescription.isNotEmpty()) { isDragging = true; owner.startDragAndDrop (e, dragDescription); } } } } void mouseUp (const MouseEvent& e) { if (selectRowOnMouseUp && e.mouseWasClicked() && isEnabled()) { owner.selectRowsBasedOnModifierKeys (row, e.mods); const int columnId = owner.getHeader()->getColumnIdAtX (e.x); if (columnId != 0 && owner.getModel() != 0) owner.getModel()->cellClicked (row, columnId, e); } } void mouseDoubleClick (const MouseEvent& e) { const int columnId = owner.getHeader()->getColumnIdAtX (e.x); if (columnId != 0 && owner.getModel() != 0) owner.getModel()->cellDoubleClicked (row, columnId, e); } const String getTooltip() { const int columnId = owner.getHeader()->getColumnIdAtX (getMouseXYRelative().getX()); if (columnId != 0 && owner.getModel() != 0) return owner.getModel()->getCellTooltip (row, columnId); return String::empty; } Component* findChildComponentForColumn (const int columnId) const { for (int i = getNumChildComponents(); --i >= 0;) { Component* const c = getChildComponent (i); if ((int) c->getProperties() [tableColumnPropertyTag] == columnId) return c; } return 0; } juce_UseDebuggingNewOperator private: TableListBox& owner; int row; bool isSelected, isDragging, selectRowOnMouseUp; BigInteger columnsWithComponents; TableListRowComp (const TableListRowComp&); TableListRowComp& operator= (const TableListRowComp&); }; class TableListBoxHeader : public TableHeaderComponent { public: TableListBoxHeader (TableListBox& owner_) : owner (owner_) { } ~TableListBoxHeader() { } void addMenuItems (PopupMenu& menu, int columnIdClicked) { if (owner.isAutoSizeMenuOptionShown()) { menu.addItem (0xf836743, TRANS("Auto-size this column"), columnIdClicked != 0); menu.addItem (0xf836744, TRANS("Auto-size all columns"), owner.getHeader()->getNumColumns (true) > 0); menu.addSeparator(); } TableHeaderComponent::addMenuItems (menu, columnIdClicked); } void reactToMenuItem (int menuReturnId, int columnIdClicked) { if (menuReturnId == 0xf836743) { owner.autoSizeColumn (columnIdClicked); } else if (menuReturnId == 0xf836744) { owner.autoSizeAllColumns(); } else { TableHeaderComponent::reactToMenuItem (menuReturnId, columnIdClicked); } } juce_UseDebuggingNewOperator private: TableListBox& owner; TableListBoxHeader (const TableListBoxHeader&); TableListBoxHeader& operator= (const TableListBoxHeader&); }; TableListBox::TableListBox (const String& name, TableListBoxModel* const model_) : ListBox (name, 0), model (model_), autoSizeOptionsShown (true) { ListBox::model = this; header = new TableListBoxHeader (*this); header->setSize (100, 28); header->addListener (this); setHeaderComponent (header); } TableListBox::~TableListBox() { header = 0; } void TableListBox::setModel (TableListBoxModel* const newModel) { if (model != newModel) { model = newModel; updateContent(); } } int TableListBox::getHeaderHeight() const { return header->getHeight(); } void TableListBox::setHeaderHeight (const int newHeight) { header->setSize (header->getWidth(), newHeight); resized(); } void TableListBox::autoSizeColumn (const int columnId) { const int width = model != 0 ? model->getColumnAutoSizeWidth (columnId) : 0; if (width > 0) header->setColumnWidth (columnId, width); } void TableListBox::autoSizeAllColumns() { for (int i = 0; i < header->getNumColumns (true); ++i) autoSizeColumn (header->getColumnIdOfIndex (i, true)); } void TableListBox::setAutoSizeMenuOptionShown (const bool shouldBeShown) { autoSizeOptionsShown = shouldBeShown; } bool TableListBox::isAutoSizeMenuOptionShown() const { return autoSizeOptionsShown; } const Rectangle TableListBox::getCellPosition (const int columnId, const int rowNumber, const bool relativeToComponentTopLeft) const { Rectangle headerCell (header->getColumnPosition (header->getIndexOfColumnId (columnId, true))); if (relativeToComponentTopLeft) headerCell.translate (header->getX(), 0); const Rectangle row (getRowPosition (rowNumber, relativeToComponentTopLeft)); return Rectangle (headerCell.getX(), row.getY(), headerCell.getWidth(), row.getHeight()); } Component* TableListBox::getCellComponent (int columnId, int rowNumber) const { TableListRowComp* const rowComp = dynamic_cast (getComponentForRowNumber (rowNumber)); return rowComp != 0 ? rowComp->findChildComponentForColumn (columnId) : 0; } void TableListBox::scrollToEnsureColumnIsOnscreen (const int columnId) { ScrollBar* const scrollbar = getHorizontalScrollBar(); if (scrollbar != 0) { const Rectangle pos (header->getColumnPosition (header->getIndexOfColumnId (columnId, true))); double x = scrollbar->getCurrentRangeStart(); const double w = scrollbar->getCurrentRangeSize(); if (pos.getX() < x) x = pos.getX(); else if (pos.getRight() > x + w) x += jmax (0.0, pos.getRight() - (x + w)); scrollbar->setCurrentRangeStart (x); } } int TableListBox::getNumRows() { return model != 0 ? model->getNumRows() : 0; } void TableListBox::paintListBoxItem (int, Graphics&, int, int, bool) { } Component* TableListBox::refreshComponentForRow (int rowNumber, bool isRowSelected_, Component* existingComponentToUpdate) { if (existingComponentToUpdate == 0) existingComponentToUpdate = new TableListRowComp (*this); static_cast (existingComponentToUpdate)->update (rowNumber, isRowSelected_); return existingComponentToUpdate; } void TableListBox::selectedRowsChanged (int row) { if (model != 0) model->selectedRowsChanged (row); } void TableListBox::deleteKeyPressed (int row) { if (model != 0) model->deleteKeyPressed (row); } void TableListBox::returnKeyPressed (int row) { if (model != 0) model->returnKeyPressed (row); } void TableListBox::backgroundClicked() { if (model != 0) model->backgroundClicked(); } void TableListBox::listWasScrolled() { if (model != 0) model->listWasScrolled(); } void TableListBox::tableColumnsChanged (TableHeaderComponent*) { setMinimumContentWidth (header->getTotalWidth()); repaint(); updateColumnComponents(); } void TableListBox::tableColumnsResized (TableHeaderComponent*) { setMinimumContentWidth (header->getTotalWidth()); repaint(); updateColumnComponents(); } void TableListBox::tableSortOrderChanged (TableHeaderComponent*) { if (model != 0) model->sortOrderChanged (header->getSortColumnId(), header->isSortedForwards()); } void TableListBox::tableColumnDraggingChanged (TableHeaderComponent*, int columnIdNowBeingDragged_) { columnIdNowBeingDragged = columnIdNowBeingDragged_; repaint(); } void TableListBox::resized() { ListBox::resized(); header->resizeAllColumnsToFit (getVisibleContentWidth()); setMinimumContentWidth (header->getTotalWidth()); } void TableListBox::updateColumnComponents() const { const int firstRow = getRowContainingPosition (0, 0); for (int i = firstRow + getNumRowsOnScreen() + 2; --i >= firstRow;) { TableListRowComp* const rowComp = dynamic_cast (getComponentForRowNumber (i)); if (rowComp != 0) rowComp->resized(); } } void TableListBoxModel::cellClicked (int, int, const MouseEvent&) { } void TableListBoxModel::cellDoubleClicked (int, int, const MouseEvent&) { } void TableListBoxModel::backgroundClicked() { } void TableListBoxModel::sortOrderChanged (int, const bool) { } int TableListBoxModel::getColumnAutoSizeWidth (int) { return 0; } void TableListBoxModel::selectedRowsChanged (int) { } void TableListBoxModel::deleteKeyPressed (int) { } void TableListBoxModel::returnKeyPressed (int) { } void TableListBoxModel::listWasScrolled() { } const String TableListBoxModel::getCellTooltip (int /*rowNumber*/, int /*columnId*/) { return String::empty; } const String TableListBoxModel::getDragSourceDescription (const SparseSet&) { return String::empty; } Component* TableListBoxModel::refreshComponentForCell (int, int, bool, Component* existingComponentToUpdate) { (void) existingComponentToUpdate; jassert (existingComponentToUpdate == 0); // indicates a failure in the code the recycles the components return 0; } END_JUCE_NAMESPACE /*** End of inlined file: juce_TableListBox.cpp ***/ /*** Start of inlined file: juce_TextEditor.cpp ***/ BEGIN_JUCE_NAMESPACE // a word or space that can't be broken down any further struct TextAtom { String atomText; float width; uint16 numChars; bool isWhitespace() const { return CharacterFunctions::isWhitespace (atomText[0]); } bool isNewLine() const { return atomText[0] == '\r' || atomText[0] == '\n'; } const String getText (const juce_wchar passwordCharacter) const { if (passwordCharacter == 0) return atomText; else return String::repeatedString (String::charToString (passwordCharacter), atomText.length()); } const String getTrimmedText (const juce_wchar passwordCharacter) const { if (passwordCharacter == 0) return atomText.substring (0, numChars); else if (isNewLine()) return String::empty; else return String::repeatedString (String::charToString (passwordCharacter), numChars); } }; // a run of text with a single font and colour class TextEditor::UniformTextSection { public: UniformTextSection (const String& text, const Font& font_, const Colour& colour_, const juce_wchar passwordCharacter) : font (font_), colour (colour_) { initialiseAtoms (text, passwordCharacter); } UniformTextSection (const UniformTextSection& other) : font (other.font), colour (other.colour) { atoms.ensureStorageAllocated (other.atoms.size()); for (int i = 0; i < other.atoms.size(); ++i) atoms.add (new TextAtom (*other.atoms.getUnchecked(i))); } ~UniformTextSection() { // (no need to delete the atoms, as they're explicitly deleted by the caller) } void clear() { for (int i = atoms.size(); --i >= 0;) delete getAtom(i); atoms.clear(); } int getNumAtoms() const { return atoms.size(); } TextAtom* getAtom (const int index) const throw() { return atoms.getUnchecked (index); } void append (const UniformTextSection& other, const juce_wchar passwordCharacter) { if (other.atoms.size() > 0) { TextAtom* const lastAtom = atoms.getLast(); int i = 0; if (lastAtom != 0) { if (! CharacterFunctions::isWhitespace (lastAtom->atomText.getLastCharacter())) { TextAtom* const first = other.getAtom(0); if (! CharacterFunctions::isWhitespace (first->atomText[0])) { lastAtom->atomText += first->atomText; lastAtom->numChars = (uint16) (lastAtom->numChars + first->numChars); lastAtom->width = font.getStringWidthFloat (lastAtom->getText (passwordCharacter)); delete first; ++i; } } } atoms.ensureStorageAllocated (atoms.size() + other.atoms.size() - i); while (i < other.atoms.size()) { atoms.add (other.getAtom(i)); ++i; } } } UniformTextSection* split (const int indexToBreakAt, const juce_wchar passwordCharacter) { UniformTextSection* const section2 = new UniformTextSection (String::empty, font, colour, passwordCharacter); int index = 0; for (int i = 0; i < atoms.size(); ++i) { TextAtom* const atom = getAtom(i); const int nextIndex = index + atom->numChars; if (index == indexToBreakAt) { int j; for (j = i; j < atoms.size(); ++j) section2->atoms.add (getAtom (j)); for (j = atoms.size(); --j >= i;) atoms.remove (j); break; } else if (indexToBreakAt >= index && indexToBreakAt < nextIndex) { TextAtom* const secondAtom = new TextAtom(); secondAtom->atomText = atom->atomText.substring (indexToBreakAt - index); secondAtom->width = font.getStringWidthFloat (secondAtom->getText (passwordCharacter)); secondAtom->numChars = (uint16) secondAtom->atomText.length(); section2->atoms.add (secondAtom); atom->atomText = atom->atomText.substring (0, indexToBreakAt - index); atom->width = font.getStringWidthFloat (atom->getText (passwordCharacter)); atom->numChars = (uint16) (indexToBreakAt - index); int j; for (j = i + 1; j < atoms.size(); ++j) section2->atoms.add (getAtom (j)); for (j = atoms.size(); --j > i;) atoms.remove (j); break; } index = nextIndex; } return section2; } void appendAllText (String::Concatenator& concatenator) const { for (int i = 0; i < atoms.size(); ++i) concatenator.append (getAtom(i)->atomText); } void appendSubstring (String::Concatenator& concatenator, const Range& range) const { int index = 0; for (int i = 0; i < atoms.size(); ++i) { const TextAtom* const atom = getAtom (i); const int nextIndex = index + atom->numChars; if (range.getStart() < nextIndex) { if (range.getEnd() <= index) break; const Range r ((range - index).getIntersectionWith (Range (0, (int) atom->numChars))); if (! r.isEmpty()) concatenator.append (atom->atomText.substring (r.getStart(), r.getEnd())); } index = nextIndex; } } int getTotalLength() const { int total = 0; for (int i = atoms.size(); --i >= 0;) total += getAtom(i)->numChars; return total; } void setFont (const Font& newFont, const juce_wchar passwordCharacter) { if (font != newFont) { font = newFont; for (int i = atoms.size(); --i >= 0;) { TextAtom* const atom = atoms.getUnchecked(i); atom->width = newFont.getStringWidthFloat (atom->getText (passwordCharacter)); } } } juce_UseDebuggingNewOperator Font font; Colour colour; private: Array atoms; void initialiseAtoms (const String& textToParse, const juce_wchar passwordCharacter) { int i = 0; const int len = textToParse.length(); const juce_wchar* const text = textToParse; while (i < len) { int start = i; // create a whitespace atom unless it starts with non-ws if (CharacterFunctions::isWhitespace (text[i]) && text[i] != '\r' && text[i] != '\n') { while (i < len && CharacterFunctions::isWhitespace (text[i]) && text[i] != '\r' && text[i] != '\n') { ++i; } } else { if (text[i] == '\r') { ++i; if ((i < len) && (text[i] == '\n')) { ++start; ++i; } } else if (text[i] == '\n') { ++i; } else { while ((i < len) && ! CharacterFunctions::isWhitespace (text[i])) ++i; } } TextAtom* const atom = new TextAtom(); atom->atomText = String (text + start, i - start); atom->width = font.getStringWidthFloat (atom->getText (passwordCharacter)); atom->numChars = (uint16) (i - start); atoms.add (atom); } } UniformTextSection& operator= (const UniformTextSection& other); }; class TextEditor::Iterator { public: Iterator (const Array & sections_, const float wordWrapWidth_, const juce_wchar passwordCharacter_) : indexInText (0), lineY (0), lineHeight (0), maxDescent (0), atomX (0), atomRight (0), atom (0), currentSection (0), sections (sections_), sectionIndex (0), atomIndex (0), wordWrapWidth (wordWrapWidth_), passwordCharacter (passwordCharacter_) { jassert (wordWrapWidth_ > 0); if (sections.size() > 0) { currentSection = sections.getUnchecked (sectionIndex); if (currentSection != 0) beginNewLine(); } } Iterator (const Iterator& other) : indexInText (other.indexInText), lineY (other.lineY), lineHeight (other.lineHeight), maxDescent (other.maxDescent), atomX (other.atomX), atomRight (other.atomRight), atom (other.atom), currentSection (other.currentSection), sections (other.sections), sectionIndex (other.sectionIndex), atomIndex (other.atomIndex), wordWrapWidth (other.wordWrapWidth), passwordCharacter (other.passwordCharacter), tempAtom (other.tempAtom) { } ~Iterator() { } bool next() { if (atom == &tempAtom) { const int numRemaining = tempAtom.atomText.length() - tempAtom.numChars; if (numRemaining > 0) { tempAtom.atomText = tempAtom.atomText.substring (tempAtom.numChars); atomX = 0; if (tempAtom.numChars > 0) lineY += lineHeight; indexInText += tempAtom.numChars; GlyphArrangement g; g.addLineOfText (currentSection->font, atom->getText (passwordCharacter), 0.0f, 0.0f); int split; for (split = 0; split < g.getNumGlyphs(); ++split) if (shouldWrap (g.getGlyph (split).getRight())) break; if (split > 0 && split <= numRemaining) { tempAtom.numChars = (uint16) split; tempAtom.width = g.getGlyph (split - 1).getRight(); atomRight = atomX + tempAtom.width; return true; } } } bool forceNewLine = false; if (sectionIndex >= sections.size()) { moveToEndOfLastAtom(); return false; } else if (atomIndex >= currentSection->getNumAtoms() - 1) { if (atomIndex >= currentSection->getNumAtoms()) { if (++sectionIndex >= sections.size()) { moveToEndOfLastAtom(); return false; } atomIndex = 0; currentSection = sections.getUnchecked (sectionIndex); } else { const TextAtom* const lastAtom = currentSection->getAtom (atomIndex); if (! lastAtom->isWhitespace()) { // handle the case where the last atom in a section is actually part of the same // word as the first atom of the next section... float right = atomRight + lastAtom->width; float lineHeight2 = lineHeight; float maxDescent2 = maxDescent; for (int section = sectionIndex + 1; section < sections.size(); ++section) { const UniformTextSection* const s = sections.getUnchecked (section); if (s->getNumAtoms() == 0) break; const TextAtom* const nextAtom = s->getAtom (0); if (nextAtom->isWhitespace()) break; right += nextAtom->width; lineHeight2 = jmax (lineHeight2, s->font.getHeight()); maxDescent2 = jmax (maxDescent2, s->font.getDescent()); if (shouldWrap (right)) { lineHeight = lineHeight2; maxDescent = maxDescent2; forceNewLine = true; break; } if (s->getNumAtoms() > 1) break; } } } } if (atom != 0) { atomX = atomRight; indexInText += atom->numChars; if (atom->isNewLine()) beginNewLine(); } atom = currentSection->getAtom (atomIndex); atomRight = atomX + atom->width; ++atomIndex; if (shouldWrap (atomRight) || forceNewLine) { if (atom->isWhitespace()) { // leave whitespace at the end of a line, but truncate it to avoid scrolling atomRight = jmin (atomRight, wordWrapWidth); } else { atomRight = atom->width; if (shouldWrap (atomRight)) // atom too big to fit on a line, so break it up.. { tempAtom = *atom; tempAtom.width = 0; tempAtom.numChars = 0; atom = &tempAtom; if (atomX > 0) beginNewLine(); return next(); } beginNewLine(); return true; } } return true; } void beginNewLine() { atomX = 0; lineY += lineHeight; int tempSectionIndex = sectionIndex; int tempAtomIndex = atomIndex; const UniformTextSection* section = sections.getUnchecked (tempSectionIndex); lineHeight = section->font.getHeight(); maxDescent = section->font.getDescent(); float x = (atom != 0) ? atom->width : 0; while (! shouldWrap (x)) { if (tempSectionIndex >= sections.size()) break; bool checkSize = false; if (tempAtomIndex >= section->getNumAtoms()) { if (++tempSectionIndex >= sections.size()) break; tempAtomIndex = 0; section = sections.getUnchecked (tempSectionIndex); checkSize = true; } const TextAtom* const nextAtom = section->getAtom (tempAtomIndex); if (nextAtom == 0) break; x += nextAtom->width; if (shouldWrap (x) || nextAtom->isNewLine()) break; if (checkSize) { lineHeight = jmax (lineHeight, section->font.getHeight()); maxDescent = jmax (maxDescent, section->font.getDescent()); } ++tempAtomIndex; } } void draw (Graphics& g, const UniformTextSection*& lastSection) const { if (passwordCharacter != 0 || ! atom->isWhitespace()) { if (lastSection != currentSection) { lastSection = currentSection; g.setColour (currentSection->colour); g.setFont (currentSection->font); } jassert (atom->getTrimmedText (passwordCharacter).isNotEmpty()); GlyphArrangement ga; ga.addLineOfText (currentSection->font, atom->getTrimmedText (passwordCharacter), atomX, (float) roundToInt (lineY + lineHeight - maxDescent)); ga.draw (g); } } void drawSelection (Graphics& g, const Range& selection) const { const int startX = roundToInt (indexToX (selection.getStart())); const int endX = roundToInt (indexToX (selection.getEnd())); const int y = roundToInt (lineY); const int nextY = roundToInt (lineY + lineHeight); g.fillRect (startX, y, endX - startX, nextY - y); } void drawSelectedText (Graphics& g, const Range& selection, const Colour& selectedTextColour) const { if (passwordCharacter != 0 || ! atom->isWhitespace()) { GlyphArrangement ga; ga.addLineOfText (currentSection->font, atom->getTrimmedText (passwordCharacter), atomX, (float) roundToInt (lineY + lineHeight - maxDescent)); if (selection.getEnd() < indexInText + atom->numChars) { GlyphArrangement ga2 (ga); ga2.removeRangeOfGlyphs (0, selection.getEnd() - indexInText); ga.removeRangeOfGlyphs (selection.getEnd() - indexInText, -1); g.setColour (currentSection->colour); ga2.draw (g); } if (selection.getStart() > indexInText) { GlyphArrangement ga2 (ga); ga2.removeRangeOfGlyphs (selection.getStart() - indexInText, -1); ga.removeRangeOfGlyphs (0, selection.getStart() - indexInText); g.setColour (currentSection->colour); ga2.draw (g); } g.setColour (selectedTextColour); ga.draw (g); } } float indexToX (const int indexToFind) const { if (indexToFind <= indexInText) return atomX; if (indexToFind >= indexInText + atom->numChars) return atomRight; GlyphArrangement g; g.addLineOfText (currentSection->font, atom->getText (passwordCharacter), atomX, 0.0f); if (indexToFind - indexInText >= g.getNumGlyphs()) return atomRight; return jmin (atomRight, g.getGlyph (indexToFind - indexInText).getLeft()); } int xToIndex (const float xToFind) const { if (xToFind <= atomX || atom->isNewLine()) return indexInText; if (xToFind >= atomRight) return indexInText + atom->numChars; GlyphArrangement g; g.addLineOfText (currentSection->font, atom->getText (passwordCharacter), atomX, 0.0f); int j; for (j = 0; j < g.getNumGlyphs(); ++j) if ((g.getGlyph(j).getLeft() + g.getGlyph(j).getRight()) / 2 > xToFind) break; return indexInText + j; } bool getCharPosition (const int index, float& cx, float& cy, float& lineHeight_) { while (next()) { if (indexInText + atom->numChars > index) { cx = indexToX (index); cy = lineY; lineHeight_ = lineHeight; return true; } } cx = atomX; cy = lineY; lineHeight_ = lineHeight; return false; } juce_UseDebuggingNewOperator int indexInText; float lineY, lineHeight, maxDescent; float atomX, atomRight; const TextAtom* atom; const UniformTextSection* currentSection; private: const Array & sections; int sectionIndex, atomIndex; const float wordWrapWidth; const juce_wchar passwordCharacter; TextAtom tempAtom; Iterator& operator= (const Iterator&); void moveToEndOfLastAtom() { if (atom != 0) { atomX = atomRight; if (atom->isNewLine()) { atomX = 0.0f; lineY += lineHeight; } } } bool shouldWrap (const float x) const { return (x - 0.0001f) >= wordWrapWidth; } }; class TextEditor::InsertAction : public UndoableAction { TextEditor& owner; const String text; const int insertIndex, oldCaretPos, newCaretPos; const Font font; const Colour colour; InsertAction (const InsertAction&); InsertAction& operator= (const InsertAction&); public: InsertAction (TextEditor& owner_, const String& text_, const int insertIndex_, const Font& font_, const Colour& colour_, const int oldCaretPos_, const int newCaretPos_) : owner (owner_), text (text_), insertIndex (insertIndex_), oldCaretPos (oldCaretPos_), newCaretPos (newCaretPos_), font (font_), colour (colour_) { } ~InsertAction() { } bool perform() { owner.insert (text, insertIndex, font, colour, 0, newCaretPos); return true; } bool undo() { owner.remove (Range (insertIndex, insertIndex + text.length()), 0, oldCaretPos); return true; } int getSizeInUnits() { return text.length() + 16; } }; class TextEditor::RemoveAction : public UndoableAction { TextEditor& owner; const Range range; const int oldCaretPos, newCaretPos; Array removedSections; RemoveAction (const RemoveAction&); RemoveAction& operator= (const RemoveAction&); public: RemoveAction (TextEditor& owner_, const Range range_, const int oldCaretPos_, const int newCaretPos_, const Array & removedSections_) : owner (owner_), range (range_), oldCaretPos (oldCaretPos_), newCaretPos (newCaretPos_), removedSections (removedSections_) { } ~RemoveAction() { for (int i = removedSections.size(); --i >= 0;) { UniformTextSection* const section = removedSections.getUnchecked (i); section->clear(); delete section; } } bool perform() { owner.remove (range, 0, newCaretPos); return true; } bool undo() { owner.reinsert (range.getStart(), removedSections); owner.moveCursorTo (oldCaretPos, false); return true; } int getSizeInUnits() { int n = 0; for (int i = removedSections.size(); --i >= 0;) n += removedSections.getUnchecked (i)->getTotalLength(); return n + 16; } }; class TextEditor::TextHolderComponent : public Component, public Timer, public Value::Listener { public: TextHolderComponent (TextEditor& owner_) : owner (owner_) { setWantsKeyboardFocus (false); setInterceptsMouseClicks (false, true); owner.getTextValue().addListener (this); } ~TextHolderComponent() { owner.getTextValue().removeListener (this); } void paint (Graphics& g) { owner.drawContent (g); } void timerCallback() { owner.timerCallbackInt(); } const MouseCursor getMouseCursor() { return owner.getMouseCursor(); } void valueChanged (Value&) { owner.textWasChangedByValue(); } private: TextEditor& owner; TextHolderComponent (const TextHolderComponent&); TextHolderComponent& operator= (const TextHolderComponent&); }; class TextEditorViewport : public Viewport { public: TextEditorViewport (TextEditor* const owner_) : owner (owner_), lastWordWrapWidth (0), rentrant (false) { } ~TextEditorViewport() { } void visibleAreaChanged (int, int, int, int) { if (! rentrant) // it's rare, but possible to get into a feedback loop as the viewport's scrollbars // appear and disappear, causing the wrap width to change. { const float wordWrapWidth = owner->getWordWrapWidth(); if (wordWrapWidth != lastWordWrapWidth) { lastWordWrapWidth = wordWrapWidth; rentrant = true; owner->updateTextHolderSize(); rentrant = false; } } } private: TextEditor* const owner; float lastWordWrapWidth; bool rentrant; TextEditorViewport (const TextEditorViewport&); TextEditorViewport& operator= (const TextEditorViewport&); }; namespace TextEditorDefs { const int flashSpeedIntervalMs = 380; const int textChangeMessageId = 0x10003001; const int returnKeyMessageId = 0x10003002; const int escapeKeyMessageId = 0x10003003; const int focusLossMessageId = 0x10003004; const int maxActionsPerTransaction = 100; static int getCharacterCategory (const juce_wchar character) { return CharacterFunctions::isLetterOrDigit (character) ? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1); } } TextEditor::TextEditor (const String& name, const juce_wchar passwordCharacter_) : Component (name), borderSize (1, 1, 1, 3), readOnly (false), multiline (false), wordWrap (false), returnKeyStartsNewLine (false), caretVisible (true), popupMenuEnabled (true), selectAllTextWhenFocused (false), scrollbarVisible (true), wasFocused (false), caretFlashState (true), keepCursorOnScreen (true), tabKeyUsed (false), menuActive (false), valueTextNeedsUpdating (false), cursorX (0), cursorY (0), cursorHeight (0), maxTextLength (0), leftIndent (4), topIndent (4), lastTransactionTime (0), currentFont (14.0f), totalNumChars (0), caretPosition (0), passwordCharacter (passwordCharacter_), dragType (notDragging) { setOpaque (true); addAndMakeVisible (viewport = new TextEditorViewport (this)); viewport->setViewedComponent (textHolder = new TextHolderComponent (*this)); viewport->setWantsKeyboardFocus (false); viewport->setScrollBarsShown (false, false); setMouseCursor (MouseCursor::IBeamCursor); setWantsKeyboardFocus (true); } TextEditor::~TextEditor() { textValue.referTo (Value()); clearInternal (0); viewport = 0; textHolder = 0; } void TextEditor::newTransaction() { lastTransactionTime = Time::getApproximateMillisecondCounter(); undoManager.beginNewTransaction(); } void TextEditor::doUndoRedo (const bool isRedo) { if (! isReadOnly()) { if (isRedo ? undoManager.redo() : undoManager.undo()) { scrollToMakeSureCursorIsVisible(); repaint(); textChanged(); } } } void TextEditor::setMultiLine (const bool shouldBeMultiLine, const bool shouldWordWrap) { if (multiline != shouldBeMultiLine || wordWrap != (shouldWordWrap && shouldBeMultiLine)) { multiline = shouldBeMultiLine; wordWrap = shouldWordWrap && shouldBeMultiLine; viewport->setScrollBarsShown (scrollbarVisible && multiline, scrollbarVisible && multiline); viewport->setViewPosition (0, 0); resized(); scrollToMakeSureCursorIsVisible(); } } bool TextEditor::isMultiLine() const { return multiline; } void TextEditor::setScrollbarsShown (bool shown) { if (scrollbarVisible != shown) { scrollbarVisible = shown; shown = shown && isMultiLine(); viewport->setScrollBarsShown (shown, shown); } } void TextEditor::setReadOnly (const bool shouldBeReadOnly) { if (readOnly != shouldBeReadOnly) { readOnly = shouldBeReadOnly; enablementChanged(); } } bool TextEditor::isReadOnly() const { return readOnly || ! isEnabled(); } bool TextEditor::isTextInputActive() const { return ! isReadOnly(); } void TextEditor::setReturnKeyStartsNewLine (const bool shouldStartNewLine) { returnKeyStartsNewLine = shouldStartNewLine; } void TextEditor::setTabKeyUsedAsCharacter (const bool shouldTabKeyBeUsed) { tabKeyUsed = shouldTabKeyBeUsed; } void TextEditor::setPopupMenuEnabled (const bool b) { popupMenuEnabled = b; } void TextEditor::setSelectAllWhenFocused (const bool b) { selectAllTextWhenFocused = b; } const Font TextEditor::getFont() const { return currentFont; } void TextEditor::setFont (const Font& newFont) { currentFont = newFont; scrollToMakeSureCursorIsVisible(); } void TextEditor::applyFontToAllText (const Font& newFont) { currentFont = newFont; const Colour overallColour (findColour (textColourId)); for (int i = sections.size(); --i >= 0;) { UniformTextSection* const uts = sections.getUnchecked (i); uts->setFont (newFont, passwordCharacter); uts->colour = overallColour; } coalesceSimilarSections(); updateTextHolderSize(); scrollToMakeSureCursorIsVisible(); repaint(); } void TextEditor::colourChanged() { setOpaque (findColour (backgroundColourId).isOpaque()); repaint(); } void TextEditor::setCaretVisible (const bool shouldCaretBeVisible) { caretVisible = shouldCaretBeVisible; if (shouldCaretBeVisible) textHolder->startTimer (TextEditorDefs::flashSpeedIntervalMs); setMouseCursor (shouldCaretBeVisible ? MouseCursor::IBeamCursor : MouseCursor::NormalCursor); } void TextEditor::setInputRestrictions (const int maxLen, const String& chars) { maxTextLength = jmax (0, maxLen); allowedCharacters = chars; } void TextEditor::setTextToShowWhenEmpty (const String& text, const Colour& colourToUse) { textToShowWhenEmpty = text; colourForTextWhenEmpty = colourToUse; } void TextEditor::setPasswordCharacter (const juce_wchar newPasswordCharacter) { if (passwordCharacter != newPasswordCharacter) { passwordCharacter = newPasswordCharacter; resized(); repaint(); } } void TextEditor::setScrollBarThickness (const int newThicknessPixels) { viewport->setScrollBarThickness (newThicknessPixels); } void TextEditor::setScrollBarButtonVisibility (const bool buttonsVisible) { viewport->setScrollBarButtonVisibility (buttonsVisible); } void TextEditor::clear() { clearInternal (0); updateTextHolderSize(); undoManager.clearUndoHistory(); } void TextEditor::setText (const String& newText, const bool sendTextChangeMessage) { const int newLength = newText.length(); if (newLength != getTotalNumChars() || getText() != newText) { const int oldCursorPos = caretPosition; const bool cursorWasAtEnd = oldCursorPos >= getTotalNumChars(); clearInternal (0); insert (newText, 0, currentFont, findColour (textColourId), 0, caretPosition); // if you're adding text with line-feeds to a single-line text editor, it // ain't gonna look right! jassert (multiline || ! newText.containsAnyOf ("\r\n")); if (cursorWasAtEnd && ! isMultiLine()) moveCursorTo (getTotalNumChars(), false); else moveCursorTo (oldCursorPos, false); if (sendTextChangeMessage) textChanged(); updateTextHolderSize(); scrollToMakeSureCursorIsVisible(); undoManager.clearUndoHistory(); repaint(); } } Value& TextEditor::getTextValue() { if (valueTextNeedsUpdating) { valueTextNeedsUpdating = false; textValue = getText(); } return textValue; } void TextEditor::textWasChangedByValue() { if (textValue.getValueSource().getReferenceCount() > 1) setText (textValue.getValue()); } void TextEditor::textChanged() { updateTextHolderSize(); postCommandMessage (TextEditorDefs::textChangeMessageId); if (textValue.getValueSource().getReferenceCount() > 1) { valueTextNeedsUpdating = false; textValue = getText(); } } void TextEditor::returnPressed() { postCommandMessage (TextEditorDefs::returnKeyMessageId); } void TextEditor::escapePressed() { postCommandMessage (TextEditorDefs::escapeKeyMessageId); } void TextEditor::addListener (Listener* const newListener) { listeners.add (newListener); } void TextEditor::removeListener (Listener* const listenerToRemove) { listeners.remove (listenerToRemove); } void TextEditor::timerCallbackInt() { const bool newState = (! caretFlashState) && ! isCurrentlyBlockedByAnotherModalComponent(); if (caretFlashState != newState) { caretFlashState = newState; if (caretFlashState) wasFocused = true; if (caretVisible && hasKeyboardFocus (false) && ! isReadOnly()) { repaintCaret(); } } const unsigned int now = Time::getApproximateMillisecondCounter(); if (now > lastTransactionTime + 200) newTransaction(); } void TextEditor::repaintCaret() { if (! findColour (caretColourId).isTransparent()) repaint (borderSize.getLeft() + textHolder->getX() + leftIndent + roundToInt (cursorX) - 1, borderSize.getTop() + textHolder->getY() + topIndent + roundToInt (cursorY) - 1, 4, roundToInt (cursorHeight) + 2); } void TextEditor::repaintText (const Range& range) { if (! range.isEmpty()) { float x = 0, y = 0, lh = currentFont.getHeight(); const float wordWrapWidth = getWordWrapWidth(); if (wordWrapWidth > 0) { Iterator i (sections, wordWrapWidth, passwordCharacter); i.getCharPosition (range.getStart(), x, y, lh); const int y1 = (int) y; int y2; if (range.getEnd() >= getTotalNumChars()) { y2 = textHolder->getHeight(); } else { i.getCharPosition (range.getEnd(), x, y, lh); y2 = (int) (y + lh * 2.0f); } textHolder->repaint (0, y1, textHolder->getWidth(), y2 - y1); } } } void TextEditor::moveCaret (int newCaretPos) { if (newCaretPos < 0) newCaretPos = 0; else if (newCaretPos > getTotalNumChars()) newCaretPos = getTotalNumChars(); if (newCaretPos != getCaretPosition()) { repaintCaret(); caretFlashState = true; caretPosition = newCaretPos; textHolder->startTimer (TextEditorDefs::flashSpeedIntervalMs); scrollToMakeSureCursorIsVisible(); repaintCaret(); } } void TextEditor::setCaretPosition (const int newIndex) { moveCursorTo (newIndex, false); } int TextEditor::getCaretPosition() const { return caretPosition; } void TextEditor::scrollEditorToPositionCaret (const int desiredCaretX, const int desiredCaretY) { updateCaretPosition(); int vx = roundToInt (cursorX) - desiredCaretX; int vy = roundToInt (cursorY) - desiredCaretY; if (desiredCaretX < jmax (1, proportionOfWidth (0.05f))) { vx += desiredCaretX - proportionOfWidth (0.2f); } else if (desiredCaretX > jmax (0, viewport->getMaximumVisibleWidth() - (wordWrap ? 2 : 10))) { vx += desiredCaretX + (isMultiLine() ? proportionOfWidth (0.2f) : 10) - viewport->getMaximumVisibleWidth(); } vx = jlimit (0, jmax (0, textHolder->getWidth() + 8 - viewport->getMaximumVisibleWidth()), vx); if (! isMultiLine()) { vy = viewport->getViewPositionY(); } else { vy = jlimit (0, jmax (0, textHolder->getHeight() - viewport->getMaximumVisibleHeight()), vy); const int curH = roundToInt (cursorHeight); if (desiredCaretY < 0) { vy = jmax (0, desiredCaretY + vy); } else if (desiredCaretY > jmax (0, viewport->getMaximumVisibleHeight() - topIndent - curH)) { vy += desiredCaretY + 2 + curH + topIndent - viewport->getMaximumVisibleHeight(); } } viewport->setViewPosition (vx, vy); } const Rectangle TextEditor::getCaretRectangle() { updateCaretPosition(); return Rectangle (roundToInt (cursorX) - viewport->getX(), roundToInt (cursorY) - viewport->getY(), 1, roundToInt (cursorHeight)); } float TextEditor::getWordWrapWidth() const { return (wordWrap) ? (float) (viewport->getMaximumVisibleWidth() - leftIndent - leftIndent / 2) : 1.0e10f; } void TextEditor::updateTextHolderSize() { const float wordWrapWidth = getWordWrapWidth(); if (wordWrapWidth > 0) { float maxWidth = 0.0f; Iterator i (sections, wordWrapWidth, passwordCharacter); while (i.next()) maxWidth = jmax (maxWidth, i.atomRight); const int w = leftIndent + roundToInt (maxWidth); const int h = topIndent + roundToInt (jmax (i.lineY + i.lineHeight, currentFont.getHeight())); textHolder->setSize (w + 1, h + 1); } } int TextEditor::getTextWidth() const { return textHolder->getWidth(); } int TextEditor::getTextHeight() const { return textHolder->getHeight(); } void TextEditor::setIndents (const int newLeftIndent, const int newTopIndent) { leftIndent = newLeftIndent; topIndent = newTopIndent; } void TextEditor::setBorder (const BorderSize& border) { borderSize = border; resized(); } const BorderSize TextEditor::getBorder() const { return borderSize; } void TextEditor::setScrollToShowCursor (const bool shouldScrollToShowCursor) { keepCursorOnScreen = shouldScrollToShowCursor; } void TextEditor::updateCaretPosition() { cursorHeight = currentFont.getHeight(); // (in case the text is empty and the call below doesn't set this value) getCharPosition (caretPosition, cursorX, cursorY, cursorHeight); } void TextEditor::scrollToMakeSureCursorIsVisible() { updateCaretPosition(); if (keepCursorOnScreen) { int x = viewport->getViewPositionX(); int y = viewport->getViewPositionY(); const int relativeCursorX = roundToInt (cursorX) - x; const int relativeCursorY = roundToInt (cursorY) - y; if (relativeCursorX < jmax (1, proportionOfWidth (0.05f))) { x += relativeCursorX - proportionOfWidth (0.2f); } else if (relativeCursorX > jmax (0, viewport->getMaximumVisibleWidth() - (wordWrap ? 2 : 10))) { x += relativeCursorX + (isMultiLine() ? proportionOfWidth (0.2f) : 10) - viewport->getMaximumVisibleWidth(); } x = jlimit (0, jmax (0, textHolder->getWidth() + 8 - viewport->getMaximumVisibleWidth()), x); if (! isMultiLine()) { y = (getHeight() - textHolder->getHeight() - topIndent) / -2; } else { const int curH = roundToInt (cursorHeight); if (relativeCursorY < 0) { y = jmax (0, relativeCursorY + y); } else if (relativeCursorY > jmax (0, viewport->getMaximumVisibleHeight() - topIndent - curH)) { y += relativeCursorY + 2 + curH + topIndent - viewport->getMaximumVisibleHeight(); } } viewport->setViewPosition (x, y); } } void TextEditor::moveCursorTo (const int newPosition, const bool isSelecting) { if (isSelecting) { moveCaret (newPosition); const Range oldSelection (selection); if (dragType == notDragging) { if (abs (getCaretPosition() - selection.getStart()) < abs (getCaretPosition() - selection.getEnd())) dragType = draggingSelectionStart; else dragType = draggingSelectionEnd; } if (dragType == draggingSelectionStart) { if (getCaretPosition() >= selection.getEnd()) dragType = draggingSelectionEnd; selection = Range::between (getCaretPosition(), selection.getEnd()); } else { if (getCaretPosition() < selection.getStart()) dragType = draggingSelectionStart; selection = Range::between (getCaretPosition(), selection.getStart()); } repaintText (selection.getUnionWith (oldSelection)); } else { dragType = notDragging; repaintText (selection); moveCaret (newPosition); selection = Range::emptyRange (getCaretPosition()); } } int TextEditor::getTextIndexAt (const int x, const int y) { return indexAtPosition ((float) (x + viewport->getViewPositionX() - leftIndent), (float) (y + viewport->getViewPositionY() - topIndent)); } void TextEditor::insertTextAtCaret (const String& newText_) { String newText (newText_); if (allowedCharacters.isNotEmpty()) newText = newText.retainCharacters (allowedCharacters); if ((! returnKeyStartsNewLine) && newText == "\n") { returnPressed(); return; } if (! isMultiLine()) newText = newText.replaceCharacters ("\r\n", " "); else newText = newText.replace ("\r\n", "\n"); const int newCaretPos = selection.getStart() + newText.length(); const int insertIndex = selection.getStart(); remove (selection, getUndoManager(), newText.isNotEmpty() ? newCaretPos - 1 : newCaretPos); if (maxTextLength > 0) newText = newText.substring (0, maxTextLength - getTotalNumChars()); if (newText.isNotEmpty()) insert (newText, insertIndex, currentFont, findColour (textColourId), getUndoManager(), newCaretPos); textChanged(); } void TextEditor::setHighlightedRegion (const Range& newSelection) { moveCursorTo (newSelection.getStart(), false); moveCursorTo (newSelection.getEnd(), true); } void TextEditor::copy() { if (passwordCharacter == 0) { const String selectedText (getHighlightedText()); if (selectedText.isNotEmpty()) SystemClipboard::copyTextToClipboard (selectedText); } } void TextEditor::paste() { if (! isReadOnly()) { const String clip (SystemClipboard::getTextFromClipboard()); if (clip.isNotEmpty()) insertTextAtCaret (clip); } } void TextEditor::cut() { if (! isReadOnly()) { moveCaret (selection.getEnd()); insertTextAtCaret (String::empty); } } void TextEditor::drawContent (Graphics& g) { const float wordWrapWidth = getWordWrapWidth(); if (wordWrapWidth > 0) { g.setOrigin (leftIndent, topIndent); const Rectangle clip (g.getClipBounds()); Colour selectedTextColour; Iterator i (sections, wordWrapWidth, passwordCharacter); while (i.lineY + 200.0 < clip.getY() && i.next()) {} if (! selection.isEmpty()) { g.setColour (findColour (highlightColourId) .withMultipliedAlpha (hasKeyboardFocus (true) ? 1.0f : 0.5f)); selectedTextColour = findColour (highlightedTextColourId); Iterator i2 (i); while (i2.next() && i2.lineY < clip.getBottom()) { if (i2.lineY + i2.lineHeight >= clip.getY() && selection.intersects (Range (i2.indexInText, i2.indexInText + i2.atom->numChars))) { i2.drawSelection (g, selection); } } } const UniformTextSection* lastSection = 0; while (i.next() && i.lineY < clip.getBottom()) { if (i.lineY + i.lineHeight >= clip.getY()) { if (selection.intersects (Range (i.indexInText, i.indexInText + i.atom->numChars))) { i.drawSelectedText (g, selection, selectedTextColour); lastSection = 0; } else { i.draw (g, lastSection); } } } } } void TextEditor::paint (Graphics& g) { getLookAndFeel().fillTextEditorBackground (g, getWidth(), getHeight(), *this); } void TextEditor::paintOverChildren (Graphics& g) { if (caretFlashState && hasKeyboardFocus (false) && caretVisible && ! isReadOnly()) { g.setColour (findColour (caretColourId)); g.fillRect (borderSize.getLeft() + textHolder->getX() + leftIndent + cursorX, borderSize.getTop() + textHolder->getY() + topIndent + cursorY, 2.0f, cursorHeight); } if (textToShowWhenEmpty.isNotEmpty() && (! hasKeyboardFocus (false)) && getTotalNumChars() == 0) { g.setColour (colourForTextWhenEmpty); g.setFont (getFont()); if (isMultiLine()) { g.drawText (textToShowWhenEmpty, 0, 0, getWidth(), getHeight(), Justification::centred, true); } else { g.drawText (textToShowWhenEmpty, leftIndent, topIndent, viewport->getWidth() - leftIndent, viewport->getHeight() - topIndent, Justification::centredLeft, true); } } getLookAndFeel().drawTextEditorOutline (g, getWidth(), getHeight(), *this); } class TextEditorMenuPerformer : public ModalComponentManager::Callback { public: TextEditorMenuPerformer (TextEditor* const editor_) : editor (editor_) { } void modalStateFinished (int returnValue) { if (editor != 0 && returnValue != 0) editor->performPopupMenuAction (returnValue); } private: Component::SafePointer editor; TextEditorMenuPerformer (const TextEditorMenuPerformer&); TextEditorMenuPerformer& operator= (const TextEditorMenuPerformer&); }; void TextEditor::mouseDown (const MouseEvent& e) { beginDragAutoRepeat (100); newTransaction(); if (wasFocused || ! selectAllTextWhenFocused) { if (! (popupMenuEnabled && e.mods.isPopupMenu())) { moveCursorTo (getTextIndexAt (e.x, e.y), e.mods.isShiftDown()); } else { PopupMenu m; m.setLookAndFeel (&getLookAndFeel()); addPopupMenuItems (m, &e); m.show (0, 0, 0, 0, new TextEditorMenuPerformer (this)); } } } void TextEditor::mouseDrag (const MouseEvent& e) { if (wasFocused || ! selectAllTextWhenFocused) { if (! (popupMenuEnabled && e.mods.isPopupMenu())) { moveCursorTo (getTextIndexAt (e.x, e.y), true); } } } void TextEditor::mouseUp (const MouseEvent& e) { newTransaction(); textHolder->startTimer (TextEditorDefs::flashSpeedIntervalMs); if (wasFocused || ! selectAllTextWhenFocused) { if (e.mouseWasClicked() && ! (popupMenuEnabled && e.mods.isPopupMenu())) { moveCaret (getTextIndexAt (e.x, e.y)); } } wasFocused = true; } void TextEditor::mouseDoubleClick (const MouseEvent& e) { int tokenEnd = getTextIndexAt (e.x, e.y); int tokenStart = tokenEnd; if (e.getNumberOfClicks() > 3) { tokenStart = 0; tokenEnd = getTotalNumChars(); } else { const String t (getText()); const int totalLength = getTotalNumChars(); while (tokenEnd < totalLength) { // (note the slight bodge here - it's because iswalnum only checks for alphabetic chars in the current locale) if (CharacterFunctions::isLetterOrDigit (t [tokenEnd]) || t [tokenEnd] > 128) ++tokenEnd; else break; } tokenStart = tokenEnd; while (tokenStart > 0) { // (note the slight bodge here - it's because iswalnum only checks for alphabetic chars in the current locale) if (CharacterFunctions::isLetterOrDigit (t [tokenStart - 1]) || t [tokenStart - 1] > 128) --tokenStart; else break; } if (e.getNumberOfClicks() > 2) { while (tokenEnd < totalLength) { if (t [tokenEnd] != '\r' && t [tokenEnd] != '\n') ++tokenEnd; else break; } while (tokenStart > 0) { if (t [tokenStart - 1] != '\r' && t [tokenStart - 1] != '\n') --tokenStart; else break; } } } moveCursorTo (tokenEnd, false); moveCursorTo (tokenStart, true); } void TextEditor::mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY) { if (! viewport->useMouseWheelMoveIfNeeded (e, wheelIncrementX, wheelIncrementY)) Component::mouseWheelMove (e, wheelIncrementX, wheelIncrementY); } bool TextEditor::keyPressed (const KeyPress& key) { if (isReadOnly() && key != KeyPress ('c', ModifierKeys::commandModifier, 0)) return false; const bool moveInWholeWordSteps = key.getModifiers().isCtrlDown() || key.getModifiers().isAltDown(); if (key.isKeyCode (KeyPress::leftKey) || key.isKeyCode (KeyPress::upKey)) { newTransaction(); int newPos; if (isMultiLine() && key.isKeyCode (KeyPress::upKey)) newPos = indexAtPosition (cursorX, cursorY - 1); else if (moveInWholeWordSteps) newPos = findWordBreakBefore (getCaretPosition()); else newPos = getCaretPosition() - 1; moveCursorTo (newPos, key.getModifiers().isShiftDown()); } else if (key.isKeyCode (KeyPress::rightKey) || key.isKeyCode (KeyPress::downKey)) { newTransaction(); int newPos; if (isMultiLine() && key.isKeyCode (KeyPress::downKey)) newPos = indexAtPosition (cursorX, cursorY + cursorHeight + 1); else if (moveInWholeWordSteps) newPos = findWordBreakAfter (getCaretPosition()); else newPos = getCaretPosition() + 1; moveCursorTo (newPos, key.getModifiers().isShiftDown()); } else if (key.isKeyCode (KeyPress::pageDownKey) && isMultiLine()) { newTransaction(); moveCursorTo (indexAtPosition (cursorX, cursorY + cursorHeight + viewport->getViewHeight()), key.getModifiers().isShiftDown()); } else if (key.isKeyCode (KeyPress::pageUpKey) && isMultiLine()) { newTransaction(); moveCursorTo (indexAtPosition (cursorX, cursorY - viewport->getViewHeight()), key.getModifiers().isShiftDown()); } else if (key.isKeyCode (KeyPress::homeKey)) { newTransaction(); if (isMultiLine() && ! moveInWholeWordSteps) moveCursorTo (indexAtPosition (0.0f, cursorY), key.getModifiers().isShiftDown()); else moveCursorTo (0, key.getModifiers().isShiftDown()); } else if (key.isKeyCode (KeyPress::endKey)) { newTransaction(); if (isMultiLine() && ! moveInWholeWordSteps) moveCursorTo (indexAtPosition ((float) textHolder->getWidth(), cursorY), key.getModifiers().isShiftDown()); else moveCursorTo (getTotalNumChars(), key.getModifiers().isShiftDown()); } else if (key.isKeyCode (KeyPress::backspaceKey)) { if (moveInWholeWordSteps) { moveCursorTo (findWordBreakBefore (getCaretPosition()), true); } else { if (selection.isEmpty() && selection.getStart() > 0) selection.setStart (selection.getEnd() - 1); } cut(); } else if (key.isKeyCode (KeyPress::deleteKey)) { if (key.getModifiers().isShiftDown()) copy(); if (selection.isEmpty() && selection.getStart() < getTotalNumChars()) selection.setEnd (selection.getStart() + 1); cut(); } else if (key == KeyPress ('c', ModifierKeys::commandModifier, 0) || key == KeyPress (KeyPress::insertKey, ModifierKeys::ctrlModifier, 0)) { newTransaction(); copy(); } else if (key == KeyPress ('x', ModifierKeys::commandModifier, 0)) { newTransaction(); copy(); cut(); } else if (key == KeyPress ('v', ModifierKeys::commandModifier, 0) || key == KeyPress (KeyPress::insertKey, ModifierKeys::shiftModifier, 0)) { newTransaction(); paste(); } else if (key == KeyPress ('z', ModifierKeys::commandModifier, 0)) { newTransaction(); doUndoRedo (false); } else if (key == KeyPress ('y', ModifierKeys::commandModifier, 0)) { newTransaction(); doUndoRedo (true); } else if (key == KeyPress ('a', ModifierKeys::commandModifier, 0)) { newTransaction(); moveCursorTo (getTotalNumChars(), false); moveCursorTo (0, true); } else if (key == KeyPress::returnKey) { newTransaction(); insertTextAtCaret ("\n"); } else if (key.isKeyCode (KeyPress::escapeKey)) { newTransaction(); moveCursorTo (getCaretPosition(), false); escapePressed(); } else if (key.getTextCharacter() >= ' ' || (tabKeyUsed && (key.getTextCharacter() == '\t'))) { insertTextAtCaret (String::charToString (key.getTextCharacter())); lastTransactionTime = Time::getApproximateMillisecondCounter(); } else { return false; } return true; } bool TextEditor::keyStateChanged (const bool isKeyDown) { if (! isKeyDown) return false; #if JUCE_WINDOWS if (KeyPress (KeyPress::F4Key, ModifierKeys::altModifier, 0).isCurrentlyDown()) return false; // We need to explicitly allow alt-F4 to pass through on Windows #endif // (overridden to avoid forwarding key events to the parent) return ! ModifierKeys::getCurrentModifiers().isCommandDown(); } const int baseMenuItemID = 0x7fff0000; void TextEditor::addPopupMenuItems (PopupMenu& m, const MouseEvent*) { const bool writable = ! isReadOnly(); if (passwordCharacter == 0) { m.addItem (baseMenuItemID + 1, TRANS("cut"), writable); m.addItem (baseMenuItemID + 2, TRANS("copy"), ! selection.isEmpty()); m.addItem (baseMenuItemID + 3, TRANS("paste"), writable); } m.addItem (baseMenuItemID + 4, TRANS("delete"), writable); m.addSeparator(); m.addItem (baseMenuItemID + 5, TRANS("select all")); m.addSeparator(); if (getUndoManager() != 0) { m.addItem (baseMenuItemID + 6, TRANS("undo"), undoManager.canUndo()); m.addItem (baseMenuItemID + 7, TRANS("redo"), undoManager.canRedo()); } } void TextEditor::performPopupMenuAction (const int menuItemID) { switch (menuItemID) { case baseMenuItemID + 1: copy(); cut(); break; case baseMenuItemID + 2: copy(); break; case baseMenuItemID + 3: paste(); break; case baseMenuItemID + 4: cut(); break; case baseMenuItemID + 5: moveCursorTo (getTotalNumChars(), false); moveCursorTo (0, true); break; case baseMenuItemID + 6: doUndoRedo (false); break; case baseMenuItemID + 7: doUndoRedo (true); break; default: break; } } void TextEditor::focusGained (FocusChangeType) { newTransaction(); caretFlashState = true; if (selectAllTextWhenFocused) { moveCursorTo (0, false); moveCursorTo (getTotalNumChars(), true); } repaint(); if (caretVisible) textHolder->startTimer (TextEditorDefs::flashSpeedIntervalMs); ComponentPeer* const peer = getPeer(); if (peer != 0 && ! isReadOnly()) peer->textInputRequired (getScreenPosition() - peer->getScreenPosition()); } void TextEditor::focusLost (FocusChangeType) { newTransaction(); wasFocused = false; textHolder->stopTimer(); caretFlashState = false; postCommandMessage (TextEditorDefs::focusLossMessageId); repaint(); } void TextEditor::resized() { viewport->setBoundsInset (borderSize); viewport->setSingleStepSizes (16, roundToInt (currentFont.getHeight())); updateTextHolderSize(); if (! isMultiLine()) { scrollToMakeSureCursorIsVisible(); } else { updateCaretPosition(); } } void TextEditor::handleCommandMessage (const int commandId) { Component::BailOutChecker checker (this); switch (commandId) { case TextEditorDefs::textChangeMessageId: listeners.callChecked (checker, &TextEditor::Listener::textEditorTextChanged, (TextEditor&) *this); break; case TextEditorDefs::returnKeyMessageId: listeners.callChecked (checker, &TextEditor::Listener::textEditorReturnKeyPressed, (TextEditor&) *this); break; case TextEditorDefs::escapeKeyMessageId: listeners.callChecked (checker, &TextEditor::Listener::textEditorEscapeKeyPressed, (TextEditor&) *this); break; case TextEditorDefs::focusLossMessageId: listeners.callChecked (checker, &TextEditor::Listener::textEditorFocusLost, (TextEditor&) *this); break; default: jassertfalse; break; } } void TextEditor::enablementChanged() { setMouseCursor (isReadOnly() ? MouseCursor::NormalCursor : MouseCursor::IBeamCursor); repaint(); } UndoManager* TextEditor::getUndoManager() throw() { return isReadOnly() ? 0 : &undoManager; } void TextEditor::clearInternal (UndoManager* const um) { remove (Range (0, getTotalNumChars()), um, caretPosition); } void TextEditor::insert (const String& text, const int insertIndex, const Font& font, const Colour& colour, UndoManager* const um, const int caretPositionToMoveTo) { if (text.isNotEmpty()) { if (um != 0) { if (um->getNumActionsInCurrentTransaction() > TextEditorDefs::maxActionsPerTransaction) newTransaction(); um->perform (new InsertAction (*this, text, insertIndex, font, colour, caretPosition, caretPositionToMoveTo)); } else { repaintText (Range (insertIndex, getTotalNumChars())); // must do this before and after changing the data, in case // a line gets moved due to word wrap int index = 0; int nextIndex = 0; for (int i = 0; i < sections.size(); ++i) { nextIndex = index + sections.getUnchecked (i)->getTotalLength(); if (insertIndex == index) { sections.insert (i, new UniformTextSection (text, font, colour, passwordCharacter)); break; } else if (insertIndex > index && insertIndex < nextIndex) { splitSection (i, insertIndex - index); sections.insert (i + 1, new UniformTextSection (text, font, colour, passwordCharacter)); break; } index = nextIndex; } if (nextIndex == insertIndex) sections.add (new UniformTextSection (text, font, colour, passwordCharacter)); coalesceSimilarSections(); totalNumChars = -1; valueTextNeedsUpdating = true; moveCursorTo (caretPositionToMoveTo, false); repaintText (Range (insertIndex, getTotalNumChars())); } } } void TextEditor::reinsert (const int insertIndex, const Array & sectionsToInsert) { int index = 0; int nextIndex = 0; for (int i = 0; i < sections.size(); ++i) { nextIndex = index + sections.getUnchecked (i)->getTotalLength(); if (insertIndex == index) { for (int j = sectionsToInsert.size(); --j >= 0;) sections.insert (i, new UniformTextSection (*sectionsToInsert.getUnchecked(j))); break; } else if (insertIndex > index && insertIndex < nextIndex) { splitSection (i, insertIndex - index); for (int j = sectionsToInsert.size(); --j >= 0;) sections.insert (i + 1, new UniformTextSection (*sectionsToInsert.getUnchecked(j))); break; } index = nextIndex; } if (nextIndex == insertIndex) { for (int j = 0; j < sectionsToInsert.size(); ++j) sections.add (new UniformTextSection (*sectionsToInsert.getUnchecked(j))); } coalesceSimilarSections(); totalNumChars = -1; valueTextNeedsUpdating = true; } void TextEditor::remove (const Range& range, UndoManager* const um, const int caretPositionToMoveTo) { if (! range.isEmpty()) { int index = 0; for (int i = 0; i < sections.size(); ++i) { const int nextIndex = index + sections.getUnchecked(i)->getTotalLength(); if (range.getStart() > index && range.getStart() < nextIndex) { splitSection (i, range.getStart() - index); --i; } else if (range.getEnd() > index && range.getEnd() < nextIndex) { splitSection (i, range.getEnd() - index); --i; } else { index = nextIndex; if (index > range.getEnd()) break; } } index = 0; if (um != 0) { Array removedSections; for (int i = 0; i < sections.size(); ++i) { if (range.getEnd() <= range.getStart()) break; UniformTextSection* const section = sections.getUnchecked (i); const int nextIndex = index + section->getTotalLength(); if (range.getStart() <= index && range.getEnd() >= nextIndex) removedSections.add (new UniformTextSection (*section)); index = nextIndex; } if (um->getNumActionsInCurrentTransaction() > TextEditorDefs::maxActionsPerTransaction) newTransaction(); um->perform (new RemoveAction (*this, range, caretPosition, caretPositionToMoveTo, removedSections)); } else { Range remainingRange (range); for (int i = 0; i < sections.size(); ++i) { UniformTextSection* const section = sections.getUnchecked (i); const int nextIndex = index + section->getTotalLength(); if (remainingRange.getStart() <= index && remainingRange.getEnd() >= nextIndex) { sections.remove(i); section->clear(); delete section; remainingRange.setEnd (remainingRange.getEnd() - (nextIndex - index)); if (remainingRange.isEmpty()) break; --i; } else { index = nextIndex; } } coalesceSimilarSections(); totalNumChars = -1; valueTextNeedsUpdating = true; moveCursorTo (caretPositionToMoveTo, false); repaintText (Range (range.getStart(), getTotalNumChars())); } } } const String TextEditor::getText() const { String t; t.preallocateStorage (getTotalNumChars()); String::Concatenator concatenator (t); for (int i = 0; i < sections.size(); ++i) sections.getUnchecked (i)->appendAllText (concatenator); return t; } const String TextEditor::getTextInRange (const Range& range) const { String t; if (! range.isEmpty()) { t.preallocateStorage (jmin (getTotalNumChars(), range.getLength())); String::Concatenator concatenator (t); int index = 0; for (int i = 0; i < sections.size(); ++i) { const UniformTextSection* const s = sections.getUnchecked (i); const int nextIndex = index + s->getTotalLength(); if (range.getStart() < nextIndex) { if (range.getEnd() <= index) break; s->appendSubstring (concatenator, range - index); } index = nextIndex; } } return t; } const String TextEditor::getHighlightedText() const { return getTextInRange (selection); } int TextEditor::getTotalNumChars() const { if (totalNumChars < 0) { totalNumChars = 0; for (int i = sections.size(); --i >= 0;) totalNumChars += sections.getUnchecked (i)->getTotalLength(); } return totalNumChars; } bool TextEditor::isEmpty() const { return getTotalNumChars() == 0; } void TextEditor::getCharPosition (const int index, float& cx, float& cy, float& lineHeight) const { const float wordWrapWidth = getWordWrapWidth(); if (wordWrapWidth > 0 && sections.size() > 0) { Iterator i (sections, wordWrapWidth, passwordCharacter); i.getCharPosition (index, cx, cy, lineHeight); } else { cx = cy = 0; lineHeight = currentFont.getHeight(); } } int TextEditor::indexAtPosition (const float x, const float y) { const float wordWrapWidth = getWordWrapWidth(); if (wordWrapWidth > 0) { Iterator i (sections, wordWrapWidth, passwordCharacter); while (i.next()) { if (i.lineY + i.lineHeight > y) { if (i.lineY > y) return jmax (0, i.indexInText - 1); if (i.atomX >= x) return i.indexInText; if (x < i.atomRight) return i.xToIndex (x); } } } return getTotalNumChars(); } int TextEditor::findWordBreakAfter (const int position) const { const String t (getTextInRange (Range (position, position + 512))); const int totalLength = t.length(); int i = 0; while (i < totalLength && CharacterFunctions::isWhitespace (t[i])) ++i; const int type = TextEditorDefs::getCharacterCategory (t[i]); while (i < totalLength && type == TextEditorDefs::getCharacterCategory (t[i])) ++i; while (i < totalLength && CharacterFunctions::isWhitespace (t[i])) ++i; return position + i; } int TextEditor::findWordBreakBefore (const int position) const { if (position <= 0) return 0; const int startOfBuffer = jmax (0, position - 512); const String t (getTextInRange (Range (startOfBuffer, position))); int i = position - startOfBuffer; while (i > 0 && CharacterFunctions::isWhitespace (t [i - 1])) --i; if (i > 0) { const int type = TextEditorDefs::getCharacterCategory (t [i - 1]); while (i > 0 && type == TextEditorDefs::getCharacterCategory (t [i - 1])) --i; } jassert (startOfBuffer + i >= 0); return startOfBuffer + i; } void TextEditor::splitSection (const int sectionIndex, const int charToSplitAt) { jassert (sections[sectionIndex] != 0); sections.insert (sectionIndex + 1, sections.getUnchecked (sectionIndex)->split (charToSplitAt, passwordCharacter)); } void TextEditor::coalesceSimilarSections() { for (int i = 0; i < sections.size() - 1; ++i) { UniformTextSection* const s1 = sections.getUnchecked (i); UniformTextSection* const s2 = sections.getUnchecked (i + 1); if (s1->font == s2->font && s1->colour == s2->colour) { s1->append (*s2, passwordCharacter); sections.remove (i + 1); delete s2; --i; } } } END_JUCE_NAMESPACE /*** End of inlined file: juce_TextEditor.cpp ***/ /*** Start of inlined file: juce_Toolbar.cpp ***/ BEGIN_JUCE_NAMESPACE const char* const Toolbar::toolbarDragDescriptor = "_toolbarItem_"; class ToolbarSpacerComp : public ToolbarItemComponent { public: ToolbarSpacerComp (const int itemId_, const float fixedSize_, const bool drawBar_) : ToolbarItemComponent (itemId_, String::empty, false), fixedSize (fixedSize_), drawBar (drawBar_) { } ~ToolbarSpacerComp() { } bool getToolbarItemSizes (int toolbarThickness, bool /*isToolbarVertical*/, int& preferredSize, int& minSize, int& maxSize) { if (fixedSize <= 0) { preferredSize = toolbarThickness * 2; minSize = 4; maxSize = 32768; } else { maxSize = roundToInt (toolbarThickness * fixedSize); minSize = drawBar ? maxSize : jmin (4, maxSize); preferredSize = maxSize; if (getEditingMode() == editableOnPalette) preferredSize = maxSize = toolbarThickness / (drawBar ? 3 : 2); } return true; } void paintButtonArea (Graphics&, int, int, bool, bool) { } void contentAreaChanged (const Rectangle&) { } int getResizeOrder() const throw() { return fixedSize <= 0 ? 0 : 1; } void paint (Graphics& g) { const int w = getWidth(); const int h = getHeight(); if (drawBar) { g.setColour (findColour (Toolbar::separatorColourId, true)); const float thickness = 0.2f; if (isToolbarVertical()) g.fillRect (w * 0.1f, h * (0.5f - thickness * 0.5f), w * 0.8f, h * thickness); else g.fillRect (w * (0.5f - thickness * 0.5f), h * 0.1f, w * thickness, h * 0.8f); } if (getEditingMode() != normalMode && ! drawBar) { g.setColour (findColour (Toolbar::separatorColourId, true)); const int indentX = jmin (2, (w - 3) / 2); const int indentY = jmin (2, (h - 3) / 2); g.drawRect (indentX, indentY, w - indentX * 2, h - indentY * 2, 1); if (fixedSize <= 0) { float x1, y1, x2, y2, x3, y3, x4, y4, hw, hl; if (isToolbarVertical()) { x1 = w * 0.5f; y1 = h * 0.4f; x2 = x1; y2 = indentX * 2.0f; x3 = x1; y3 = h * 0.6f; x4 = x1; y4 = h - y2; hw = w * 0.15f; hl = w * 0.2f; } else { x1 = w * 0.4f; y1 = h * 0.5f; x2 = indentX * 2.0f; y2 = y1; x3 = w * 0.6f; y3 = y1; x4 = w - x2; y4 = y1; hw = h * 0.15f; hl = h * 0.2f; } Path p; p.addArrow (Line (x1, y1, x2, y2), 1.5f, hw, hl); p.addArrow (Line (x3, y3, x4, y4), 1.5f, hw, hl); g.fillPath (p); } } } juce_UseDebuggingNewOperator private: const float fixedSize; const bool drawBar; ToolbarSpacerComp (const ToolbarSpacerComp&); ToolbarSpacerComp& operator= (const ToolbarSpacerComp&); }; class Toolbar::MissingItemsComponent : public PopupMenuCustomComponent { public: MissingItemsComponent (Toolbar& owner_, const int height_) : PopupMenuCustomComponent (true), owner (owner_), height (height_) { for (int i = owner_.items.size(); --i >= 0;) { ToolbarItemComponent* const tc = owner_.items.getUnchecked(i); if (dynamic_cast (tc) == 0 && ! tc->isVisible()) { oldIndexes.insert (0, i); addAndMakeVisible (tc, 0); } } layout (400); } ~MissingItemsComponent() { // deleting the toolbar while its menu it open?? jassert (owner.isValidComponent()); for (int i = 0; i < getNumChildComponents(); ++i) { ToolbarItemComponent* const tc = dynamic_cast (getChildComponent (i)); if (tc != 0) { tc->setVisible (false); const int index = oldIndexes.remove (i); owner.addChildComponent (tc, index); --i; } } owner.resized(); } void layout (const int preferredWidth) { const int indent = 8; int x = indent; int y = indent; int maxX = 0; for (int i = 0; i < getNumChildComponents(); ++i) { ToolbarItemComponent* const tc = dynamic_cast (getChildComponent (i)); if (tc != 0) { int preferredSize = 1, minSize = 1, maxSize = 1; if (tc->getToolbarItemSizes (height, false, preferredSize, minSize, maxSize)) { if (x + preferredSize > preferredWidth && x > indent) { x = indent; y += height; } tc->setBounds (x, y, preferredSize, height); x += preferredSize; maxX = jmax (maxX, x); } } } setSize (maxX + 8, y + height + 8); } void getIdealSize (int& idealWidth, int& idealHeight) { idealWidth = getWidth(); idealHeight = getHeight(); } juce_UseDebuggingNewOperator private: Toolbar& owner; const int height; Array oldIndexes; MissingItemsComponent (const MissingItemsComponent&); MissingItemsComponent& operator= (const MissingItemsComponent&); }; Toolbar::Toolbar() : vertical (false), isEditingActive (false), toolbarStyle (Toolbar::iconsOnly) { addChildComponent (missingItemsButton = getLookAndFeel().createToolbarMissingItemsButton (*this)); missingItemsButton->setAlwaysOnTop (true); missingItemsButton->addButtonListener (this); } Toolbar::~Toolbar() { animator.cancelAllAnimations (true); deleteAllChildren(); } void Toolbar::setVertical (const bool shouldBeVertical) { if (vertical != shouldBeVertical) { vertical = shouldBeVertical; resized(); } } void Toolbar::clear() { for (int i = items.size(); --i >= 0;) { ToolbarItemComponent* const tc = items.getUnchecked(i); items.remove (i); delete tc; } resized(); } ToolbarItemComponent* Toolbar::createItem (ToolbarItemFactory& factory, const int itemId) { if (itemId == ToolbarItemFactory::separatorBarId) return new ToolbarSpacerComp (itemId, 0.1f, true); else if (itemId == ToolbarItemFactory::spacerId) return new ToolbarSpacerComp (itemId, 0.5f, false); else if (itemId == ToolbarItemFactory::flexibleSpacerId) return new ToolbarSpacerComp (itemId, 0, false); return factory.createItem (itemId); } void Toolbar::addItemInternal (ToolbarItemFactory& factory, const int itemId, const int insertIndex) { // An ID can't be zero - this might indicate a mistake somewhere? jassert (itemId != 0); ToolbarItemComponent* const tc = createItem (factory, itemId); if (tc != 0) { #if JUCE_DEBUG Array allowedIds; factory.getAllToolbarItemIds (allowedIds); // If your factory can create an item for a given ID, it must also return // that ID from its getAllToolbarItemIds() method! jassert (allowedIds.contains (itemId)); #endif items.insert (insertIndex, tc); addAndMakeVisible (tc, insertIndex); } } void Toolbar::addItem (ToolbarItemFactory& factory, const int itemId, const int insertIndex) { addItemInternal (factory, itemId, insertIndex); resized(); } void Toolbar::addDefaultItems (ToolbarItemFactory& factoryToUse) { Array ids; factoryToUse.getDefaultItemSet (ids); clear(); for (int i = 0; i < ids.size(); ++i) addItemInternal (factoryToUse, ids.getUnchecked (i), -1); resized(); } void Toolbar::removeToolbarItem (const int itemIndex) { ToolbarItemComponent* const tc = getItemComponent (itemIndex); if (tc != 0) { items.removeValue (tc); delete tc; resized(); } } int Toolbar::getNumItems() const throw() { return items.size(); } int Toolbar::getItemId (const int itemIndex) const throw() { ToolbarItemComponent* const tc = getItemComponent (itemIndex); return tc != 0 ? tc->getItemId() : 0; } ToolbarItemComponent* Toolbar::getItemComponent (const int itemIndex) const throw() { return items [itemIndex]; } ToolbarItemComponent* Toolbar::getNextActiveComponent (int index, const int delta) const { for (;;) { index += delta; ToolbarItemComponent* const tc = getItemComponent (index); if (tc == 0) break; if (tc->isActive) return tc; } return 0; } void Toolbar::setStyle (const ToolbarItemStyle& newStyle) { if (toolbarStyle != newStyle) { toolbarStyle = newStyle; updateAllItemPositions (false); } } const String Toolbar::toString() const { String s ("TB:"); for (int i = 0; i < getNumItems(); ++i) s << getItemId(i) << ' '; return s.trimEnd(); } bool Toolbar::restoreFromString (ToolbarItemFactory& factoryToUse, const String& savedVersion) { if (! savedVersion.startsWith ("TB:")) return false; StringArray tokens; tokens.addTokens (savedVersion.substring (3), false); clear(); for (int i = 0; i < tokens.size(); ++i) addItemInternal (factoryToUse, tokens[i].getIntValue(), -1); resized(); return true; } void Toolbar::paint (Graphics& g) { getLookAndFeel().paintToolbarBackground (g, getWidth(), getHeight(), *this); } int Toolbar::getThickness() const throw() { return vertical ? getWidth() : getHeight(); } int Toolbar::getLength() const throw() { return vertical ? getHeight() : getWidth(); } void Toolbar::setEditingActive (const bool active) { if (isEditingActive != active) { isEditingActive = active; updateAllItemPositions (false); } } void Toolbar::resized() { updateAllItemPositions (false); } void Toolbar::updateAllItemPositions (const bool animate) { if (getWidth() > 0 && getHeight() > 0) { StretchableObjectResizer resizer; int i; for (i = 0; i < items.size(); ++i) { ToolbarItemComponent* const tc = items.getUnchecked(i); tc->setEditingMode (isEditingActive ? ToolbarItemComponent::editableOnToolbar : ToolbarItemComponent::normalMode); tc->setStyle (toolbarStyle); ToolbarSpacerComp* const spacer = dynamic_cast (tc); int preferredSize = 1, minSize = 1, maxSize = 1; if (tc->getToolbarItemSizes (getThickness(), isVertical(), preferredSize, minSize, maxSize)) { tc->isActive = true; resizer.addItem (preferredSize, minSize, maxSize, spacer != 0 ? spacer->getResizeOrder() : 2); } else { tc->isActive = false; tc->setVisible (false); } } resizer.resizeToFit (getLength()); int totalLength = 0; for (i = 0; i < resizer.getNumItems(); ++i) totalLength += (int) resizer.getItemSize (i); const bool itemsOffTheEnd = totalLength > getLength(); const int extrasButtonSize = getThickness() / 2; missingItemsButton->setSize (extrasButtonSize, extrasButtonSize); missingItemsButton->setVisible (itemsOffTheEnd); missingItemsButton->setEnabled (! isEditingActive); if (vertical) missingItemsButton->setCentrePosition (getWidth() / 2, getHeight() - 4 - extrasButtonSize / 2); else missingItemsButton->setCentrePosition (getWidth() - 4 - extrasButtonSize / 2, getHeight() / 2); const int maxLength = itemsOffTheEnd ? (vertical ? missingItemsButton->getY() : missingItemsButton->getX()) - 4 : getLength(); int pos = 0, activeIndex = 0; for (i = 0; i < items.size(); ++i) { ToolbarItemComponent* const tc = items.getUnchecked(i); if (tc->isActive) { const int size = (int) resizer.getItemSize (activeIndex++); Rectangle newBounds; if (vertical) newBounds.setBounds (0, pos, getWidth(), size); else newBounds.setBounds (pos, 0, size, getHeight()); if (animate) { animator.animateComponent (tc, newBounds, 200, 3.0, 0.0); } else { animator.cancelAnimation (tc, false); tc->setBounds (newBounds); } pos += size; tc->setVisible (pos <= maxLength && ((! tc->isBeingDragged) || tc->getEditingMode() == ToolbarItemComponent::editableOnPalette)); } } } } void Toolbar::buttonClicked (Button*) { jassert (missingItemsButton->isShowing()); if (missingItemsButton->isShowing()) { PopupMenu m; m.addCustomItem (1, new MissingItemsComponent (*this, getThickness())); m.showAt (missingItemsButton); } } bool Toolbar::isInterestedInDragSource (const String& sourceDescription, Component* /*sourceComponent*/) { return sourceDescription == toolbarDragDescriptor && isEditingActive; } void Toolbar::itemDragMove (const String&, Component* sourceComponent, int x, int y) { ToolbarItemComponent* const tc = dynamic_cast (sourceComponent); if (tc != 0) { if (getNumItems() == 0) { if (tc->getEditingMode() == ToolbarItemComponent::editableOnPalette) { ToolbarItemPalette* const palette = tc->findParentComponentOfClass ((ToolbarItemPalette*) 0); if (palette != 0) palette->replaceComponent (tc); } else { jassert (tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar); } items.add (tc); addChildComponent (tc); updateAllItemPositions (false); } else { for (int i = getNumItems(); --i >= 0;) { int currentIndex = getIndexOfChildComponent (tc); if (currentIndex < 0) { if (tc->getEditingMode() == ToolbarItemComponent::editableOnPalette) { ToolbarItemPalette* const palette = tc->findParentComponentOfClass ((ToolbarItemPalette*) 0); if (palette != 0) palette->replaceComponent (tc); } else { jassert (tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar); } items.add (tc); addChildComponent (tc); currentIndex = getIndexOfChildComponent (tc); updateAllItemPositions (true); } int newIndex = currentIndex; const int dragObjectLeft = vertical ? (y - tc->dragOffsetY) : (x - tc->dragOffsetX); const int dragObjectRight = dragObjectLeft + (vertical ? tc->getHeight() : tc->getWidth()); const Rectangle current (animator.getComponentDestination (getChildComponent (newIndex))); ToolbarItemComponent* const prev = getNextActiveComponent (newIndex, -1); if (prev != 0) { const Rectangle previousPos (animator.getComponentDestination (prev)); if (abs (dragObjectLeft - (vertical ? previousPos.getY() : previousPos.getX()) < abs (dragObjectRight - (vertical ? current.getBottom() : current.getRight())))) { newIndex = getIndexOfChildComponent (prev); } } ToolbarItemComponent* const next = getNextActiveComponent (newIndex, 1); if (next != 0) { const Rectangle nextPos (animator.getComponentDestination (next)); if (abs (dragObjectLeft - (vertical ? current.getY() : current.getX()) > abs (dragObjectRight - (vertical ? nextPos.getBottom() : nextPos.getRight())))) { newIndex = getIndexOfChildComponent (next) + 1; } } if (newIndex != currentIndex) { items.removeValue (tc); removeChildComponent (tc); addChildComponent (tc, newIndex); items.insert (newIndex, tc); updateAllItemPositions (true); } else { break; } } } } } void Toolbar::itemDragExit (const String&, Component* sourceComponent) { ToolbarItemComponent* const tc = dynamic_cast (sourceComponent); if (tc != 0) { if (isParentOf (tc)) { items.removeValue (tc); removeChildComponent (tc); updateAllItemPositions (true); } } } void Toolbar::itemDropped (const String&, Component*, int, int) { } void Toolbar::mouseDown (const MouseEvent& e) { if (e.mods.isPopupMenu()) { } } class ToolbarCustomisationDialog : public DialogWindow { public: ToolbarCustomisationDialog (ToolbarItemFactory& factory, Toolbar* const toolbar_, const int optionFlags) : DialogWindow (TRANS("Add/remove items from toolbar"), Colours::white, true, true), toolbar (toolbar_) { setContentComponent (new CustomiserPanel (factory, toolbar, optionFlags), true, true); setResizable (true, true); setResizeLimits (400, 300, 1500, 1000); positionNearBar(); } ~ToolbarCustomisationDialog() { setContentComponent (0, true); } void closeButtonPressed() { setVisible (false); } bool canModalEventBeSentToComponent (const Component* comp) { return toolbar->isParentOf (comp); } void positionNearBar() { const Rectangle screenSize (toolbar->getParentMonitorArea()); const int tbx = toolbar->getScreenX(); const int tby = toolbar->getScreenY(); const int gap = 8; int x, y; if (toolbar->isVertical()) { y = tby; if (tbx > screenSize.getCentreX()) x = tbx - getWidth() - gap; else x = tbx + toolbar->getWidth() + gap; } else { x = tbx + (toolbar->getWidth() - getWidth()) / 2; if (tby > screenSize.getCentreY()) y = tby - getHeight() - gap; else y = tby + toolbar->getHeight() + gap; } setTopLeftPosition (x, y); } private: Toolbar* const toolbar; class CustomiserPanel : public Component, private ComboBoxListener, // (can't use ComboBox::Listener due to idiotic VC2005 bug) private ButtonListener { public: CustomiserPanel (ToolbarItemFactory& factory_, Toolbar* const toolbar_, const int optionFlags) : factory (factory_), toolbar (toolbar_), styleBox (0), defaultButton (0) { addAndMakeVisible (palette = new ToolbarItemPalette (factory, toolbar)); if ((optionFlags & (Toolbar::allowIconsOnlyChoice | Toolbar::allowIconsWithTextChoice | Toolbar::allowTextOnlyChoice)) != 0) { addAndMakeVisible (styleBox = new ComboBox (String::empty)); styleBox->setEditableText (false); if ((optionFlags & Toolbar::allowIconsOnlyChoice) != 0) styleBox->addItem (TRANS("Show icons only"), 1); if ((optionFlags & Toolbar::allowIconsWithTextChoice) != 0) styleBox->addItem (TRANS("Show icons and descriptions"), 2); if ((optionFlags & Toolbar::allowTextOnlyChoice) != 0) styleBox->addItem (TRANS("Show descriptions only"), 3); if (toolbar_->getStyle() == Toolbar::iconsOnly) styleBox->setSelectedId (1); else if (toolbar_->getStyle() == Toolbar::iconsWithText) styleBox->setSelectedId (2); else if (toolbar_->getStyle() == Toolbar::textOnly) styleBox->setSelectedId (3); styleBox->addListener (this); } if ((optionFlags & Toolbar::showResetToDefaultsButton) != 0) { addAndMakeVisible (defaultButton = new TextButton (TRANS ("Restore to default set of items"))); defaultButton->addButtonListener (this); } addAndMakeVisible (instructions = new Label (String::empty, TRANS ("You can drag the items above and drop them onto a toolbar to add them.\n\nItems on the toolbar can also be dragged around to change their order, or dragged off the edge to delete them."))); instructions->setFont (Font (13.0f)); setSize (500, 300); } ~CustomiserPanel() { deleteAllChildren(); } void comboBoxChanged (ComboBox*) { if (styleBox->getSelectedId() == 1) toolbar->setStyle (Toolbar::iconsOnly); else if (styleBox->getSelectedId() == 2) toolbar->setStyle (Toolbar::iconsWithText); else if (styleBox->getSelectedId() == 3) toolbar->setStyle (Toolbar::textOnly); palette->resized(); // to make it update the styles } void buttonClicked (Button*) { toolbar->addDefaultItems (factory); } void paint (Graphics& g) { Colour background; DialogWindow* const dw = findParentComponentOfClass ((DialogWindow*) 0); if (dw != 0) background = dw->getBackgroundColour(); g.setColour (background.contrasting().withAlpha (0.3f)); g.fillRect (palette->getX(), palette->getBottom() - 1, palette->getWidth(), 1); } void resized() { palette->setBounds (0, 0, getWidth(), getHeight() - 120); if (styleBox != 0) styleBox->setBounds (10, getHeight() - 110, 200, 22); if (defaultButton != 0) { defaultButton->changeWidthToFitText (22); defaultButton->setTopLeftPosition (240, getHeight() - 110); } instructions->setBounds (10, getHeight() - 80, getWidth() - 20, 80); } private: ToolbarItemFactory& factory; Toolbar* const toolbar; Label* instructions; ToolbarItemPalette* palette; ComboBox* styleBox; TextButton* defaultButton; }; }; void Toolbar::showCustomisationDialog (ToolbarItemFactory& factory, const int optionFlags) { setEditingActive (true); ToolbarCustomisationDialog dw (factory, this, optionFlags); dw.runModalLoop(); jassert (isValidComponent()); // ? deleting the toolbar while it's being edited? setEditingActive (false); } END_JUCE_NAMESPACE /*** End of inlined file: juce_Toolbar.cpp ***/ /*** Start of inlined file: juce_ToolbarItemComponent.cpp ***/ BEGIN_JUCE_NAMESPACE ToolbarItemFactory::ToolbarItemFactory() { } ToolbarItemFactory::~ToolbarItemFactory() { } class ItemDragAndDropOverlayComponent : public Component { public: ItemDragAndDropOverlayComponent() : isDragging (false) { setAlwaysOnTop (true); setRepaintsOnMouseActivity (true); setMouseCursor (MouseCursor::DraggingHandCursor); } ~ItemDragAndDropOverlayComponent() { } void paint (Graphics& g) { ToolbarItemComponent* const tc = dynamic_cast (getParentComponent()); if (isMouseOverOrDragging() && tc != 0 && tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar) { g.setColour (findColour (Toolbar::editingModeOutlineColourId, true)); g.drawRect (0, 0, getWidth(), getHeight(), jmin (2, (getWidth() - 1) / 2, (getHeight() - 1) / 2)); } } void mouseDown (const MouseEvent& e) { isDragging = false; ToolbarItemComponent* const tc = dynamic_cast (getParentComponent()); if (tc != 0) { tc->dragOffsetX = e.x; tc->dragOffsetY = e.y; } } void mouseDrag (const MouseEvent& e) { if (! (isDragging || e.mouseWasClicked())) { isDragging = true; DragAndDropContainer* const dnd = DragAndDropContainer::findParentDragContainerFor (this); if (dnd != 0) { dnd->startDragging (Toolbar::toolbarDragDescriptor, getParentComponent(), Image::null, true); ToolbarItemComponent* const tc = dynamic_cast (getParentComponent()); if (tc != 0) { tc->isBeingDragged = true; if (tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar) tc->setVisible (false); } } } } void mouseUp (const MouseEvent&) { isDragging = false; ToolbarItemComponent* const tc = dynamic_cast (getParentComponent()); if (tc != 0) { tc->isBeingDragged = false; Toolbar* const tb = tc->getToolbar(); if (tb != 0) tb->updateAllItemPositions (true); else if (tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar) delete tc; } } void parentSizeChanged() { setBounds (0, 0, getParentWidth(), getParentHeight()); } juce_UseDebuggingNewOperator private: bool isDragging; ItemDragAndDropOverlayComponent (const ItemDragAndDropOverlayComponent&); ItemDragAndDropOverlayComponent& operator= (const ItemDragAndDropOverlayComponent&); }; ToolbarItemComponent::ToolbarItemComponent (const int itemId_, const String& labelText, const bool isBeingUsedAsAButton_) : Button (labelText), itemId (itemId_), mode (normalMode), toolbarStyle (Toolbar::iconsOnly), dragOffsetX (0), dragOffsetY (0), isActive (true), isBeingDragged (false), isBeingUsedAsAButton (isBeingUsedAsAButton_) { // Your item ID can't be 0! jassert (itemId_ != 0); } ToolbarItemComponent::~ToolbarItemComponent() { jassert (overlayComp == 0 || overlayComp->isValidComponent()); overlayComp = 0; } Toolbar* ToolbarItemComponent::getToolbar() const { return dynamic_cast (getParentComponent()); } bool ToolbarItemComponent::isToolbarVertical() const { const Toolbar* const t = getToolbar(); return t != 0 && t->isVertical(); } void ToolbarItemComponent::setStyle (const Toolbar::ToolbarItemStyle& newStyle) { if (toolbarStyle != newStyle) { toolbarStyle = newStyle; repaint(); resized(); } } void ToolbarItemComponent::paintButton (Graphics& g, const bool over, const bool down) { if (isBeingUsedAsAButton) getLookAndFeel().paintToolbarButtonBackground (g, getWidth(), getHeight(), over, down, *this); if (toolbarStyle != Toolbar::iconsOnly) { const int indent = contentArea.getX(); int y = indent; int h = getHeight() - indent * 2; if (toolbarStyle == Toolbar::iconsWithText) { y = contentArea.getBottom() + indent / 2; h -= contentArea.getHeight(); } getLookAndFeel().paintToolbarButtonLabel (g, indent, y, getWidth() - indent * 2, h, getButtonText(), *this); } if (! contentArea.isEmpty()) { g.saveState(); g.setOrigin (contentArea.getX(), contentArea.getY()); g.reduceClipRegion (0, 0, contentArea.getWidth(), contentArea.getHeight()); paintButtonArea (g, contentArea.getWidth(), contentArea.getHeight(), over, down); g.restoreState(); } } void ToolbarItemComponent::resized() { if (toolbarStyle != Toolbar::textOnly) { const int indent = jmin (proportionOfWidth (0.08f), proportionOfHeight (0.08f)); contentArea = Rectangle (indent, indent, getWidth() - indent * 2, toolbarStyle == Toolbar::iconsWithText ? proportionOfHeight (0.55f) : (getHeight() - indent * 2)); } else { contentArea = Rectangle(); } contentAreaChanged (contentArea); } void ToolbarItemComponent::setEditingMode (const ToolbarEditingMode newMode) { if (mode != newMode) { mode = newMode; repaint(); if (mode == normalMode) { jassert (overlayComp == 0 || overlayComp->isValidComponent()); overlayComp = 0; } else if (overlayComp == 0) { addAndMakeVisible (overlayComp = new ItemDragAndDropOverlayComponent()); overlayComp->parentSizeChanged(); } resized(); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ToolbarItemComponent.cpp ***/ /*** Start of inlined file: juce_ToolbarItemPalette.cpp ***/ BEGIN_JUCE_NAMESPACE ToolbarItemPalette::ToolbarItemPalette (ToolbarItemFactory& factory_, Toolbar* const toolbar_) : factory (factory_), toolbar (toolbar_) { Component* const itemHolder = new Component(); Array allIds; factory_.getAllToolbarItemIds (allIds); for (int i = 0; i < allIds.size(); ++i) { ToolbarItemComponent* const tc = Toolbar::createItem (factory_, allIds.getUnchecked (i)); jassert (tc != 0); if (tc != 0) { itemHolder->addAndMakeVisible (tc); tc->setEditingMode (ToolbarItemComponent::editableOnPalette); } } viewport = new Viewport(); viewport->setViewedComponent (itemHolder); addAndMakeVisible (viewport); } ToolbarItemPalette::~ToolbarItemPalette() { viewport->getViewedComponent()->deleteAllChildren(); deleteAllChildren(); } void ToolbarItemPalette::resized() { viewport->setBoundsInset (BorderSize (1)); Component* const itemHolder = viewport->getViewedComponent(); const int indent = 8; const int preferredWidth = viewport->getWidth() - viewport->getScrollBarThickness() - indent; const int height = toolbar->getThickness(); int x = indent; int y = indent; int maxX = 0; for (int i = 0; i < itemHolder->getNumChildComponents(); ++i) { ToolbarItemComponent* const tc = dynamic_cast (itemHolder->getChildComponent (i)); if (tc != 0) { tc->setStyle (toolbar->getStyle()); int preferredSize = 1, minSize = 1, maxSize = 1; if (tc->getToolbarItemSizes (height, false, preferredSize, minSize, maxSize)) { if (x + preferredSize > preferredWidth && x > indent) { x = indent; y += height; } tc->setBounds (x, y, preferredSize, height); x += preferredSize + 8; maxX = jmax (maxX, x); } } } itemHolder->setSize (maxX, y + height + 8); } void ToolbarItemPalette::replaceComponent (ToolbarItemComponent* const comp) { ToolbarItemComponent* const tc = Toolbar::createItem (factory, comp->getItemId()); jassert (tc != 0); if (tc != 0) { tc->setBounds (comp->getBounds()); tc->setStyle (toolbar->getStyle()); tc->setEditingMode (comp->getEditingMode()); viewport->getViewedComponent()->addAndMakeVisible (tc, getIndexOfChildComponent (comp)); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ToolbarItemPalette.cpp ***/ /*** Start of inlined file: juce_TreeView.cpp ***/ BEGIN_JUCE_NAMESPACE class TreeViewContentComponent : public Component, public TooltipClient { public: TreeViewContentComponent (TreeView& owner_) : owner (owner_), buttonUnderMouse (0), isDragging (false) { } ~TreeViewContentComponent() { deleteAllChildren(); } void mouseDown (const MouseEvent& e) { updateButtonUnderMouse (e); isDragging = false; needSelectionOnMouseUp = false; Rectangle pos; TreeViewItem* const item = findItemAt (e.y, pos); if (item == 0) return; // (if the open/close buttons are hidden, we'll treat clicks to the left of the item // as selection clicks) if (e.x < pos.getX() && owner.openCloseButtonsVisible) { if (e.x >= pos.getX() - owner.getIndentSize()) item->setOpen (! item->isOpen()); // (clicks to the left of an open/close button are ignored) } else { // mouse-down inside the body of the item.. if (! owner.isMultiSelectEnabled()) item->setSelected (true, true); else if (item->isSelected()) needSelectionOnMouseUp = ! e.mods.isPopupMenu(); else selectBasedOnModifiers (item, e.mods); if (e.x >= pos.getX()) item->itemClicked (e.withNewPosition (e.getPosition() - pos.getPosition())); } } void mouseUp (const MouseEvent& e) { updateButtonUnderMouse (e); if (needSelectionOnMouseUp && e.mouseWasClicked()) { Rectangle pos; TreeViewItem* const item = findItemAt (e.y, pos); if (item != 0) selectBasedOnModifiers (item, e.mods); } } void mouseDoubleClick (const MouseEvent& e) { if (e.getNumberOfClicks() != 3) // ignore triple clicks { Rectangle pos; TreeViewItem* const item = findItemAt (e.y, pos); if (item != 0 && (e.x >= pos.getX() || ! owner.openCloseButtonsVisible)) item->itemDoubleClicked (e.withNewPosition (e.getPosition() - pos.getPosition())); } } void mouseDrag (const MouseEvent& e) { if (isEnabled() && ! (isDragging || e.mouseWasClicked() || e.getDistanceFromDragStart() < 5 || e.mods.isPopupMenu())) { isDragging = true; Rectangle pos; TreeViewItem* const item = findItemAt (e.getMouseDownY(), pos); if (item != 0 && e.getMouseDownX() >= pos.getX()) { const String dragDescription (item->getDragSourceDescription()); if (dragDescription.isNotEmpty()) { DragAndDropContainer* const dragContainer = DragAndDropContainer::findParentDragContainerFor (this); if (dragContainer != 0) { pos.setSize (pos.getWidth(), item->itemHeight); Image dragImage (Component::createComponentSnapshot (pos, true)); dragImage.multiplyAllAlphas (0.6f); Point imageOffset (pos.getPosition() - e.getPosition()); dragContainer->startDragging (dragDescription, &owner, dragImage, true, &imageOffset); } else { // to be able to do a drag-and-drop operation, the treeview needs to // be inside a component which is also a DragAndDropContainer. jassertfalse; } } } } } void mouseMove (const MouseEvent& e) { updateButtonUnderMouse (e); } void mouseExit (const MouseEvent& e) { updateButtonUnderMouse (e); } void paint (Graphics& g) { if (owner.rootItem != 0) { owner.handleAsyncUpdate(); if (! owner.rootItemVisible) g.setOrigin (0, -owner.rootItem->itemHeight); owner.rootItem->paintRecursively (g, getWidth()); } } TreeViewItem* findItemAt (int y, Rectangle& itemPosition) const { if (owner.rootItem != 0) { owner.handleAsyncUpdate(); if (! owner.rootItemVisible) y += owner.rootItem->itemHeight; TreeViewItem* const ti = owner.rootItem->findItemRecursively (y); if (ti != 0) itemPosition = ti->getItemPosition (false); return ti; } return 0; } void updateComponents() { const int visibleTop = -getY(); const int visibleBottom = visibleTop + getParentHeight(); BigInteger itemsToKeep; { TreeViewItem* item = owner.rootItem; int y = (item != 0 && ! owner.rootItemVisible) ? -item->itemHeight : 0; while (item != 0 && y < visibleBottom) { y += item->itemHeight; if (y >= visibleTop) { const int index = rowComponentIds.indexOf (item->uid); if (index < 0) { Component* const comp = item->createItemComponent(); if (comp != 0) { addAndMakeVisible (comp); itemsToKeep.setBit (rowComponentItems.size()); rowComponentItems.add (item); rowComponentIds.add (item->uid); rowComponents.add (comp); } } else { itemsToKeep.setBit (index); } } item = item->getNextVisibleItem (true); } } for (int i = rowComponentItems.size(); --i >= 0;) { Component* const comp = rowComponents.getUnchecked(i); bool keep = false; if (isParentOf (comp)) { if (itemsToKeep[i]) { const TreeViewItem* const item = rowComponentItems.getUnchecked(i); Rectangle pos (item->getItemPosition (false)); pos.setSize (pos.getWidth(), item->itemHeight); if (pos.getBottom() >= visibleTop && pos.getY() < visibleBottom) { keep = true; comp->setBounds (pos); } } if ((! keep) && isMouseDraggingInChildCompOf (comp)) { keep = true; comp->setSize (0, 0); } } if (! keep) { delete comp; rowComponents.remove (i); rowComponentIds.remove (i); rowComponentItems.remove (i); } } } void updateButtonUnderMouse (const MouseEvent& e) { TreeViewItem* newItem = 0; if (owner.openCloseButtonsVisible) { Rectangle pos; TreeViewItem* item = findItemAt (e.y, pos); if (item != 0 && e.x < pos.getX() && e.x >= pos.getX() - owner.getIndentSize()) { newItem = item; if (! newItem->mightContainSubItems()) newItem = 0; } } if (buttonUnderMouse != newItem) { if (buttonUnderMouse != 0 && containsItem (buttonUnderMouse)) { const Rectangle r (buttonUnderMouse->getItemPosition (false)); repaint (0, r.getY(), r.getX(), buttonUnderMouse->getItemHeight()); } buttonUnderMouse = newItem; if (buttonUnderMouse != 0) { const Rectangle r (buttonUnderMouse->getItemPosition (false)); repaint (0, r.getY(), r.getX(), buttonUnderMouse->getItemHeight()); } } } bool isMouseOverButton (TreeViewItem* const item) const throw() { return item == buttonUnderMouse; } void resized() { owner.itemsChanged(); } const String getTooltip() { Rectangle pos; TreeViewItem* const item = findItemAt (getMouseXYRelative().getY(), pos); if (item != 0) return item->getTooltip(); return owner.getTooltip(); } juce_UseDebuggingNewOperator private: TreeView& owner; Array rowComponentItems; Array rowComponentIds; Array rowComponents; TreeViewItem* buttonUnderMouse; bool isDragging, needSelectionOnMouseUp; void selectBasedOnModifiers (TreeViewItem* const item, const ModifierKeys& modifiers) { TreeViewItem* firstSelected = 0; if (modifiers.isShiftDown() && ((firstSelected = owner.getSelectedItem (0)) != 0)) { TreeViewItem* const lastSelected = owner.getSelectedItem (owner.getNumSelectedItems() - 1); jassert (lastSelected != 0); int rowStart = firstSelected->getRowNumberInTree(); int rowEnd = lastSelected->getRowNumberInTree(); if (rowStart > rowEnd) swapVariables (rowStart, rowEnd); int ourRow = item->getRowNumberInTree(); int otherEnd = ourRow < rowEnd ? rowStart : rowEnd; if (ourRow > otherEnd) swapVariables (ourRow, otherEnd); for (int i = ourRow; i <= otherEnd; ++i) owner.getItemOnRow (i)->setSelected (true, false); } else { const bool cmd = modifiers.isCommandDown(); item->setSelected ((! cmd) || ! item->isSelected(), ! cmd); } } bool containsItem (TreeViewItem* const item) const { for (int i = rowComponentItems.size(); --i >= 0;) if (rowComponentItems.getUnchecked(i) == item) return true; return false; } static bool isMouseDraggingInChildCompOf (Component* const comp) { for (int i = Desktop::getInstance().getNumMouseSources(); --i >= 0;) { MouseInputSource* const source = Desktop::getInstance().getMouseSource(i); if (source->isDragging()) { Component* const underMouse = source->getComponentUnderMouse(); if (underMouse != 0 && (comp == underMouse || comp->isParentOf (underMouse))) return true; } } return false; } TreeViewContentComponent (const TreeViewContentComponent&); TreeViewContentComponent& operator= (const TreeViewContentComponent&); }; class TreeView::TreeViewport : public Viewport { public: TreeViewport() throw() : lastX (-1) {} ~TreeViewport() throw() {} void updateComponents (const bool triggerResize = false) { TreeViewContentComponent* const tvc = static_cast (getViewedComponent()); if (tvc != 0) { if (triggerResize) tvc->resized(); else tvc->updateComponents(); } repaint(); } void visibleAreaChanged (int x, int, int, int) { const bool hasScrolledSideways = (x != lastX); lastX = x; updateComponents (hasScrolledSideways); } juce_UseDebuggingNewOperator private: int lastX; TreeViewport (const TreeViewport&); TreeViewport& operator= (const TreeViewport&); }; TreeView::TreeView (const String& componentName) : Component (componentName), rootItem (0), indentSize (24), defaultOpenness (false), needsRecalculating (true), rootItemVisible (true), multiSelectEnabled (false), openCloseButtonsVisible (true) { addAndMakeVisible (viewport = new TreeViewport()); viewport->setViewedComponent (new TreeViewContentComponent (*this)); viewport->setWantsKeyboardFocus (false); setWantsKeyboardFocus (true); } TreeView::~TreeView() { if (rootItem != 0) rootItem->setOwnerView (0); } void TreeView::setRootItem (TreeViewItem* const newRootItem) { if (rootItem != newRootItem) { if (newRootItem != 0) { jassert (newRootItem->ownerView == 0); // can't use a tree item in more than one tree at once.. if (newRootItem->ownerView != 0) newRootItem->ownerView->setRootItem (0); } if (rootItem != 0) rootItem->setOwnerView (0); rootItem = newRootItem; if (newRootItem != 0) newRootItem->setOwnerView (this); needsRecalculating = true; handleAsyncUpdate(); if (rootItem != 0 && (defaultOpenness || ! rootItemVisible)) { rootItem->setOpen (false); // force a re-open rootItem->setOpen (true); } } } void TreeView::deleteRootItem() { const ScopedPointer deleter (rootItem); setRootItem (0); } void TreeView::setRootItemVisible (const bool shouldBeVisible) { rootItemVisible = shouldBeVisible; if (rootItem != 0 && (defaultOpenness || ! rootItemVisible)) { rootItem->setOpen (false); // force a re-open rootItem->setOpen (true); } itemsChanged(); } void TreeView::colourChanged() { setOpaque (findColour (backgroundColourId).isOpaque()); repaint(); } void TreeView::setIndentSize (const int newIndentSize) { if (indentSize != newIndentSize) { indentSize = newIndentSize; resized(); } } void TreeView::setDefaultOpenness (const bool isOpenByDefault) { if (defaultOpenness != isOpenByDefault) { defaultOpenness = isOpenByDefault; itemsChanged(); } } void TreeView::setMultiSelectEnabled (const bool canMultiSelect) { multiSelectEnabled = canMultiSelect; } void TreeView::setOpenCloseButtonsVisible (const bool shouldBeVisible) { if (openCloseButtonsVisible != shouldBeVisible) { openCloseButtonsVisible = shouldBeVisible; itemsChanged(); } } Viewport* TreeView::getViewport() const throw() { return viewport; } void TreeView::clearSelectedItems() { if (rootItem != 0) rootItem->deselectAllRecursively(); } int TreeView::getNumSelectedItems() const throw() { return (rootItem != 0) ? rootItem->countSelectedItemsRecursively() : 0; } TreeViewItem* TreeView::getSelectedItem (const int index) const throw() { return (rootItem != 0) ? rootItem->getSelectedItemWithIndex (index) : 0; } int TreeView::getNumRowsInTree() const { if (rootItem != 0) return rootItem->getNumRows() - (rootItemVisible ? 0 : 1); return 0; } TreeViewItem* TreeView::getItemOnRow (int index) const { if (! rootItemVisible) ++index; if (rootItem != 0 && index >= 0) return rootItem->getItemOnRow (index); return 0; } TreeViewItem* TreeView::getItemAt (int y) const throw() { TreeViewContentComponent* const tc = static_cast (viewport->getViewedComponent()); Rectangle pos; return tc->findItemAt (relativePositionToOtherComponent (tc, Point (0, y)).getY(), pos); } TreeViewItem* TreeView::findItemFromIdentifierString (const String& identifierString) const { if (rootItem == 0) return 0; return rootItem->findItemFromIdentifierString (identifierString); } XmlElement* TreeView::getOpennessState (const bool alsoIncludeScrollPosition) const { XmlElement* e = 0; if (rootItem != 0) { e = rootItem->getOpennessState(); if (e != 0 && alsoIncludeScrollPosition) e->setAttribute ("scrollPos", viewport->getViewPositionY()); } return e; } void TreeView::restoreOpennessState (const XmlElement& newState) { if (rootItem != 0) { rootItem->restoreOpennessState (newState); if (newState.hasAttribute ("scrollPos")) viewport->setViewPosition (viewport->getViewPositionX(), newState.getIntAttribute ("scrollPos")); } } void TreeView::paint (Graphics& g) { g.fillAll (findColour (backgroundColourId)); } void TreeView::resized() { viewport->setBounds (getLocalBounds()); itemsChanged(); handleAsyncUpdate(); } void TreeView::enablementChanged() { repaint(); } void TreeView::moveSelectedRow (int delta) { if (delta == 0) return; int rowSelected = 0; TreeViewItem* const firstSelected = getSelectedItem (0); if (firstSelected != 0) rowSelected = firstSelected->getRowNumberInTree(); rowSelected = jlimit (0, getNumRowsInTree() - 1, rowSelected + delta); for (;;) { TreeViewItem* item = getItemOnRow (rowSelected); if (item != 0) { if (! item->canBeSelected()) { // if the row we want to highlight doesn't allow it, try skipping // to the next item.. const int nextRowToTry = jlimit (0, getNumRowsInTree() - 1, rowSelected + (delta < 0 ? -1 : 1)); if (rowSelected != nextRowToTry) { rowSelected = nextRowToTry; continue; } else { break; } } item->setSelected (true, true); scrollToKeepItemVisible (item); } break; } } void TreeView::scrollToKeepItemVisible (TreeViewItem* item) { if (item != 0 && item->ownerView == this) { handleAsyncUpdate(); item = item->getDeepestOpenParentItem(); int y = item->y; int viewTop = viewport->getViewPositionY(); if (y < viewTop) { viewport->setViewPosition (viewport->getViewPositionX(), y); } else if (y + item->itemHeight > viewTop + viewport->getViewHeight()) { viewport->setViewPosition (viewport->getViewPositionX(), (y + item->itemHeight) - viewport->getViewHeight()); } } } bool TreeView::keyPressed (const KeyPress& key) { if (key.isKeyCode (KeyPress::upKey)) { moveSelectedRow (-1); } else if (key.isKeyCode (KeyPress::downKey)) { moveSelectedRow (1); } else if (key.isKeyCode (KeyPress::pageDownKey) || key.isKeyCode (KeyPress::pageUpKey)) { if (rootItem != 0) { int rowsOnScreen = getHeight() / jmax (1, rootItem->itemHeight); if (key.isKeyCode (KeyPress::pageUpKey)) rowsOnScreen = -rowsOnScreen; moveSelectedRow (rowsOnScreen); } } else if (key.isKeyCode (KeyPress::homeKey)) { moveSelectedRow (-0x3fffffff); } else if (key.isKeyCode (KeyPress::endKey)) { moveSelectedRow (0x3fffffff); } else if (key.isKeyCode (KeyPress::returnKey)) { TreeViewItem* const firstSelected = getSelectedItem (0); if (firstSelected != 0) firstSelected->setOpen (! firstSelected->isOpen()); } else if (key.isKeyCode (KeyPress::leftKey)) { TreeViewItem* const firstSelected = getSelectedItem (0); if (firstSelected != 0) { if (firstSelected->isOpen()) { firstSelected->setOpen (false); } else { TreeViewItem* parent = firstSelected->parentItem; if ((! rootItemVisible) && parent == rootItem) parent = 0; if (parent != 0) { parent->setSelected (true, true); scrollToKeepItemVisible (parent); } } } } else if (key.isKeyCode (KeyPress::rightKey)) { TreeViewItem* const firstSelected = getSelectedItem (0); if (firstSelected != 0) { if (firstSelected->isOpen() || ! firstSelected->mightContainSubItems()) moveSelectedRow (1); else firstSelected->setOpen (true); } } else { return false; } return true; } void TreeView::itemsChanged() throw() { needsRecalculating = true; repaint(); triggerAsyncUpdate(); } void TreeView::handleAsyncUpdate() { if (needsRecalculating) { needsRecalculating = false; const ScopedLock sl (nodeAlterationLock); if (rootItem != 0) rootItem->updatePositions (rootItemVisible ? 0 : -rootItem->itemHeight); viewport->updateComponents(); if (rootItem != 0) { viewport->getViewedComponent() ->setSize (jmax (viewport->getMaximumVisibleWidth(), rootItem->totalWidth), rootItem->totalHeight - (rootItemVisible ? 0 : rootItem->itemHeight)); } else { viewport->getViewedComponent()->setSize (0, 0); } } } class TreeView::InsertPointHighlight : public Component { public: InsertPointHighlight() : lastItem (0) { setSize (100, 12); setAlwaysOnTop (true); setInterceptsMouseClicks (false, false); } ~InsertPointHighlight() {} void setTargetPosition (TreeViewItem* const item, int insertIndex, const int x, const int y, const int width) throw() { lastItem = item; lastIndex = insertIndex; const int offset = getHeight() / 2; setBounds (x - offset, y - offset, width - (x - offset), getHeight()); } void paint (Graphics& g) { Path p; const float h = (float) getHeight(); p.addEllipse (2.0f, 2.0f, h - 4.0f, h - 4.0f); p.startNewSubPath (h - 2.0f, h / 2.0f); p.lineTo ((float) getWidth(), h / 2.0f); g.setColour (findColour (TreeView::dragAndDropIndicatorColourId, true)); g.strokePath (p, PathStrokeType (2.0f)); } TreeViewItem* lastItem; int lastIndex; private: InsertPointHighlight (const InsertPointHighlight&); InsertPointHighlight& operator= (const InsertPointHighlight&); }; class TreeView::TargetGroupHighlight : public Component { public: TargetGroupHighlight() { setAlwaysOnTop (true); setInterceptsMouseClicks (false, false); } ~TargetGroupHighlight() {} void setTargetPosition (TreeViewItem* const item) throw() { Rectangle r (item->getItemPosition (true)); r.setHeight (item->getItemHeight()); setBounds (r); } void paint (Graphics& g) { g.setColour (findColour (TreeView::dragAndDropIndicatorColourId, true)); g.drawRoundedRectangle (1.0f, 1.0f, getWidth() - 2.0f, getHeight() - 2.0f, 3.0f, 2.0f); } private: TargetGroupHighlight (const TargetGroupHighlight&); TargetGroupHighlight& operator= (const TargetGroupHighlight&); }; void TreeView::showDragHighlight (TreeViewItem* item, int insertIndex, int x, int y) throw() { beginDragAutoRepeat (100); if (dragInsertPointHighlight == 0) { addAndMakeVisible (dragInsertPointHighlight = new InsertPointHighlight()); addAndMakeVisible (dragTargetGroupHighlight = new TargetGroupHighlight()); } dragInsertPointHighlight->setTargetPosition (item, insertIndex, x, y, viewport->getViewWidth()); dragTargetGroupHighlight->setTargetPosition (item); } void TreeView::hideDragHighlight() throw() { dragInsertPointHighlight = 0; dragTargetGroupHighlight = 0; } TreeViewItem* TreeView::getInsertPosition (int& x, int& y, int& insertIndex, const StringArray& files, const String& sourceDescription, Component* sourceComponent) const throw() { insertIndex = 0; TreeViewItem* item = getItemAt (y); if (item == 0) return 0; Rectangle itemPos (item->getItemPosition (true)); insertIndex = item->getIndexInParent(); const int oldY = y; y = itemPos.getY(); if (item->getNumSubItems() == 0 || ! item->isOpen()) { if (files.size() > 0 ? item->isInterestedInFileDrag (files) : item->isInterestedInDragSource (sourceDescription, sourceComponent)) { // Check if we're trying to drag into an empty group item.. if (oldY > itemPos.getY() + itemPos.getHeight() / 4 && oldY < itemPos.getBottom() - itemPos.getHeight() / 4) { insertIndex = 0; x = itemPos.getX() + getIndentSize(); y = itemPos.getBottom(); return item; } } } if (oldY > itemPos.getCentreY()) { y += item->getItemHeight(); while (item->isLastOfSiblings() && item->parentItem != 0 && item->parentItem->parentItem != 0) { if (x > itemPos.getX()) break; item = item->parentItem; itemPos = item->getItemPosition (true); insertIndex = item->getIndexInParent(); } ++insertIndex; } x = itemPos.getX(); return item->parentItem; } void TreeView::handleDrag (const StringArray& files, const String& sourceDescription, Component* sourceComponent, int x, int y) { const bool scrolled = viewport->autoScroll (x, y, 20, 10); int insertIndex; TreeViewItem* const item = getInsertPosition (x, y, insertIndex, files, sourceDescription, sourceComponent); if (item != 0) { if (scrolled || dragInsertPointHighlight == 0 || dragInsertPointHighlight->lastItem != item || dragInsertPointHighlight->lastIndex != insertIndex) { if (files.size() > 0 ? item->isInterestedInFileDrag (files) : item->isInterestedInDragSource (sourceDescription, sourceComponent)) showDragHighlight (item, insertIndex, x, y); else hideDragHighlight(); } } else { hideDragHighlight(); } } void TreeView::handleDrop (const StringArray& files, const String& sourceDescription, Component* sourceComponent, int x, int y) { hideDragHighlight(); int insertIndex; TreeViewItem* const item = getInsertPosition (x, y, insertIndex, files, sourceDescription, sourceComponent); if (item != 0) { if (files.size() > 0) { if (item->isInterestedInFileDrag (files)) item->filesDropped (files, insertIndex); } else { if (item->isInterestedInDragSource (sourceDescription, sourceComponent)) item->itemDropped (sourceDescription, sourceComponent, insertIndex); } } } bool TreeView::isInterestedInFileDrag (const StringArray&) { return true; } void TreeView::fileDragEnter (const StringArray& files, int x, int y) { fileDragMove (files, x, y); } void TreeView::fileDragMove (const StringArray& files, int x, int y) { handleDrag (files, String::empty, 0, x, y); } void TreeView::fileDragExit (const StringArray&) { hideDragHighlight(); } void TreeView::filesDropped (const StringArray& files, int x, int y) { handleDrop (files, String::empty, 0, x, y); } bool TreeView::isInterestedInDragSource (const String& /*sourceDescription*/, Component* /*sourceComponent*/) { return true; } void TreeView::itemDragEnter (const String& sourceDescription, Component* sourceComponent, int x, int y) { itemDragMove (sourceDescription, sourceComponent, x, y); } void TreeView::itemDragMove (const String& sourceDescription, Component* sourceComponent, int x, int y) { handleDrag (StringArray(), sourceDescription, sourceComponent, x, y); } void TreeView::itemDragExit (const String& /*sourceDescription*/, Component* /*sourceComponent*/) { hideDragHighlight(); } void TreeView::itemDropped (const String& sourceDescription, Component* sourceComponent, int x, int y) { handleDrop (StringArray(), sourceDescription, sourceComponent, x, y); } enum TreeViewOpenness { opennessDefault = 0, opennessClosed = 1, opennessOpen = 2 }; TreeViewItem::TreeViewItem() : ownerView (0), parentItem (0), y (0), itemHeight (0), totalHeight (0), selected (false), redrawNeeded (true), drawLinesInside (true), drawsInLeftMargin (false), openness (opennessDefault) { static int nextUID = 0; uid = nextUID++; } TreeViewItem::~TreeViewItem() { } const String TreeViewItem::getUniqueName() const { return String::empty; } void TreeViewItem::itemOpennessChanged (bool) { } int TreeViewItem::getNumSubItems() const throw() { return subItems.size(); } TreeViewItem* TreeViewItem::getSubItem (const int index) const throw() { return subItems [index]; } void TreeViewItem::clearSubItems() { if (subItems.size() > 0) { if (ownerView != 0) { const ScopedLock sl (ownerView->nodeAlterationLock); subItems.clear(); treeHasChanged(); } else { subItems.clear(); } } } void TreeViewItem::addSubItem (TreeViewItem* const newItem, const int insertPosition) { if (newItem != 0) { newItem->parentItem = this; newItem->setOwnerView (ownerView); newItem->y = 0; newItem->itemHeight = newItem->getItemHeight(); newItem->totalHeight = 0; newItem->itemWidth = newItem->getItemWidth(); newItem->totalWidth = 0; if (ownerView != 0) { const ScopedLock sl (ownerView->nodeAlterationLock); subItems.insert (insertPosition, newItem); treeHasChanged(); if (newItem->isOpen()) newItem->itemOpennessChanged (true); } else { subItems.insert (insertPosition, newItem); if (newItem->isOpen()) newItem->itemOpennessChanged (true); } } } void TreeViewItem::removeSubItem (const int index, const bool deleteItem) { if (ownerView != 0) { const ScopedLock sl (ownerView->nodeAlterationLock); if (((unsigned int) index) < (unsigned int) subItems.size()) { subItems.remove (index, deleteItem); treeHasChanged(); } } else { subItems.remove (index, deleteItem); } } bool TreeViewItem::isOpen() const throw() { if (openness == opennessDefault) return ownerView != 0 && ownerView->defaultOpenness; else return openness == opennessOpen; } void TreeViewItem::setOpen (const bool shouldBeOpen) { if (isOpen() != shouldBeOpen) { openness = shouldBeOpen ? opennessOpen : opennessClosed; treeHasChanged(); itemOpennessChanged (isOpen()); } } bool TreeViewItem::isSelected() const throw() { return selected; } void TreeViewItem::deselectAllRecursively() { setSelected (false, false); for (int i = 0; i < subItems.size(); ++i) subItems.getUnchecked(i)->deselectAllRecursively(); } void TreeViewItem::setSelected (const bool shouldBeSelected, const bool deselectOtherItemsFirst) { if (shouldBeSelected && ! canBeSelected()) return; if (deselectOtherItemsFirst) getTopLevelItem()->deselectAllRecursively(); if (shouldBeSelected != selected) { selected = shouldBeSelected; if (ownerView != 0) ownerView->repaint(); itemSelectionChanged (shouldBeSelected); } } void TreeViewItem::paintItem (Graphics&, int, int) { } void TreeViewItem::paintOpenCloseButton (Graphics& g, int width, int height, bool isMouseOver) { ownerView->getLookAndFeel() .drawTreeviewPlusMinusBox (g, 0, 0, width, height, ! isOpen(), isMouseOver); } void TreeViewItem::itemClicked (const MouseEvent&) { } void TreeViewItem::itemDoubleClicked (const MouseEvent&) { if (mightContainSubItems()) setOpen (! isOpen()); } void TreeViewItem::itemSelectionChanged (bool) { } const String TreeViewItem::getTooltip() { return String::empty; } const String TreeViewItem::getDragSourceDescription() { return String::empty; } bool TreeViewItem::isInterestedInFileDrag (const StringArray&) { return false; } void TreeViewItem::filesDropped (const StringArray& /*files*/, int /*insertIndex*/) { } bool TreeViewItem::isInterestedInDragSource (const String& /*sourceDescription*/, Component* /*sourceComponent*/) { return false; } void TreeViewItem::itemDropped (const String& /*sourceDescription*/, Component* /*sourceComponent*/, int /*insertIndex*/) { } const Rectangle TreeViewItem::getItemPosition (const bool relativeToTreeViewTopLeft) const throw() { const int indentX = getIndentX(); int width = itemWidth; if (ownerView != 0 && width < 0) width = ownerView->viewport->getViewWidth() - indentX; Rectangle r (indentX, y, jmax (0, width), totalHeight); if (relativeToTreeViewTopLeft) r -= ownerView->viewport->getViewPosition(); return r; } void TreeViewItem::treeHasChanged() const throw() { if (ownerView != 0) ownerView->itemsChanged(); } void TreeViewItem::repaintItem() const { if (ownerView != 0 && areAllParentsOpen()) { Rectangle r (getItemPosition (true)); r.setLeft (0); ownerView->viewport->repaint (r); } } bool TreeViewItem::areAllParentsOpen() const throw() { return parentItem == 0 || (parentItem->isOpen() && parentItem->areAllParentsOpen()); } void TreeViewItem::updatePositions (int newY) { y = newY; itemHeight = getItemHeight(); totalHeight = itemHeight; itemWidth = getItemWidth(); totalWidth = jmax (itemWidth, 0) + getIndentX(); if (isOpen()) { newY += totalHeight; for (int i = 0; i < subItems.size(); ++i) { TreeViewItem* const ti = subItems.getUnchecked(i); ti->updatePositions (newY); newY += ti->totalHeight; totalHeight += ti->totalHeight; totalWidth = jmax (totalWidth, ti->totalWidth); } } } TreeViewItem* TreeViewItem::getDeepestOpenParentItem() throw() { TreeViewItem* result = this; TreeViewItem* item = this; while (item->parentItem != 0) { item = item->parentItem; if (! item->isOpen()) result = item; } return result; } void TreeViewItem::setOwnerView (TreeView* const newOwner) throw() { ownerView = newOwner; for (int i = subItems.size(); --i >= 0;) subItems.getUnchecked(i)->setOwnerView (newOwner); } int TreeViewItem::getIndentX() const throw() { const int indentWidth = ownerView->getIndentSize(); int x = ownerView->rootItemVisible ? indentWidth : 0; if (! ownerView->openCloseButtonsVisible) x -= indentWidth; TreeViewItem* p = parentItem; while (p != 0) { x += indentWidth; p = p->parentItem; } return x; } void TreeViewItem::setDrawsInLeftMargin (bool canDrawInLeftMargin) throw() { drawsInLeftMargin = canDrawInLeftMargin; } void TreeViewItem::paintRecursively (Graphics& g, int width) { jassert (ownerView != 0); if (ownerView == 0) return; const int indent = getIndentX(); const int itemW = itemWidth < 0 ? width - indent : itemWidth; { g.saveState(); g.setOrigin (indent, 0); if (g.reduceClipRegion (drawsInLeftMargin ? -indent : 0, 0, drawsInLeftMargin ? itemW + indent : itemW, itemHeight)) paintItem (g, itemW, itemHeight); g.restoreState(); } g.setColour (ownerView->findColour (TreeView::linesColourId)); const float halfH = itemHeight * 0.5f; int depth = 0; TreeViewItem* p = parentItem; while (p != 0) { ++depth; p = p->parentItem; } if (! ownerView->rootItemVisible) --depth; const int indentWidth = ownerView->getIndentSize(); if (depth >= 0 && ownerView->openCloseButtonsVisible) { float x = (depth + 0.5f) * indentWidth; if (depth >= 0) { if (parentItem != 0 && parentItem->drawLinesInside) g.drawLine (x, 0, x, isLastOfSiblings() ? halfH : (float) itemHeight); if ((parentItem != 0 && parentItem->drawLinesInside) || (parentItem == 0 && drawLinesInside)) g.drawLine (x, halfH, x + indentWidth / 2, halfH); } p = parentItem; int d = depth; while (p != 0 && --d >= 0) { x -= (float) indentWidth; if ((p->parentItem == 0 || p->parentItem->drawLinesInside) && ! p->isLastOfSiblings()) { g.drawLine (x, 0, x, (float) itemHeight); } p = p->parentItem; } if (mightContainSubItems()) { g.saveState(); g.setOrigin (depth * indentWidth, 0); g.reduceClipRegion (0, 0, indentWidth, itemHeight); paintOpenCloseButton (g, indentWidth, itemHeight, static_cast (ownerView->viewport->getViewedComponent()) ->isMouseOverButton (this)); g.restoreState(); } } if (isOpen()) { const Rectangle clip (g.getClipBounds()); for (int i = 0; i < subItems.size(); ++i) { TreeViewItem* const ti = subItems.getUnchecked(i); const int relY = ti->y - y; if (relY >= clip.getBottom()) break; if (relY + ti->totalHeight >= clip.getY()) { g.saveState(); g.setOrigin (0, relY); if (g.reduceClipRegion (0, 0, width, ti->totalHeight)) ti->paintRecursively (g, width); g.restoreState(); } } } } bool TreeViewItem::isLastOfSiblings() const throw() { return parentItem == 0 || parentItem->subItems.getLast() == this; } int TreeViewItem::getIndexInParent() const throw() { if (parentItem == 0) return 0; return parentItem->subItems.indexOf (this); } TreeViewItem* TreeViewItem::getTopLevelItem() throw() { return (parentItem == 0) ? this : parentItem->getTopLevelItem(); } int TreeViewItem::getNumRows() const throw() { int num = 1; if (isOpen()) { for (int i = subItems.size(); --i >= 0;) num += subItems.getUnchecked(i)->getNumRows(); } return num; } TreeViewItem* TreeViewItem::getItemOnRow (int index) throw() { if (index == 0) return this; if (index > 0 && isOpen()) { --index; for (int i = 0; i < subItems.size(); ++i) { TreeViewItem* const item = subItems.getUnchecked(i); if (index == 0) return item; const int numRows = item->getNumRows(); if (numRows > index) return item->getItemOnRow (index); index -= numRows; } } return 0; } TreeViewItem* TreeViewItem::findItemRecursively (int targetY) throw() { if (((unsigned int) targetY) < (unsigned int) totalHeight) { const int h = itemHeight; if (targetY < h) return this; if (isOpen()) { targetY -= h; for (int i = 0; i < subItems.size(); ++i) { TreeViewItem* const ti = subItems.getUnchecked(i); if (targetY < ti->totalHeight) return ti->findItemRecursively (targetY); targetY -= ti->totalHeight; } } } return 0; } int TreeViewItem::countSelectedItemsRecursively() const throw() { int total = 0; if (isSelected()) ++total; for (int i = subItems.size(); --i >= 0;) total += subItems.getUnchecked(i)->countSelectedItemsRecursively(); return total; } TreeViewItem* TreeViewItem::getSelectedItemWithIndex (int index) throw() { if (isSelected()) { if (index == 0) return this; --index; } if (index >= 0) { for (int i = 0; i < subItems.size(); ++i) { TreeViewItem* const item = subItems.getUnchecked(i); TreeViewItem* const found = item->getSelectedItemWithIndex (index); if (found != 0) return found; index -= item->countSelectedItemsRecursively(); } } return 0; } int TreeViewItem::getRowNumberInTree() const throw() { if (parentItem != 0 && ownerView != 0) { int n = 1 + parentItem->getRowNumberInTree(); int ourIndex = parentItem->subItems.indexOf (this); jassert (ourIndex >= 0); while (--ourIndex >= 0) n += parentItem->subItems [ourIndex]->getNumRows(); if (parentItem->parentItem == 0 && ! ownerView->rootItemVisible) --n; return n; } else { return 0; } } void TreeViewItem::setLinesDrawnForSubItems (const bool drawLines) throw() { drawLinesInside = drawLines; } TreeViewItem* TreeViewItem::getNextVisibleItem (const bool recurse) const throw() { if (recurse && isOpen() && subItems.size() > 0) return subItems [0]; if (parentItem != 0) { const int nextIndex = parentItem->subItems.indexOf (this) + 1; if (nextIndex >= parentItem->subItems.size()) return parentItem->getNextVisibleItem (false); return parentItem->subItems [nextIndex]; } return 0; } const String TreeViewItem::getItemIdentifierString() const { String s; if (parentItem != 0) s = parentItem->getItemIdentifierString(); return s + "/" + getUniqueName().replaceCharacter ('/', '\\'); } TreeViewItem* TreeViewItem::findItemFromIdentifierString (const String& identifierString) { const String thisId (getUniqueName()); if (thisId == identifierString) return this; if (identifierString.startsWith (thisId + "/")) { const String remainingPath (identifierString.substring (thisId.length() + 1)); bool wasOpen = isOpen(); setOpen (true); for (int i = subItems.size(); --i >= 0;) { TreeViewItem* item = subItems.getUnchecked(i)->findItemFromIdentifierString (remainingPath); if (item != 0) return item; } setOpen (wasOpen); } return 0; } void TreeViewItem::restoreOpennessState (const XmlElement& e) throw() { if (e.hasTagName ("CLOSED")) { setOpen (false); } else if (e.hasTagName ("OPEN")) { setOpen (true); forEachXmlChildElement (e, n) { const String id (n->getStringAttribute ("id")); for (int i = 0; i < subItems.size(); ++i) { TreeViewItem* const ti = subItems.getUnchecked(i); if (ti->getUniqueName() == id) { ti->restoreOpennessState (*n); break; } } } } } XmlElement* TreeViewItem::getOpennessState() const throw() { const String name (getUniqueName()); if (name.isNotEmpty()) { XmlElement* e; if (isOpen()) { e = new XmlElement ("OPEN"); for (int i = 0; i < subItems.size(); ++i) e->addChildElement (subItems.getUnchecked(i)->getOpennessState()); } else { e = new XmlElement ("CLOSED"); } e->setAttribute ("id", name); return e; } else { // trying to save the openness for an element that has no name - this won't // work because it needs the names to identify what to open. jassertfalse; } return 0; } END_JUCE_NAMESPACE /*** End of inlined file: juce_TreeView.cpp ***/ /*** Start of inlined file: juce_DirectoryContentsDisplayComponent.cpp ***/ BEGIN_JUCE_NAMESPACE DirectoryContentsDisplayComponent::DirectoryContentsDisplayComponent (DirectoryContentsList& listToShow) : fileList (listToShow) { } DirectoryContentsDisplayComponent::~DirectoryContentsDisplayComponent() { } FileBrowserListener::~FileBrowserListener() { } void DirectoryContentsDisplayComponent::addListener (FileBrowserListener* const listener) { listeners.add (listener); } void DirectoryContentsDisplayComponent::removeListener (FileBrowserListener* const listener) { listeners.remove (listener); } void DirectoryContentsDisplayComponent::sendSelectionChangeMessage() { Component::BailOutChecker checker (dynamic_cast (this)); listeners.callChecked (checker, &FileBrowserListener::selectionChanged); } void DirectoryContentsDisplayComponent::sendMouseClickMessage (const File& file, const MouseEvent& e) { if (fileList.getDirectory().exists()) { Component::BailOutChecker checker (dynamic_cast (this)); listeners.callChecked (checker, &FileBrowserListener::fileClicked, file, e); } } void DirectoryContentsDisplayComponent::sendDoubleClickMessage (const File& file) { if (fileList.getDirectory().exists()) { Component::BailOutChecker checker (dynamic_cast (this)); listeners.callChecked (checker, &FileBrowserListener::fileDoubleClicked, file); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_DirectoryContentsDisplayComponent.cpp ***/ /*** Start of inlined file: juce_DirectoryContentsList.cpp ***/ BEGIN_JUCE_NAMESPACE DirectoryContentsList::DirectoryContentsList (const FileFilter* const fileFilter_, TimeSliceThread& thread_) : fileFilter (fileFilter_), thread (thread_), fileTypeFlags (File::ignoreHiddenFiles | File::findFiles), fileFindHandle (0), shouldStop (true) { } DirectoryContentsList::~DirectoryContentsList() { clear(); } void DirectoryContentsList::setIgnoresHiddenFiles (const bool shouldIgnoreHiddenFiles) { setTypeFlags (shouldIgnoreHiddenFiles ? (fileTypeFlags | File::ignoreHiddenFiles) : (fileTypeFlags & ~File::ignoreHiddenFiles)); } bool DirectoryContentsList::ignoresHiddenFiles() const { return (fileTypeFlags & File::ignoreHiddenFiles) != 0; } const File& DirectoryContentsList::getDirectory() const { return root; } void DirectoryContentsList::setDirectory (const File& directory, const bool includeDirectories, const bool includeFiles) { jassert (includeDirectories || includeFiles); // you have to speciify at least one of these! if (directory != root) { clear(); root = directory; // (this forces a refresh when setTypeFlags() is called, rather than triggering two refreshes) fileTypeFlags &= ~(File::findDirectories | File::findFiles); } int newFlags = fileTypeFlags; if (includeDirectories) newFlags |= File::findDirectories; else newFlags &= ~File::findDirectories; if (includeFiles) newFlags |= File::findFiles; else newFlags &= ~File::findFiles; setTypeFlags (newFlags); } void DirectoryContentsList::setTypeFlags (const int newFlags) { if (fileTypeFlags != newFlags) { fileTypeFlags = newFlags; refresh(); } } void DirectoryContentsList::clear() { shouldStop = true; thread.removeTimeSliceClient (this); fileFindHandle = 0; if (files.size() > 0) { files.clear(); changed(); } } void DirectoryContentsList::refresh() { clear(); if (root.isDirectory()) { fileFindHandle = new DirectoryIterator (root, false, "*", fileTypeFlags); shouldStop = false; thread.addTimeSliceClient (this); } } int DirectoryContentsList::getNumFiles() const { return files.size(); } bool DirectoryContentsList::getFileInfo (const int index, FileInfo& result) const { const ScopedLock sl (fileListLock); const FileInfo* const info = files [index]; if (info != 0) { result = *info; return true; } return false; } const File DirectoryContentsList::getFile (const int index) const { const ScopedLock sl (fileListLock); const FileInfo* const info = files [index]; if (info != 0) return root.getChildFile (info->filename); return File::nonexistent; } bool DirectoryContentsList::isStillLoading() const { return fileFindHandle != 0; } void DirectoryContentsList::changed() { sendChangeMessage (this); } bool DirectoryContentsList::useTimeSlice() { const uint32 startTime = Time::getApproximateMillisecondCounter(); bool hasChanged = false; for (int i = 100; --i >= 0;) { if (! checkNextFile (hasChanged)) { if (hasChanged) changed(); return false; } if (shouldStop || (Time::getApproximateMillisecondCounter() > startTime + 150)) break; } if (hasChanged) changed(); return true; } bool DirectoryContentsList::checkNextFile (bool& hasChanged) { if (fileFindHandle != 0) { bool fileFoundIsDir, isHidden, isReadOnly; int64 fileSize; Time modTime, creationTime; if (fileFindHandle->next (&fileFoundIsDir, &isHidden, &fileSize, &modTime, &creationTime, &isReadOnly)) { if (addFile (fileFindHandle->getFile(), fileFoundIsDir, fileSize, modTime, creationTime, isReadOnly)) { hasChanged = true; } return true; } else { fileFindHandle = 0; } } return false; } int DirectoryContentsList::compareElements (const DirectoryContentsList::FileInfo* const first, const DirectoryContentsList::FileInfo* const second) { #if JUCE_WINDOWS if (first->isDirectory != second->isDirectory) return first->isDirectory ? -1 : 1; #endif return first->filename.compareIgnoreCase (second->filename); } bool DirectoryContentsList::addFile (const File& file, const bool isDir, const int64 fileSize, const Time& modTime, const Time& creationTime, const bool isReadOnly) { if (fileFilter == 0 || ((! isDir) && fileFilter->isFileSuitable (file)) || (isDir && fileFilter->isDirectorySuitable (file))) { ScopedPointer info (new FileInfo()); info->filename = file.getFileName(); info->fileSize = fileSize; info->modificationTime = modTime; info->creationTime = creationTime; info->isDirectory = isDir; info->isReadOnly = isReadOnly; const ScopedLock sl (fileListLock); for (int i = files.size(); --i >= 0;) if (files.getUnchecked(i)->filename == info->filename) return false; files.addSorted (*this, info.release()); return true; } return false; } END_JUCE_NAMESPACE /*** End of inlined file: juce_DirectoryContentsList.cpp ***/ /*** Start of inlined file: juce_FileBrowserComponent.cpp ***/ BEGIN_JUCE_NAMESPACE FileBrowserComponent::FileBrowserComponent (int flags_, const File& initialFileOrDirectory, const FileFilter* fileFilter_, FilePreviewComponent* previewComp_) : FileFilter (String::empty), fileFilter (fileFilter_), flags (flags_), previewComp (previewComp_), thread ("Juce FileBrowser") { // You need to specify one or other of the open/save flags.. jassert ((flags & (saveMode | openMode)) != 0); jassert ((flags & (saveMode | openMode)) != (saveMode | openMode)); // You need to specify at least one of these flags.. jassert ((flags & (canSelectFiles | canSelectDirectories)) != 0); String filename; if (initialFileOrDirectory == File::nonexistent) { currentRoot = File::getCurrentWorkingDirectory(); } else if (initialFileOrDirectory.isDirectory()) { currentRoot = initialFileOrDirectory; } else { chosenFiles.add (initialFileOrDirectory); currentRoot = initialFileOrDirectory.getParentDirectory(); filename = initialFileOrDirectory.getFileName(); } fileList = new DirectoryContentsList (this, thread); if ((flags & useTreeView) != 0) { FileTreeComponent* const tree = new FileTreeComponent (*fileList); if ((flags & canSelectMultipleItems) != 0) tree->setMultiSelectEnabled (true); addAndMakeVisible (tree); fileListComponent = tree; } else { FileListComponent* const list = new FileListComponent (*fileList); list->setOutlineThickness (1); if ((flags & canSelectMultipleItems) != 0) list->setMultipleSelectionEnabled (true); addAndMakeVisible (list); fileListComponent = list; } fileListComponent->addListener (this); addAndMakeVisible (currentPathBox = new ComboBox ("path")); currentPathBox->setEditableText (true); StringArray rootNames, rootPaths; const BigInteger separators (getRoots (rootNames, rootPaths)); for (int i = 0; i < rootNames.size(); ++i) { if (separators [i]) currentPathBox->addSeparator(); currentPathBox->addItem (rootNames[i], i + 1); } currentPathBox->addSeparator(); currentPathBox->addListener (this); addAndMakeVisible (filenameBox = new TextEditor()); filenameBox->setMultiLine (false); filenameBox->setSelectAllWhenFocused (true); filenameBox->setText (filename, false); filenameBox->addListener (this); filenameBox->setReadOnly ((flags & (filenameBoxIsReadOnly | canSelectMultipleItems)) != 0); Label* label = new Label ("f", TRANS("file:")); addAndMakeVisible (label); label->attachToComponent (filenameBox, true); addAndMakeVisible (goUpButton = getLookAndFeel().createFileBrowserGoUpButton()); goUpButton->addButtonListener (this); goUpButton->setTooltip (TRANS ("go up to parent directory")); if (previewComp != 0) addAndMakeVisible (previewComp); setRoot (currentRoot); thread.startThread (4); } FileBrowserComponent::~FileBrowserComponent() { if (previewComp != 0) removeChildComponent (previewComp); deleteAllChildren(); fileList = 0; thread.stopThread (10000); } void FileBrowserComponent::addListener (FileBrowserListener* const newListener) { listeners.add (newListener); } void FileBrowserComponent::removeListener (FileBrowserListener* const listener) { listeners.remove (listener); } bool FileBrowserComponent::isSaveMode() const throw() { return (flags & saveMode) != 0; } int FileBrowserComponent::getNumSelectedFiles() const throw() { if (chosenFiles.size() == 0 && currentFileIsValid()) return 1; return chosenFiles.size(); } const File FileBrowserComponent::getSelectedFile (int index) const throw() { if ((flags & canSelectDirectories) != 0 && filenameBox->getText().isEmpty()) return currentRoot; if (! filenameBox->isReadOnly()) return currentRoot.getChildFile (filenameBox->getText()); return chosenFiles[index]; } bool FileBrowserComponent::currentFileIsValid() const { if (isSaveMode()) return ! getSelectedFile (0).isDirectory(); else return getSelectedFile (0).exists(); } const File FileBrowserComponent::getHighlightedFile() const throw() { return fileListComponent->getSelectedFile (0); } void FileBrowserComponent::deselectAllFiles() { fileListComponent->deselectAllFiles(); } bool FileBrowserComponent::isFileSuitable (const File& file) const { return (flags & canSelectFiles) != 0 ? (fileFilter == 0 || fileFilter->isFileSuitable (file)) : false; } bool FileBrowserComponent::isDirectorySuitable (const File&) const { return true; } bool FileBrowserComponent::isFileOrDirSuitable (const File& f) const { if (f.isDirectory()) return (flags & canSelectDirectories) != 0 && (fileFilter == 0 || fileFilter->isDirectorySuitable (f)); return (flags & canSelectFiles) != 0 && f.exists() && (fileFilter == 0 || fileFilter->isFileSuitable (f)); } const File FileBrowserComponent::getRoot() const { return currentRoot; } void FileBrowserComponent::setRoot (const File& newRootDirectory) { if (currentRoot != newRootDirectory) { fileListComponent->scrollToTop(); String path (newRootDirectory.getFullPathName()); if (path.isEmpty()) path = File::separatorString; StringArray rootNames, rootPaths; getRoots (rootNames, rootPaths); if (! rootPaths.contains (path, true)) { bool alreadyListed = false; for (int i = currentPathBox->getNumItems(); --i >= 0;) { if (currentPathBox->getItemText (i).equalsIgnoreCase (path)) { alreadyListed = true; break; } } if (! alreadyListed) currentPathBox->addItem (path, currentPathBox->getNumItems() + 2); } } currentRoot = newRootDirectory; fileList->setDirectory (currentRoot, true, true); String currentRootName (currentRoot.getFullPathName()); if (currentRootName.isEmpty()) currentRootName = File::separatorString; currentPathBox->setText (currentRootName, true); goUpButton->setEnabled (currentRoot.getParentDirectory().isDirectory() && currentRoot.getParentDirectory() != currentRoot); } void FileBrowserComponent::goUp() { setRoot (getRoot().getParentDirectory()); } void FileBrowserComponent::refresh() { fileList->refresh(); } const String FileBrowserComponent::getActionVerb() const { return isSaveMode() ? TRANS("Save") : TRANS("Open"); } FilePreviewComponent* FileBrowserComponent::getPreviewComponent() const throw() { return previewComp; } void FileBrowserComponent::resized() { getLookAndFeel() .layoutFileBrowserComponent (*this, fileListComponent, previewComp, currentPathBox, filenameBox, goUpButton); } void FileBrowserComponent::sendListenerChangeMessage() { Component::BailOutChecker checker (this); if (previewComp != 0) previewComp->selectedFileChanged (getSelectedFile (0)); // You shouldn't delete the browser when the file gets changed! jassert (! checker.shouldBailOut()); listeners.callChecked (checker, &FileBrowserListener::selectionChanged); } void FileBrowserComponent::selectionChanged() { StringArray newFilenames; bool resetChosenFiles = true; for (int i = 0; i < fileListComponent->getNumSelectedFiles(); ++i) { const File f (fileListComponent->getSelectedFile (i)); if (isFileOrDirSuitable (f)) { if (resetChosenFiles) { chosenFiles.clear(); resetChosenFiles = false; } chosenFiles.add (f); newFilenames.add (f.getRelativePathFrom (getRoot())); } } if (newFilenames.size() > 0) filenameBox->setText (newFilenames.joinIntoString (", "), false); sendListenerChangeMessage(); } void FileBrowserComponent::fileClicked (const File& f, const MouseEvent& e) { Component::BailOutChecker checker (this); listeners.callChecked (checker, &FileBrowserListener::fileClicked, f, e); } void FileBrowserComponent::fileDoubleClicked (const File& f) { if (f.isDirectory()) { setRoot (f); if ((flags & canSelectDirectories) != 0) filenameBox->setText (String::empty); } else { Component::BailOutChecker checker (this); listeners.callChecked (checker, &FileBrowserListener::fileDoubleClicked, f); } } bool FileBrowserComponent::keyPressed (const KeyPress& key) { (void) key; #if JUCE_LINUX || JUCE_WINDOWS if (key.getModifiers().isCommandDown() && (key.getKeyCode() == 'H' || key.getKeyCode() == 'h')) { fileList->setIgnoresHiddenFiles (! fileList->ignoresHiddenFiles()); fileList->refresh(); return true; } #endif return false; } void FileBrowserComponent::textEditorTextChanged (TextEditor&) { sendListenerChangeMessage(); } void FileBrowserComponent::textEditorReturnKeyPressed (TextEditor&) { if (filenameBox->getText().containsChar (File::separator)) { const File f (currentRoot.getChildFile (filenameBox->getText())); if (f.isDirectory()) { setRoot (f); chosenFiles.clear(); filenameBox->setText (String::empty); } else { setRoot (f.getParentDirectory()); chosenFiles.clear(); chosenFiles.add (f); filenameBox->setText (f.getFileName()); } } else { fileDoubleClicked (getSelectedFile (0)); } } void FileBrowserComponent::textEditorEscapeKeyPressed (TextEditor&) { } void FileBrowserComponent::textEditorFocusLost (TextEditor&) { if (! isSaveMode()) selectionChanged(); } void FileBrowserComponent::buttonClicked (Button*) { goUp(); } void FileBrowserComponent::comboBoxChanged (ComboBox*) { const String newText (currentPathBox->getText().trim().unquoted()); if (newText.isNotEmpty()) { const int index = currentPathBox->getSelectedId() - 1; StringArray rootNames, rootPaths; getRoots (rootNames, rootPaths); if (rootPaths [index].isNotEmpty()) { setRoot (File (rootPaths [index])); } else { File f (newText); for (;;) { if (f.isDirectory()) { setRoot (f); break; } if (f.getParentDirectory() == f) break; f = f.getParentDirectory(); } } } } const BigInteger FileBrowserComponent::getRoots (StringArray& rootNames, StringArray& rootPaths) { BigInteger separators; #if JUCE_WINDOWS Array roots; File::findFileSystemRoots (roots); rootPaths.clear(); for (int i = 0; i < roots.size(); ++i) { const File& drive = roots.getReference(i); String name (drive.getFullPathName()); rootPaths.add (name); if (drive.isOnHardDisk()) { String volume (drive.getVolumeLabel()); if (volume.isEmpty()) volume = TRANS("Hard Drive"); name << " [" << drive.getVolumeLabel() << ']'; } else if (drive.isOnCDRomDrive()) { name << TRANS(" [CD/DVD drive]"); } rootNames.add (name); } separators.setBit (rootPaths.size()); rootPaths.add (File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName()); rootNames.add ("Documents"); rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName()); rootNames.add ("Desktop"); #endif #if JUCE_MAC rootPaths.add (File::getSpecialLocation (File::userHomeDirectory).getFullPathName()); rootNames.add ("Home folder"); rootPaths.add (File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName()); rootNames.add ("Documents"); rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName()); rootNames.add ("Desktop"); separators.setBit (rootPaths.size()); Array volumes; File vol ("/Volumes"); vol.findChildFiles (volumes, File::findDirectories, false); for (int i = 0; i < volumes.size(); ++i) { const File& volume = volumes.getReference(i); if (volume.isDirectory() && ! volume.getFileName().startsWithChar ('.')) { rootPaths.add (volume.getFullPathName()); rootNames.add (volume.getFileName()); } } #endif #if JUCE_LINUX rootPaths.add ("/"); rootNames.add ("/"); rootPaths.add (File::getSpecialLocation (File::userHomeDirectory).getFullPathName()); rootNames.add ("Home folder"); rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName()); rootNames.add ("Desktop"); #endif return separators; } END_JUCE_NAMESPACE /*** End of inlined file: juce_FileBrowserComponent.cpp ***/ /*** Start of inlined file: juce_FileChooser.cpp ***/ BEGIN_JUCE_NAMESPACE FileChooser::FileChooser (const String& chooserBoxTitle, const File& currentFileOrDirectory, const String& fileFilters, const bool useNativeDialogBox_) : title (chooserBoxTitle), filters (fileFilters), startingFile (currentFileOrDirectory), useNativeDialogBox (useNativeDialogBox_) { #if JUCE_LINUX useNativeDialogBox = false; #endif if (! fileFilters.containsNonWhitespaceChars()) filters = "*"; } FileChooser::~FileChooser() { } bool FileChooser::browseForFileToOpen (FilePreviewComponent* previewComponent) { return showDialog (false, true, false, false, false, previewComponent); } bool FileChooser::browseForMultipleFilesToOpen (FilePreviewComponent* previewComponent) { return showDialog (false, true, false, false, true, previewComponent); } bool FileChooser::browseForMultipleFilesOrDirectories (FilePreviewComponent* previewComponent) { return showDialog (true, true, false, false, true, previewComponent); } bool FileChooser::browseForFileToSave (const bool warnAboutOverwritingExistingFiles) { return showDialog (false, true, true, warnAboutOverwritingExistingFiles, false, 0); } bool FileChooser::browseForDirectory() { return showDialog (true, false, false, false, false, 0); } const File FileChooser::getResult() const { // if you've used a multiple-file select, you should use the getResults() method // to retrieve all the files that were chosen. jassert (results.size() <= 1); return results.getFirst(); } const Array& FileChooser::getResults() const { return results; } bool FileChooser::showDialog (const bool selectsDirectories, const bool selectsFiles, const bool isSave, const bool warnAboutOverwritingExistingFiles, const bool selectMultipleFiles, FilePreviewComponent* const previewComponent) { Component::SafePointer previouslyFocused (Component::getCurrentlyFocusedComponent()); results.clear(); // the preview component needs to be the right size before you pass it in here.. jassert (previewComponent == 0 || (previewComponent->getWidth() > 10 && previewComponent->getHeight() > 10)); #if JUCE_WINDOWS if (useNativeDialogBox && ! (selectsFiles && selectsDirectories)) #elif JUCE_MAC if (useNativeDialogBox && (previewComponent == 0)) #else if (false) #endif { showPlatformDialog (results, title, startingFile, filters, selectsDirectories, selectsFiles, isSave, warnAboutOverwritingExistingFiles, selectMultipleFiles, previewComponent); } else { WildcardFileFilter wildcard (selectsFiles ? filters : String::empty, selectsDirectories ? "*" : String::empty, String::empty); int flags = isSave ? FileBrowserComponent::saveMode : FileBrowserComponent::openMode; if (selectsFiles) flags |= FileBrowserComponent::canSelectFiles; if (selectsDirectories) { flags |= FileBrowserComponent::canSelectDirectories; if (! isSave) flags |= FileBrowserComponent::filenameBoxIsReadOnly; } if (selectMultipleFiles) flags |= FileBrowserComponent::canSelectMultipleItems; FileBrowserComponent browserComponent (flags, startingFile, &wildcard, previewComponent); FileChooserDialogBox box (title, String::empty, browserComponent, warnAboutOverwritingExistingFiles, browserComponent.findColour (AlertWindow::backgroundColourId)); if (box.show()) { for (int i = 0; i < browserComponent.getNumSelectedFiles(); ++i) results.add (browserComponent.getSelectedFile (i)); } } if (previouslyFocused != 0) previouslyFocused->grabKeyboardFocus(); return results.size() > 0; } FilePreviewComponent::FilePreviewComponent() { } FilePreviewComponent::~FilePreviewComponent() { } END_JUCE_NAMESPACE /*** End of inlined file: juce_FileChooser.cpp ***/ /*** Start of inlined file: juce_FileChooserDialogBox.cpp ***/ BEGIN_JUCE_NAMESPACE FileChooserDialogBox::FileChooserDialogBox (const String& name, const String& instructions, FileBrowserComponent& chooserComponent, const bool warnAboutOverwritingExistingFiles_, const Colour& backgroundColour) : ResizableWindow (name, backgroundColour, true), warnAboutOverwritingExistingFiles (warnAboutOverwritingExistingFiles_) { setContentComponent (content = new ContentComponent (name, instructions, chooserComponent)); setResizable (true, true); setResizeLimits (300, 300, 1200, 1000); content->okButton.addButtonListener (this); content->cancelButton.addButtonListener (this); content->chooserComponent.addListener (this); } FileChooserDialogBox::~FileChooserDialogBox() { content->chooserComponent.removeListener (this); } bool FileChooserDialogBox::show (int w, int h) { return showAt (-1, -1, w, h); } bool FileChooserDialogBox::showAt (int x, int y, int w, int h) { if (w <= 0) { Component* const previewComp = content->chooserComponent.getPreviewComponent(); if (previewComp != 0) w = 400 + previewComp->getWidth(); else w = 600; } if (h <= 0) h = 500; if (x < 0 || y < 0) centreWithSize (w, h); else setBounds (x, y, w, h); const bool ok = (runModalLoop() != 0); setVisible (false); return ok; } void FileChooserDialogBox::buttonClicked (Button* button) { if (button == &(content->okButton)) { if (warnAboutOverwritingExistingFiles && content->chooserComponent.isSaveMode() && content->chooserComponent.getSelectedFile(0).exists()) { if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, TRANS("File already exists"), TRANS("There's already a file called:") + "\n\n" + content->chooserComponent.getSelectedFile(0).getFullPathName() + "\n\n" + TRANS("Are you sure you want to overwrite it?"), TRANS("overwrite"), TRANS("cancel"))) { return; } } exitModalState (1); } else if (button == &(content->cancelButton)) { closeButtonPressed(); } } void FileChooserDialogBox::closeButtonPressed() { setVisible (false); } void FileChooserDialogBox::selectionChanged() { content->okButton.setEnabled (content->chooserComponent.currentFileIsValid()); } void FileChooserDialogBox::fileClicked (const File&, const MouseEvent&) { } void FileChooserDialogBox::fileDoubleClicked (const File&) { selectionChanged(); content->okButton.triggerClick(); } FileChooserDialogBox::ContentComponent::ContentComponent (const String& name, const String& instructions_, FileBrowserComponent& chooserComponent_) : Component (name), instructions (instructions_), chooserComponent (chooserComponent_), okButton (chooserComponent_.getActionVerb()), cancelButton (TRANS ("Cancel")) { addAndMakeVisible (&chooserComponent); addAndMakeVisible (&okButton); okButton.setEnabled (chooserComponent.currentFileIsValid()); okButton.addShortcut (KeyPress (KeyPress::returnKey, 0, 0)); addAndMakeVisible (&cancelButton); cancelButton.addShortcut (KeyPress (KeyPress::escapeKey, 0, 0)); setInterceptsMouseClicks (false, true); } FileChooserDialogBox::ContentComponent::~ContentComponent() { } void FileChooserDialogBox::ContentComponent::paint (Graphics& g) { g.setColour (getLookAndFeel().findColour (FileChooserDialogBox::titleTextColourId)); text.draw (g); } void FileChooserDialogBox::ContentComponent::resized() { getLookAndFeel().createFileChooserHeaderText (getName(), instructions, text, getWidth()); const Rectangle bb (text.getBoundingBox (0, text.getNumGlyphs(), false)); const int y = roundToInt (bb.getBottom()) + 10; const int buttonHeight = 26; const int buttonY = getHeight() - buttonHeight - 8; chooserComponent.setBounds (0, y, getWidth(), buttonY - y - 20); okButton.setBounds (proportionOfWidth (0.25f), buttonY, proportionOfWidth (0.2f), buttonHeight); cancelButton.setBounds (proportionOfWidth (0.55f), buttonY, proportionOfWidth (0.2f), buttonHeight); } END_JUCE_NAMESPACE /*** End of inlined file: juce_FileChooserDialogBox.cpp ***/ /*** Start of inlined file: juce_FileFilter.cpp ***/ BEGIN_JUCE_NAMESPACE FileFilter::FileFilter (const String& filterDescription) : description (filterDescription) { } FileFilter::~FileFilter() { } const String& FileFilter::getDescription() const throw() { return description; } END_JUCE_NAMESPACE /*** End of inlined file: juce_FileFilter.cpp ***/ /*** Start of inlined file: juce_FileListComponent.cpp ***/ BEGIN_JUCE_NAMESPACE const Image juce_createIconForFile (const File& file); FileListComponent::FileListComponent (DirectoryContentsList& listToShow) : ListBox (String::empty, 0), DirectoryContentsDisplayComponent (listToShow) { setModel (this); fileList.addChangeListener (this); } FileListComponent::~FileListComponent() { fileList.removeChangeListener (this); } int FileListComponent::getNumSelectedFiles() const { return getNumSelectedRows(); } const File FileListComponent::getSelectedFile (int index) const { return fileList.getFile (getSelectedRow (index)); } void FileListComponent::deselectAllFiles() { deselectAllRows(); } void FileListComponent::scrollToTop() { getVerticalScrollBar()->setCurrentRangeStart (0); } void FileListComponent::changeListenerCallback (void*) { updateContent(); if (lastDirectory != fileList.getDirectory()) { lastDirectory = fileList.getDirectory(); deselectAllRows(); } } class FileListItemComponent : public Component, public TimeSliceClient, public AsyncUpdater { public: FileListItemComponent (FileListComponent& owner_, TimeSliceThread& thread_) : owner (owner_), thread (thread_), highlighted (false), index (0), icon (0) { } ~FileListItemComponent() { thread.removeTimeSliceClient (this); clearIcon(); } void paint (Graphics& g) { getLookAndFeel().drawFileBrowserRow (g, getWidth(), getHeight(), file.getFileName(), &icon, fileSize, modTime, isDirectory, highlighted, index); } void mouseDown (const MouseEvent& e) { owner.selectRowsBasedOnModifierKeys (index, e.mods); owner.sendMouseClickMessage (file, e); } void mouseDoubleClick (const MouseEvent&) { owner.sendDoubleClickMessage (file); } void update (const File& root, const DirectoryContentsList::FileInfo* const fileInfo, const int index_, const bool highlighted_) { thread.removeTimeSliceClient (this); if (highlighted_ != highlighted || index_ != index) { index = index_; highlighted = highlighted_; repaint(); } File newFile; String newFileSize; String newModTime; if (fileInfo != 0) { newFile = root.getChildFile (fileInfo->filename); newFileSize = File::descriptionOfSizeInBytes (fileInfo->fileSize); newModTime = fileInfo->modificationTime.formatted ("%d %b '%y %H:%M"); } if (newFile != file || fileSize != newFileSize || modTime != newModTime) { file = newFile; fileSize = newFileSize; modTime = newModTime; isDirectory = fileInfo != 0 && fileInfo->isDirectory; repaint(); clearIcon(); } if (file != File::nonexistent && icon.isNull() && ! isDirectory) { updateIcon (true); if (! icon.isValid()) thread.addTimeSliceClient (this); } } bool useTimeSlice() { updateIcon (false); return false; } void handleAsyncUpdate() { repaint(); } juce_UseDebuggingNewOperator private: FileListComponent& owner; TimeSliceThread& thread; bool highlighted; int index; File file; String fileSize; String modTime; Image icon; bool isDirectory; void clearIcon() { icon = Image::null; } void updateIcon (const bool onlyUpdateIfCached) { if (icon.isNull()) { const int hashCode = (file.getFullPathName() + "_iconCacheSalt").hashCode(); Image im (ImageCache::getFromHashCode (hashCode)); if (im.isNull() && ! onlyUpdateIfCached) { im = juce_createIconForFile (file); if (im.isValid()) ImageCache::addImageToCache (im, hashCode); } if (im.isValid()) { icon = im; triggerAsyncUpdate(); } } } }; int FileListComponent::getNumRows() { return fileList.getNumFiles(); } void FileListComponent::paintListBoxItem (int, Graphics&, int, int, bool) { } Component* FileListComponent::refreshComponentForRow (int row, bool isSelected, Component* existingComponentToUpdate) { FileListItemComponent* comp = dynamic_cast (existingComponentToUpdate); if (comp == 0) { delete existingComponentToUpdate; comp = new FileListItemComponent (*this, fileList.getTimeSliceThread()); } DirectoryContentsList::FileInfo fileInfo; if (fileList.getFileInfo (row, fileInfo)) comp->update (fileList.getDirectory(), &fileInfo, row, isSelected); else comp->update (fileList.getDirectory(), 0, row, isSelected); return comp; } void FileListComponent::selectedRowsChanged (int /*lastRowSelected*/) { sendSelectionChangeMessage(); } void FileListComponent::deleteKeyPressed (int /*currentSelectedRow*/) { } void FileListComponent::returnKeyPressed (int currentSelectedRow) { sendDoubleClickMessage (fileList.getFile (currentSelectedRow)); } END_JUCE_NAMESPACE /*** End of inlined file: juce_FileListComponent.cpp ***/ /*** Start of inlined file: juce_FilenameComponent.cpp ***/ BEGIN_JUCE_NAMESPACE FilenameComponent::FilenameComponent (const String& name, const File& currentFile, const bool canEditFilename, const bool isDirectory, const bool isForSaving, const String& fileBrowserWildcard, const String& enforcedSuffix_, const String& textWhenNothingSelected) : Component (name), maxRecentFiles (30), isDir (isDirectory), isSaving (isForSaving), isFileDragOver (false), wildcard (fileBrowserWildcard), enforcedSuffix (enforcedSuffix_) { addAndMakeVisible (&filenameBox); filenameBox.setEditableText (canEditFilename); filenameBox.addListener (this); filenameBox.setTextWhenNothingSelected (textWhenNothingSelected); filenameBox.setTextWhenNoChoicesAvailable (TRANS("(no recently seleced files)")); setBrowseButtonText ("..."); setCurrentFile (currentFile, true); } FilenameComponent::~FilenameComponent() { } void FilenameComponent::paintOverChildren (Graphics& g) { if (isFileDragOver) { g.setColour (Colours::red.withAlpha (0.2f)); g.drawRect (0, 0, getWidth(), getHeight(), 3); } } void FilenameComponent::resized() { getLookAndFeel().layoutFilenameComponent (*this, &filenameBox, browseButton); } void FilenameComponent::setBrowseButtonText (const String& newBrowseButtonText) { browseButtonText = newBrowseButtonText; lookAndFeelChanged(); } void FilenameComponent::lookAndFeelChanged() { browseButton = 0; addAndMakeVisible (browseButton = getLookAndFeel().createFilenameComponentBrowseButton (browseButtonText)); browseButton->setConnectedEdges (Button::ConnectedOnLeft); resized(); browseButton->addButtonListener (this); } void FilenameComponent::setTooltip (const String& newTooltip) { SettableTooltipClient::setTooltip (newTooltip); filenameBox.setTooltip (newTooltip); } void FilenameComponent::setDefaultBrowseTarget (const File& newDefaultDirectory) { defaultBrowseFile = newDefaultDirectory; } void FilenameComponent::buttonClicked (Button*) { FileChooser fc (TRANS("Choose a new file"), getCurrentFile() == File::nonexistent ? defaultBrowseFile : getCurrentFile(), wildcard); if (isDir ? fc.browseForDirectory() : (isSaving ? fc.browseForFileToSave (false) : fc.browseForFileToOpen())) { setCurrentFile (fc.getResult(), true); } } void FilenameComponent::comboBoxChanged (ComboBox*) { setCurrentFile (getCurrentFile(), true); } bool FilenameComponent::isInterestedInFileDrag (const StringArray&) { return true; } void FilenameComponent::filesDropped (const StringArray& filenames, int, int) { isFileDragOver = false; repaint(); const File f (filenames[0]); if (f.exists() && (f.isDirectory() == isDir)) setCurrentFile (f, true); } void FilenameComponent::fileDragEnter (const StringArray&, int, int) { isFileDragOver = true; repaint(); } void FilenameComponent::fileDragExit (const StringArray&) { isFileDragOver = false; repaint(); } const File FilenameComponent::getCurrentFile() const { File f (filenameBox.getText()); if (enforcedSuffix.isNotEmpty()) f = f.withFileExtension (enforcedSuffix); return f; } void FilenameComponent::setCurrentFile (File newFile, const bool addToRecentlyUsedList, const bool sendChangeNotification) { if (enforcedSuffix.isNotEmpty()) newFile = newFile.withFileExtension (enforcedSuffix); if (newFile.getFullPathName() != lastFilename) { lastFilename = newFile.getFullPathName(); if (addToRecentlyUsedList) addRecentlyUsedFile (newFile); filenameBox.setText (lastFilename, true); if (sendChangeNotification) triggerAsyncUpdate(); } } void FilenameComponent::setFilenameIsEditable (const bool shouldBeEditable) { filenameBox.setEditableText (shouldBeEditable); } const StringArray FilenameComponent::getRecentlyUsedFilenames() const { StringArray names; for (int i = 0; i < filenameBox.getNumItems(); ++i) names.add (filenameBox.getItemText (i)); return names; } void FilenameComponent::setRecentlyUsedFilenames (const StringArray& filenames) { if (filenames != getRecentlyUsedFilenames()) { filenameBox.clear(); for (int i = 0; i < jmin (filenames.size(), maxRecentFiles); ++i) filenameBox.addItem (filenames[i], i + 1); } } void FilenameComponent::setMaxNumberOfRecentFiles (const int newMaximum) { maxRecentFiles = jmax (1, newMaximum); setRecentlyUsedFilenames (getRecentlyUsedFilenames()); } void FilenameComponent::addRecentlyUsedFile (const File& file) { StringArray files (getRecentlyUsedFilenames()); if (file.getFullPathName().isNotEmpty()) { files.removeString (file.getFullPathName(), true); files.insert (0, file.getFullPathName()); setRecentlyUsedFilenames (files); } } void FilenameComponent::addListener (FilenameComponentListener* const listener) { listeners.add (listener); } void FilenameComponent::removeListener (FilenameComponentListener* const listener) { listeners.remove (listener); } void FilenameComponent::handleAsyncUpdate() { Component::BailOutChecker checker (this); listeners.callChecked (checker, &FilenameComponentListener::filenameComponentChanged, this); } END_JUCE_NAMESPACE /*** End of inlined file: juce_FilenameComponent.cpp ***/ /*** Start of inlined file: juce_FileSearchPathListComponent.cpp ***/ BEGIN_JUCE_NAMESPACE FileSearchPathListComponent::FileSearchPathListComponent() { addAndMakeVisible (listBox = new ListBox (String::empty, this)); listBox->setColour (ListBox::backgroundColourId, Colours::black.withAlpha (0.02f)); listBox->setColour (ListBox::outlineColourId, Colours::black.withAlpha (0.1f)); listBox->setOutlineThickness (1); addAndMakeVisible (addButton = new TextButton ("+")); addButton->addButtonListener (this); addButton->setConnectedEdges (Button::ConnectedOnLeft | Button::ConnectedOnRight | Button::ConnectedOnBottom | Button::ConnectedOnTop); addAndMakeVisible (removeButton = new TextButton ("-")); removeButton->addButtonListener (this); removeButton->setConnectedEdges (Button::ConnectedOnLeft | Button::ConnectedOnRight | Button::ConnectedOnBottom | Button::ConnectedOnTop); addAndMakeVisible (changeButton = new TextButton (TRANS("change..."))); changeButton->addButtonListener (this); addAndMakeVisible (upButton = new DrawableButton (String::empty, DrawableButton::ImageOnButtonBackground)); upButton->addButtonListener (this); { Path arrowPath; arrowPath.addArrow (Line (50.0f, 100.0f, 50.0f, 0.0f), 40.0f, 100.0f, 50.0f); DrawablePath arrowImage; arrowImage.setFill (Colours::black.withAlpha (0.4f)); arrowImage.setPath (arrowPath); upButton->setImages (&arrowImage); } addAndMakeVisible (downButton = new DrawableButton (String::empty, DrawableButton::ImageOnButtonBackground)); downButton->addButtonListener (this); { Path arrowPath; arrowPath.addArrow (Line (50.0f, 0.0f, 50.0f, 100.0f), 40.0f, 100.0f, 50.0f); DrawablePath arrowImage; arrowImage.setFill (Colours::black.withAlpha (0.4f)); arrowImage.setPath (arrowPath); downButton->setImages (&arrowImage); } updateButtons(); } FileSearchPathListComponent::~FileSearchPathListComponent() { deleteAllChildren(); } void FileSearchPathListComponent::updateButtons() { const bool anythingSelected = listBox->getNumSelectedRows() > 0; removeButton->setEnabled (anythingSelected); changeButton->setEnabled (anythingSelected); upButton->setEnabled (anythingSelected); downButton->setEnabled (anythingSelected); } void FileSearchPathListComponent::changed() { listBox->updateContent(); listBox->repaint(); updateButtons(); } void FileSearchPathListComponent::setPath (const FileSearchPath& newPath) { if (newPath.toString() != path.toString()) { path = newPath; changed(); } } void FileSearchPathListComponent::setDefaultBrowseTarget (const File& newDefaultDirectory) { defaultBrowseTarget = newDefaultDirectory; } int FileSearchPathListComponent::getNumRows() { return path.getNumPaths(); } void FileSearchPathListComponent::paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected) { if (rowIsSelected) g.fillAll (findColour (TextEditor::highlightColourId)); g.setColour (findColour (ListBox::textColourId)); Font f (height * 0.7f); f.setHorizontalScale (0.9f); g.setFont (f); g.drawText (path [rowNumber].getFullPathName(), 4, 0, width - 6, height, Justification::centredLeft, true); } void FileSearchPathListComponent::deleteKeyPressed (int row) { if (((unsigned int) row) < (unsigned int) path.getNumPaths()) { path.remove (row); changed(); } } void FileSearchPathListComponent::returnKeyPressed (int row) { FileChooser chooser (TRANS("Change folder..."), path [row], "*"); if (chooser.browseForDirectory()) { path.remove (row); path.add (chooser.getResult(), row); changed(); } } void FileSearchPathListComponent::listBoxItemDoubleClicked (int row, const MouseEvent&) { returnKeyPressed (row); } void FileSearchPathListComponent::selectedRowsChanged (int) { updateButtons(); } void FileSearchPathListComponent::paint (Graphics& g) { g.fillAll (findColour (backgroundColourId)); } void FileSearchPathListComponent::resized() { const int buttonH = 22; const int buttonY = getHeight() - buttonH - 4; listBox->setBounds (2, 2, getWidth() - 4, buttonY - 5); addButton->setBounds (2, buttonY, buttonH, buttonH); removeButton->setBounds (addButton->getRight(), buttonY, buttonH, buttonH); changeButton->changeWidthToFitText (buttonH); downButton->setSize (buttonH * 2, buttonH); upButton->setSize (buttonH * 2, buttonH); downButton->setTopRightPosition (getWidth() - 2, buttonY); upButton->setTopRightPosition (downButton->getX() - 4, buttonY); changeButton->setTopRightPosition (upButton->getX() - 8, buttonY); } bool FileSearchPathListComponent::isInterestedInFileDrag (const StringArray&) { return true; } void FileSearchPathListComponent::filesDropped (const StringArray& filenames, int, int mouseY) { for (int i = filenames.size(); --i >= 0;) { const File f (filenames[i]); if (f.isDirectory()) { const int row = listBox->getRowContainingPosition (0, mouseY - listBox->getY()); path.add (f, row); changed(); } } } void FileSearchPathListComponent::buttonClicked (Button* button) { const int currentRow = listBox->getSelectedRow(); if (button == removeButton) { deleteKeyPressed (currentRow); } else if (button == addButton) { File start (defaultBrowseTarget); if (start == File::nonexistent) start = path [0]; if (start == File::nonexistent) start = File::getCurrentWorkingDirectory(); FileChooser chooser (TRANS("Add a folder..."), start, "*"); if (chooser.browseForDirectory()) { path.add (chooser.getResult(), currentRow); } } else if (button == changeButton) { returnKeyPressed (currentRow); } else if (button == upButton) { if (currentRow > 0 && currentRow < path.getNumPaths()) { const File f (path[currentRow]); path.remove (currentRow); path.add (f, currentRow - 1); listBox->selectRow (currentRow - 1); } } else if (button == downButton) { if (currentRow >= 0 && currentRow < path.getNumPaths() - 1) { const File f (path[currentRow]); path.remove (currentRow); path.add (f, currentRow + 1); listBox->selectRow (currentRow + 1); } } changed(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_FileSearchPathListComponent.cpp ***/ /*** Start of inlined file: juce_FileTreeComponent.cpp ***/ BEGIN_JUCE_NAMESPACE const Image juce_createIconForFile (const File& file); class FileListTreeItem : public TreeViewItem, public TimeSliceClient, public AsyncUpdater, public ChangeListener { public: FileListTreeItem (FileTreeComponent& owner_, DirectoryContentsList* const parentContentsList_, const int indexInContentsList_, const File& file_, TimeSliceThread& thread_) : file (file_), owner (owner_), parentContentsList (parentContentsList_), indexInContentsList (indexInContentsList_), subContentsList (0), canDeleteSubContentsList (false), thread (thread_), icon (0) { DirectoryContentsList::FileInfo fileInfo; if (parentContentsList_ != 0 && parentContentsList_->getFileInfo (indexInContentsList_, fileInfo)) { fileSize = File::descriptionOfSizeInBytes (fileInfo.fileSize); modTime = fileInfo.modificationTime.formatted ("%d %b '%y %H:%M"); isDirectory = fileInfo.isDirectory; } else { isDirectory = true; } } ~FileListTreeItem() { thread.removeTimeSliceClient (this); clearSubItems(); if (canDeleteSubContentsList) delete subContentsList; } bool mightContainSubItems() { return isDirectory; } const String getUniqueName() const { return file.getFullPathName(); } int getItemHeight() const { return 22; } const String getDragSourceDescription() { return owner.getDragAndDropDescription(); } void itemOpennessChanged (bool isNowOpen) { if (isNowOpen) { clearSubItems(); isDirectory = file.isDirectory(); if (isDirectory) { if (subContentsList == 0) { jassert (parentContentsList != 0); DirectoryContentsList* const l = new DirectoryContentsList (parentContentsList->getFilter(), thread); l->setDirectory (file, true, true); setSubContentsList (l); canDeleteSubContentsList = true; } changeListenerCallback (0); } } } void setSubContentsList (DirectoryContentsList* newList) { jassert (subContentsList == 0); subContentsList = newList; newList->addChangeListener (this); } void changeListenerCallback (void*) { clearSubItems(); if (isOpen() && subContentsList != 0) { for (int i = 0; i < subContentsList->getNumFiles(); ++i) { FileListTreeItem* const item = new FileListTreeItem (owner, subContentsList, i, subContentsList->getFile(i), thread); addSubItem (item); } } } void paintItem (Graphics& g, int width, int height) { if (file != File::nonexistent) { updateIcon (true); if (icon.isNull()) thread.addTimeSliceClient (this); } owner.getLookAndFeel() .drawFileBrowserRow (g, width, height, file.getFileName(), &icon, fileSize, modTime, isDirectory, isSelected(), indexInContentsList); } void itemClicked (const MouseEvent& e) { owner.sendMouseClickMessage (file, e); } void itemDoubleClicked (const MouseEvent& e) { TreeViewItem::itemDoubleClicked (e); owner.sendDoubleClickMessage (file); } void itemSelectionChanged (bool) { owner.sendSelectionChangeMessage(); } bool useTimeSlice() { updateIcon (false); thread.removeTimeSliceClient (this); return false; } void handleAsyncUpdate() { owner.repaint(); } const File file; juce_UseDebuggingNewOperator private: FileTreeComponent& owner; DirectoryContentsList* parentContentsList; int indexInContentsList; DirectoryContentsList* subContentsList; bool isDirectory, canDeleteSubContentsList; TimeSliceThread& thread; Image icon; String fileSize; String modTime; void updateIcon (const bool onlyUpdateIfCached) { if (icon.isNull()) { const int hashCode = (file.getFullPathName() + "_iconCacheSalt").hashCode(); Image im (ImageCache::getFromHashCode (hashCode)); if (im.isNull() && ! onlyUpdateIfCached) { im = juce_createIconForFile (file); if (im.isValid()) ImageCache::addImageToCache (im, hashCode); } if (im.isValid()) { icon = im; triggerAsyncUpdate(); } } } }; FileTreeComponent::FileTreeComponent (DirectoryContentsList& listToShow) : DirectoryContentsDisplayComponent (listToShow) { FileListTreeItem* const root = new FileListTreeItem (*this, 0, 0, listToShow.getDirectory(), listToShow.getTimeSliceThread()); root->setSubContentsList (&listToShow); setRootItemVisible (false); setRootItem (root); } FileTreeComponent::~FileTreeComponent() { deleteRootItem(); } const File FileTreeComponent::getSelectedFile (const int index) const { const FileListTreeItem* const item = dynamic_cast (getSelectedItem (index)); return item != 0 ? item->file : File::nonexistent; } void FileTreeComponent::deselectAllFiles() { clearSelectedItems(); } void FileTreeComponent::scrollToTop() { getViewport()->getVerticalScrollBar()->setCurrentRangeStart (0); } void FileTreeComponent::setDragAndDropDescription (const String& description) { dragAndDropDescription = description; } END_JUCE_NAMESPACE /*** End of inlined file: juce_FileTreeComponent.cpp ***/ /*** Start of inlined file: juce_ImagePreviewComponent.cpp ***/ BEGIN_JUCE_NAMESPACE ImagePreviewComponent::ImagePreviewComponent() { } ImagePreviewComponent::~ImagePreviewComponent() { } void ImagePreviewComponent::getThumbSize (int& w, int& h) const { const int availableW = proportionOfWidth (0.97f); const int availableH = getHeight() - 13 * 4; const double scale = jmin (1.0, availableW / (double) w, availableH / (double) h); w = roundToInt (scale * w); h = roundToInt (scale * h); } void ImagePreviewComponent::selectedFileChanged (const File& file) { if (fileToLoad != file) { fileToLoad = file; startTimer (100); } } void ImagePreviewComponent::timerCallback() { stopTimer(); currentThumbnail = Image::null; currentDetails = String::empty; repaint(); ScopedPointer in (fileToLoad.createInputStream()); if (in != 0) { ImageFileFormat* const format = ImageFileFormat::findImageFormatForStream (*in); if (format != 0) { currentThumbnail = format->decodeImage (*in); if (currentThumbnail.isValid()) { int w = currentThumbnail.getWidth(); int h = currentThumbnail.getHeight(); currentDetails << fileToLoad.getFileName() << "\n" << format->getFormatName() << "\n" << w << " x " << h << " pixels\n" << File::descriptionOfSizeInBytes (fileToLoad.getSize()); getThumbSize (w, h); currentThumbnail = currentThumbnail.rescaled (w, h); } } } } void ImagePreviewComponent::paint (Graphics& g) { if (currentThumbnail.isValid()) { g.setFont (13.0f); int w = currentThumbnail.getWidth(); int h = currentThumbnail.getHeight(); getThumbSize (w, h); const int numLines = 4; const int totalH = 13 * numLines + h + 4; const int y = (getHeight() - totalH) / 2; g.drawImageWithin (currentThumbnail, (getWidth() - w) / 2, y, w, h, RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, false); g.drawFittedText (currentDetails, 0, y + h + 4, getWidth(), 100, Justification::centredTop, numLines); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ImagePreviewComponent.cpp ***/ /*** Start of inlined file: juce_WildcardFileFilter.cpp ***/ BEGIN_JUCE_NAMESPACE WildcardFileFilter::WildcardFileFilter (const String& fileWildcardPatterns, const String& directoryWildcardPatterns, const String& description_) : FileFilter (description_.isEmpty() ? fileWildcardPatterns : (description_ + " (" + fileWildcardPatterns + ")")) { parse (fileWildcardPatterns, fileWildcards); parse (directoryWildcardPatterns, directoryWildcards); } WildcardFileFilter::~WildcardFileFilter() { } bool WildcardFileFilter::isFileSuitable (const File& file) const { return match (file, fileWildcards); } bool WildcardFileFilter::isDirectorySuitable (const File& file) const { return match (file, directoryWildcards); } void WildcardFileFilter::parse (const String& pattern, StringArray& result) { result.addTokens (pattern.toLowerCase(), ";,", "\"'"); result.trim(); result.removeEmptyStrings(); // special case for *.*, because people use it to mean "any file", but it // would actually ignore files with no extension. for (int i = result.size(); --i >= 0;) if (result[i] == "*.*") result.set (i, "*"); } bool WildcardFileFilter::match (const File& file, const StringArray& wildcards) { const String filename (file.getFileName()); for (int i = wildcards.size(); --i >= 0;) if (filename.matchesWildcard (wildcards[i], true)) return true; return false; } END_JUCE_NAMESPACE /*** End of inlined file: juce_WildcardFileFilter.cpp ***/ /*** Start of inlined file: juce_KeyboardFocusTraverser.cpp ***/ BEGIN_JUCE_NAMESPACE KeyboardFocusTraverser::KeyboardFocusTraverser() { } KeyboardFocusTraverser::~KeyboardFocusTraverser() { } namespace KeyboardFocusHelpers { // This will sort a set of components, so that they are ordered in terms of // left-to-right and then top-to-bottom. class ScreenPositionComparator { public: ScreenPositionComparator() {} static int compareElements (const Component* const first, const Component* const second) { int explicitOrder1 = first->getExplicitFocusOrder(); if (explicitOrder1 <= 0) explicitOrder1 = std::numeric_limits::max() / 2; int explicitOrder2 = second->getExplicitFocusOrder(); if (explicitOrder2 <= 0) explicitOrder2 = std::numeric_limits::max() / 2; if (explicitOrder1 != explicitOrder2) return explicitOrder1 - explicitOrder2; const int diff = first->getY() - second->getY(); return (diff == 0) ? first->getX() - second->getX() : diff; } }; static void findAllFocusableComponents (Component* const parent, Array & comps) { if (parent->getNumChildComponents() > 0) { Array localComps; ScreenPositionComparator comparator; int i; for (i = parent->getNumChildComponents(); --i >= 0;) { Component* const c = parent->getChildComponent (i); if (c->isVisible() && c->isEnabled()) localComps.addSorted (comparator, c); } for (i = 0; i < localComps.size(); ++i) { Component* const c = localComps.getUnchecked (i); if (c->getWantsKeyboardFocus()) comps.add (c); if (! c->isFocusContainer()) findAllFocusableComponents (c, comps); } } } } static Component* getIncrementedComponent (Component* const current, const int delta) { Component* focusContainer = current->getParentComponent(); if (focusContainer != 0) { while (focusContainer->getParentComponent() != 0 && ! focusContainer->isFocusContainer()) focusContainer = focusContainer->getParentComponent(); if (focusContainer != 0) { Array comps; KeyboardFocusHelpers::findAllFocusableComponents (focusContainer, comps); if (comps.size() > 0) { const int index = comps.indexOf (current); return comps [(index + comps.size() + delta) % comps.size()]; } } } return 0; } Component* KeyboardFocusTraverser::getNextComponent (Component* current) { return getIncrementedComponent (current, 1); } Component* KeyboardFocusTraverser::getPreviousComponent (Component* current) { return getIncrementedComponent (current, -1); } Component* KeyboardFocusTraverser::getDefaultComponent (Component* parentComponent) { Array comps; if (parentComponent != 0) KeyboardFocusHelpers::findAllFocusableComponents (parentComponent, comps); return comps.getFirst(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_KeyboardFocusTraverser.cpp ***/ /*** Start of inlined file: juce_KeyListener.cpp ***/ BEGIN_JUCE_NAMESPACE bool KeyListener::keyStateChanged (const bool, Component*) { return false; } END_JUCE_NAMESPACE /*** End of inlined file: juce_KeyListener.cpp ***/ /*** Start of inlined file: juce_KeyMappingEditorComponent.cpp ***/ BEGIN_JUCE_NAMESPACE // N.B. these two includes are put here deliberately to avoid problems with // old GCCs failing on long include paths const int maxKeys = 3; class KeyMappingChangeButton : public Button { public: KeyMappingChangeButton (KeyMappingEditorComponent* const owner_, const CommandID commandID_, const String& keyName, const int keyNum_) : Button (keyName), owner (owner_), commandID (commandID_), keyNum (keyNum_) { setWantsKeyboardFocus (false); setTriggeredOnMouseDown (keyNum >= 0); if (keyNum_ < 0) setTooltip (TRANS("adds a new key-mapping")); else setTooltip (TRANS("click to change this key-mapping")); } ~KeyMappingChangeButton() { } void paintButton (Graphics& g, bool /*isOver*/, bool /*isDown*/) { getLookAndFeel().drawKeymapChangeButton (g, getWidth(), getHeight(), *this, keyNum >= 0 ? getName() : String::empty); } void clicked() { if (keyNum >= 0) { // existing key clicked.. PopupMenu m; m.addItem (1, TRANS("change this key-mapping")); m.addSeparator(); m.addItem (2, TRANS("remove this key-mapping")); const int res = m.show(); if (res == 1) { owner->assignNewKey (commandID, keyNum); } else if (res == 2) { owner->getMappings()->removeKeyPress (commandID, keyNum); } } else { // + button pressed.. owner->assignNewKey (commandID, -1); } } void fitToContent (const int h) throw() { if (keyNum < 0) { setSize (h, h); } else { Font f (h * 0.6f); setSize (jlimit (h * 4, h * 8, 6 + f.getStringWidth (getName())), h); } } juce_UseDebuggingNewOperator private: KeyMappingEditorComponent* const owner; const CommandID commandID; const int keyNum; KeyMappingChangeButton (const KeyMappingChangeButton&); KeyMappingChangeButton& operator= (const KeyMappingChangeButton&); }; class KeyMappingItemComponent : public Component { public: KeyMappingItemComponent (KeyMappingEditorComponent* const owner_, const CommandID commandID_) : owner (owner_), commandID (commandID_) { setInterceptsMouseClicks (false, true); const bool isReadOnly = owner_->isCommandReadOnly (commandID); const Array keyPresses (owner_->getMappings()->getKeyPressesAssignedToCommand (commandID)); for (int i = 0; i < jmin (maxKeys, keyPresses.size()); ++i) { KeyMappingChangeButton* const kb = new KeyMappingChangeButton (owner_, commandID, owner_->getDescriptionForKeyPress (keyPresses.getReference (i)), i); kb->setEnabled (! isReadOnly); addAndMakeVisible (kb); } KeyMappingChangeButton* const kb = new KeyMappingChangeButton (owner_, commandID, String::empty, -1); addChildComponent (kb); kb->setVisible (keyPresses.size() < maxKeys && ! isReadOnly); } ~KeyMappingItemComponent() { deleteAllChildren(); } void paint (Graphics& g) { g.setFont (getHeight() * 0.7f); g.setColour (findColour (KeyMappingEditorComponent::textColourId)); g.drawFittedText (owner->getMappings()->getCommandManager()->getNameOfCommand (commandID), 4, 0, jmax (40, getChildComponent (0)->getX() - 5), getHeight(), Justification::centredLeft, true); } void resized() { int x = getWidth() - 4; for (int i = getNumChildComponents(); --i >= 0;) { KeyMappingChangeButton* const kb = dynamic_cast (getChildComponent (i)); kb->fitToContent (getHeight() - 2); kb->setTopRightPosition (x, 1); x -= kb->getWidth() + 5; } } juce_UseDebuggingNewOperator private: KeyMappingEditorComponent* const owner; const CommandID commandID; KeyMappingItemComponent (const KeyMappingItemComponent&); KeyMappingItemComponent& operator= (const KeyMappingItemComponent&); }; class KeyMappingTreeViewItem : public TreeViewItem { public: KeyMappingTreeViewItem (KeyMappingEditorComponent* const owner_, const CommandID commandID_) : owner (owner_), commandID (commandID_) { } ~KeyMappingTreeViewItem() { } const String getUniqueName() const { return String ((int) commandID) + "_id"; } bool mightContainSubItems() { return false; } int getItemHeight() const { return 20; } Component* createItemComponent() { return new KeyMappingItemComponent (owner, commandID); } juce_UseDebuggingNewOperator private: KeyMappingEditorComponent* const owner; const CommandID commandID; KeyMappingTreeViewItem (const KeyMappingTreeViewItem&); KeyMappingTreeViewItem& operator= (const KeyMappingTreeViewItem&); }; class KeyCategoryTreeViewItem : public TreeViewItem { public: KeyCategoryTreeViewItem (KeyMappingEditorComponent* const owner_, const String& name) : owner (owner_), categoryName (name) { } ~KeyCategoryTreeViewItem() { } const String getUniqueName() const { return categoryName + "_cat"; } bool mightContainSubItems() { return true; } int getItemHeight() const { return 28; } void paintItem (Graphics& g, int width, int height) { g.setFont (height * 0.6f, Font::bold); g.setColour (owner->findColour (KeyMappingEditorComponent::textColourId)); g.drawText (categoryName, 2, 0, width - 2, height, Justification::centredLeft, true); } void itemOpennessChanged (bool isNowOpen) { if (isNowOpen) { if (getNumSubItems() == 0) { Array commands (owner->getMappings()->getCommandManager()->getCommandsInCategory (categoryName)); for (int i = 0; i < commands.size(); ++i) { if (owner->shouldCommandBeIncluded (commands[i])) addSubItem (new KeyMappingTreeViewItem (owner, commands[i])); } } } else { clearSubItems(); } } juce_UseDebuggingNewOperator private: KeyMappingEditorComponent* owner; String categoryName; KeyCategoryTreeViewItem (const KeyCategoryTreeViewItem&); KeyCategoryTreeViewItem& operator= (const KeyCategoryTreeViewItem&); }; KeyMappingEditorComponent::KeyMappingEditorComponent (KeyPressMappingSet* const mappingManager, const bool showResetToDefaultButton) : mappings (mappingManager) { jassert (mappingManager != 0); // can't be null! mappingManager->addChangeListener (this); setLinesDrawnForSubItems (false); resetButton = 0; if (showResetToDefaultButton) { addAndMakeVisible (resetButton = new TextButton (TRANS("reset to defaults"))); resetButton->addButtonListener (this); } addAndMakeVisible (tree = new TreeView()); tree->setColour (TreeView::backgroundColourId, findColour (backgroundColourId)); tree->setRootItemVisible (false); tree->setDefaultOpenness (true); tree->setRootItem (this); } KeyMappingEditorComponent::~KeyMappingEditorComponent() { mappings->removeChangeListener (this); deleteAllChildren(); } bool KeyMappingEditorComponent::mightContainSubItems() { return true; } const String KeyMappingEditorComponent::getUniqueName() const { return "keys"; } void KeyMappingEditorComponent::setColours (const Colour& mainBackground, const Colour& textColour) { setColour (backgroundColourId, mainBackground); setColour (textColourId, textColour); tree->setColour (TreeView::backgroundColourId, mainBackground); } void KeyMappingEditorComponent::parentHierarchyChanged() { changeListenerCallback (0); } void KeyMappingEditorComponent::resized() { int h = getHeight(); if (resetButton != 0) { const int buttonHeight = 20; h -= buttonHeight + 8; int x = getWidth() - 8; resetButton->changeWidthToFitText (buttonHeight); resetButton->setTopRightPosition (x, h + 6); } tree->setBounds (0, 0, getWidth(), h); } void KeyMappingEditorComponent::buttonClicked (Button* button) { if (button == resetButton) { if (AlertWindow::showOkCancelBox (AlertWindow::QuestionIcon, TRANS("Reset to defaults"), TRANS("Are you sure you want to reset all the key-mappings to their default state?"), TRANS("Reset"))) { mappings->resetToDefaultMappings(); } } } void KeyMappingEditorComponent::changeListenerCallback (void*) { ScopedPointer oldOpenness (tree->getOpennessState (true)); clearSubItems(); const StringArray categories (mappings->getCommandManager()->getCommandCategories()); for (int i = 0; i < categories.size(); ++i) { const Array commands (mappings->getCommandManager()->getCommandsInCategory (categories[i])); int count = 0; for (int j = 0; j < commands.size(); ++j) if (shouldCommandBeIncluded (commands[j])) ++count; if (count > 0) addSubItem (new KeyCategoryTreeViewItem (this, categories[i])); } if (oldOpenness != 0) tree->restoreOpennessState (*oldOpenness); } class KeyEntryWindow : public AlertWindow { public: KeyEntryWindow (KeyMappingEditorComponent* const owner_) : AlertWindow (TRANS("New key-mapping"), TRANS("Please press a key combination now..."), AlertWindow::NoIcon), owner (owner_) { addButton (TRANS("ok"), 1); addButton (TRANS("cancel"), 0); // (avoid return + escape keys getting processed by the buttons..) for (int i = getNumChildComponents(); --i >= 0;) getChildComponent (i)->setWantsKeyboardFocus (false); setWantsKeyboardFocus (true); grabKeyboardFocus(); } ~KeyEntryWindow() { } bool keyPressed (const KeyPress& key) { lastPress = key; String message (TRANS("Key: ") + owner->getDescriptionForKeyPress (key)); const CommandID previousCommand = owner->getMappings()->findCommandForKeyPress (key); if (previousCommand != 0) { message << "\n\n" << TRANS("(Currently assigned to \"") << owner->getMappings()->getCommandManager()->getNameOfCommand (previousCommand) << "\")"; } setMessage (message); return true; } bool keyStateChanged (bool) { return true; } KeyPress lastPress; juce_UseDebuggingNewOperator private: KeyMappingEditorComponent* owner; KeyEntryWindow (const KeyEntryWindow&); KeyEntryWindow& operator= (const KeyEntryWindow&); }; void KeyMappingEditorComponent::assignNewKey (const CommandID commandID, const int index) { KeyEntryWindow entryWindow (this); if (entryWindow.runModalLoop() != 0) { entryWindow.setVisible (false); if (entryWindow.lastPress.isValid()) { const CommandID previousCommand = mappings->findCommandForKeyPress (entryWindow.lastPress); if (previousCommand != 0) { if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, TRANS("Change key-mapping"), TRANS("This key is already assigned to the command \"") + mappings->getCommandManager()->getNameOfCommand (previousCommand) + TRANS("\"\n\nDo you want to re-assign it to this new command instead?"), TRANS("re-assign"), TRANS("cancel"))) { return; } } mappings->removeKeyPress (entryWindow.lastPress); if (index >= 0) mappings->removeKeyPress (commandID, index); mappings->addKeyPress (commandID, entryWindow.lastPress, index); } } } bool KeyMappingEditorComponent::shouldCommandBeIncluded (const CommandID commandID) { const ApplicationCommandInfo* const ci = mappings->getCommandManager()->getCommandForID (commandID); return (ci != 0) && ((ci->flags & ApplicationCommandInfo::hiddenFromKeyEditor) == 0); } bool KeyMappingEditorComponent::isCommandReadOnly (const CommandID commandID) { const ApplicationCommandInfo* const ci = mappings->getCommandManager()->getCommandForID (commandID); return (ci != 0) && ((ci->flags & ApplicationCommandInfo::readOnlyInKeyEditor) != 0); } const String KeyMappingEditorComponent::getDescriptionForKeyPress (const KeyPress& key) { return key.getTextDescription(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_KeyMappingEditorComponent.cpp ***/ /*** Start of inlined file: juce_KeyPress.cpp ***/ BEGIN_JUCE_NAMESPACE KeyPress::KeyPress() throw() : keyCode (0), mods (0), textCharacter (0) { } KeyPress::KeyPress (const int keyCode_, const ModifierKeys& mods_, const juce_wchar textCharacter_) throw() : keyCode (keyCode_), mods (mods_), textCharacter (textCharacter_) { } KeyPress::KeyPress (const int keyCode_) throw() : keyCode (keyCode_), textCharacter (0) { } KeyPress::KeyPress (const KeyPress& other) throw() : keyCode (other.keyCode), mods (other.mods), textCharacter (other.textCharacter) { } KeyPress& KeyPress::operator= (const KeyPress& other) throw() { keyCode = other.keyCode; mods = other.mods; textCharacter = other.textCharacter; return *this; } bool KeyPress::operator== (const KeyPress& other) const throw() { return mods.getRawFlags() == other.mods.getRawFlags() && (textCharacter == other.textCharacter || textCharacter == 0 || other.textCharacter == 0) && (keyCode == other.keyCode || (keyCode < 256 && other.keyCode < 256 && CharacterFunctions::toLowerCase ((juce_wchar) keyCode) == CharacterFunctions::toLowerCase ((juce_wchar) other.keyCode))); } bool KeyPress::operator!= (const KeyPress& other) const throw() { return ! operator== (other); } bool KeyPress::isCurrentlyDown() const { return isKeyCurrentlyDown (keyCode) && (ModifierKeys::getCurrentModifiers().getRawFlags() & ModifierKeys::allKeyboardModifiers) == (mods.getRawFlags() & ModifierKeys::allKeyboardModifiers); } namespace KeyPressHelpers { struct KeyNameAndCode { const char* name; int code; }; static const KeyNameAndCode translations[] = { { "spacebar", KeyPress::spaceKey }, { "return", KeyPress::returnKey }, { "escape", KeyPress::escapeKey }, { "backspace", KeyPress::backspaceKey }, { "cursor left", KeyPress::leftKey }, { "cursor right", KeyPress::rightKey }, { "cursor up", KeyPress::upKey }, { "cursor down", KeyPress::downKey }, { "page up", KeyPress::pageUpKey }, { "page down", KeyPress::pageDownKey }, { "home", KeyPress::homeKey }, { "end", KeyPress::endKey }, { "delete", KeyPress::deleteKey }, { "insert", KeyPress::insertKey }, { "tab", KeyPress::tabKey }, { "play", KeyPress::playKey }, { "stop", KeyPress::stopKey }, { "fast forward", KeyPress::fastForwardKey }, { "rewind", KeyPress::rewindKey } }; static const String numberPadPrefix() { return "numpad "; } } const KeyPress KeyPress::createFromDescription (const String& desc) { int modifiers = 0; if (desc.containsWholeWordIgnoreCase ("ctrl") || desc.containsWholeWordIgnoreCase ("control") || desc.containsWholeWordIgnoreCase ("ctl")) modifiers |= ModifierKeys::ctrlModifier; if (desc.containsWholeWordIgnoreCase ("shift") || desc.containsWholeWordIgnoreCase ("shft")) modifiers |= ModifierKeys::shiftModifier; if (desc.containsWholeWordIgnoreCase ("alt") || desc.containsWholeWordIgnoreCase ("option")) modifiers |= ModifierKeys::altModifier; if (desc.containsWholeWordIgnoreCase ("command") || desc.containsWholeWordIgnoreCase ("cmd")) modifiers |= ModifierKeys::commandModifier; int key = 0; for (int i = 0; i < numElementsInArray (KeyPressHelpers::translations); ++i) { if (desc.containsWholeWordIgnoreCase (String (KeyPressHelpers::translations[i].name))) { key = KeyPressHelpers::translations[i].code; break; } } if (key == 0) { // see if it's a numpad key.. if (desc.containsIgnoreCase (KeyPressHelpers::numberPadPrefix())) { const juce_wchar lastChar = desc.trimEnd().getLastCharacter(); if (lastChar >= '0' && lastChar <= '9') key = numberPad0 + lastChar - '0'; else if (lastChar == '+') key = numberPadAdd; else if (lastChar == '-') key = numberPadSubtract; else if (lastChar == '*') key = numberPadMultiply; else if (lastChar == '/') key = numberPadDivide; else if (lastChar == '.') key = numberPadDecimalPoint; else if (lastChar == '=') key = numberPadEquals; else if (desc.endsWith ("separator")) key = numberPadSeparator; else if (desc.endsWith ("delete")) key = numberPadDelete; } if (key == 0) { // see if it's a function key.. if (! desc.containsChar ('#')) // avoid mistaking hex-codes like "#f1" for (int i = 1; i <= 12; ++i) if (desc.containsWholeWordIgnoreCase ("f" + String (i))) key = F1Key + i - 1; if (key == 0) { // give up and use the hex code.. const int hexCode = desc.fromFirstOccurrenceOf ("#", false, false) .toLowerCase() .retainCharacters ("0123456789abcdef") .getHexValue32(); if (hexCode > 0) key = hexCode; else key = CharacterFunctions::toUpperCase (desc.getLastCharacter()); } } } return KeyPress (key, ModifierKeys (modifiers), 0); } const String KeyPress::getTextDescription() const { String desc; if (keyCode > 0) { // some keyboard layouts use a shift-key to get the slash, but in those cases, we // want to store it as being a slash, not shift+whatever. if (textCharacter == '/') return "/"; if (mods.isCtrlDown()) desc << "ctrl + "; if (mods.isShiftDown()) desc << "shift + "; #if JUCE_MAC // only do this on the mac, because on Windows ctrl and command are the same, // and this would get confusing if (mods.isCommandDown()) desc << "command + "; if (mods.isAltDown()) desc << "option + "; #else if (mods.isAltDown()) desc << "alt + "; #endif for (int i = 0; i < numElementsInArray (KeyPressHelpers::translations); ++i) if (keyCode == KeyPressHelpers::translations[i].code) return desc + KeyPressHelpers::translations[i].name; if (keyCode >= F1Key && keyCode <= F16Key) desc << 'F' << (1 + keyCode - F1Key); else if (keyCode >= numberPad0 && keyCode <= numberPad9) desc << KeyPressHelpers::numberPadPrefix() << (keyCode - numberPad0); else if (keyCode >= 33 && keyCode < 176) desc += CharacterFunctions::toUpperCase ((juce_wchar) keyCode); else if (keyCode == numberPadAdd) desc << KeyPressHelpers::numberPadPrefix() << '+'; else if (keyCode == numberPadSubtract) desc << KeyPressHelpers::numberPadPrefix() << '-'; else if (keyCode == numberPadMultiply) desc << KeyPressHelpers::numberPadPrefix() << '*'; else if (keyCode == numberPadDivide) desc << KeyPressHelpers::numberPadPrefix() << '/'; else if (keyCode == numberPadSeparator) desc << KeyPressHelpers::numberPadPrefix() << "separator"; else if (keyCode == numberPadDecimalPoint) desc << KeyPressHelpers::numberPadPrefix() << '.'; else if (keyCode == numberPadDelete) desc << KeyPressHelpers::numberPadPrefix() << "delete"; else desc << '#' << String::toHexString (keyCode); } return desc; } END_JUCE_NAMESPACE /*** End of inlined file: juce_KeyPress.cpp ***/ /*** Start of inlined file: juce_KeyPressMappingSet.cpp ***/ BEGIN_JUCE_NAMESPACE KeyPressMappingSet::KeyPressMappingSet (ApplicationCommandManager* const commandManager_) : commandManager (commandManager_) { // A manager is needed to get the descriptions of commands, and will be called when // a command is invoked. So you can't leave this null.. jassert (commandManager_ != 0); Desktop::getInstance().addFocusChangeListener (this); } KeyPressMappingSet::KeyPressMappingSet (const KeyPressMappingSet& other) : commandManager (other.commandManager) { Desktop::getInstance().addFocusChangeListener (this); } KeyPressMappingSet::~KeyPressMappingSet() { Desktop::getInstance().removeFocusChangeListener (this); } const Array KeyPressMappingSet::getKeyPressesAssignedToCommand (const CommandID commandID) const { for (int i = 0; i < mappings.size(); ++i) if (mappings.getUnchecked(i)->commandID == commandID) return mappings.getUnchecked (i)->keypresses; return Array (); } void KeyPressMappingSet::addKeyPress (const CommandID commandID, const KeyPress& newKeyPress, int insertIndex) { // If you specify an upper-case letter but no shift key, how is the user supposed to press it!? // Stick to lower-case letters when defining a keypress, to avoid ambiguity. jassert (! (CharacterFunctions::isUpperCase (newKeyPress.getTextCharacter()) && ! newKeyPress.getModifiers().isShiftDown())); if (findCommandForKeyPress (newKeyPress) != commandID) { removeKeyPress (newKeyPress); if (newKeyPress.isValid()) { for (int i = mappings.size(); --i >= 0;) { if (mappings.getUnchecked(i)->commandID == commandID) { mappings.getUnchecked(i)->keypresses.insert (insertIndex, newKeyPress); sendChangeMessage (this); return; } } const ApplicationCommandInfo* const ci = commandManager->getCommandForID (commandID); if (ci != 0) { CommandMapping* const cm = new CommandMapping(); cm->commandID = commandID; cm->keypresses.add (newKeyPress); cm->wantsKeyUpDownCallbacks = (ci->flags & ApplicationCommandInfo::wantsKeyUpDownCallbacks) != 0; mappings.add (cm); sendChangeMessage (this); } } } } void KeyPressMappingSet::resetToDefaultMappings() { mappings.clear(); for (int i = 0; i < commandManager->getNumCommands(); ++i) { const ApplicationCommandInfo* const ci = commandManager->getCommandForIndex (i); for (int j = 0; j < ci->defaultKeypresses.size(); ++j) { addKeyPress (ci->commandID, ci->defaultKeypresses.getReference (j)); } } sendChangeMessage (this); } void KeyPressMappingSet::resetToDefaultMapping (const CommandID commandID) { clearAllKeyPresses (commandID); const ApplicationCommandInfo* const ci = commandManager->getCommandForID (commandID); for (int j = 0; j < ci->defaultKeypresses.size(); ++j) { addKeyPress (ci->commandID, ci->defaultKeypresses.getReference (j)); } } void KeyPressMappingSet::clearAllKeyPresses() { if (mappings.size() > 0) { sendChangeMessage (this); mappings.clear(); } } void KeyPressMappingSet::clearAllKeyPresses (const CommandID commandID) { for (int i = mappings.size(); --i >= 0;) { if (mappings.getUnchecked(i)->commandID == commandID) { mappings.remove (i); sendChangeMessage (this); } } } void KeyPressMappingSet::removeKeyPress (const KeyPress& keypress) { if (keypress.isValid()) { for (int i = mappings.size(); --i >= 0;) { CommandMapping* const cm = mappings.getUnchecked(i); for (int j = cm->keypresses.size(); --j >= 0;) { if (keypress == cm->keypresses [j]) { cm->keypresses.remove (j); sendChangeMessage (this); } } } } } void KeyPressMappingSet::removeKeyPress (const CommandID commandID, const int keyPressIndex) { for (int i = mappings.size(); --i >= 0;) { if (mappings.getUnchecked(i)->commandID == commandID) { mappings.getUnchecked(i)->keypresses.remove (keyPressIndex); sendChangeMessage (this); break; } } } CommandID KeyPressMappingSet::findCommandForKeyPress (const KeyPress& keyPress) const throw() { for (int i = 0; i < mappings.size(); ++i) if (mappings.getUnchecked(i)->keypresses.contains (keyPress)) return mappings.getUnchecked(i)->commandID; return 0; } bool KeyPressMappingSet::containsMapping (const CommandID commandID, const KeyPress& keyPress) const throw() { for (int i = mappings.size(); --i >= 0;) if (mappings.getUnchecked(i)->commandID == commandID) return mappings.getUnchecked(i)->keypresses.contains (keyPress); return false; } void KeyPressMappingSet::invokeCommand (const CommandID commandID, const KeyPress& key, const bool isKeyDown, const int millisecsSinceKeyPressed, Component* const originatingComponent) const { ApplicationCommandTarget::InvocationInfo info (commandID); info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromKeyPress; info.isKeyDown = isKeyDown; info.keyPress = key; info.millisecsSinceKeyPressed = millisecsSinceKeyPressed; info.originatingComponent = originatingComponent; commandManager->invoke (info, false); } bool KeyPressMappingSet::restoreFromXml (const XmlElement& xmlVersion) { if (xmlVersion.hasTagName ("KEYMAPPINGS")) { if (xmlVersion.getBoolAttribute ("basedOnDefaults", true)) { // if the XML was created as a set of differences from the default mappings, // (i.e. by calling createXml (true)), then we need to first restore the defaults. resetToDefaultMappings(); } else { // if the XML was created calling createXml (false), then we need to clear all // the keys and treat the xml as describing the entire set of mappings. clearAllKeyPresses(); } forEachXmlChildElement (xmlVersion, map) { const CommandID commandId = map->getStringAttribute ("commandId").getHexValue32(); if (commandId != 0) { const KeyPress key (KeyPress::createFromDescription (map->getStringAttribute ("key"))); if (map->hasTagName ("MAPPING")) { addKeyPress (commandId, key); } else if (map->hasTagName ("UNMAPPING")) { if (containsMapping (commandId, key)) removeKeyPress (key); } } } return true; } return false; } XmlElement* KeyPressMappingSet::createXml (const bool saveDifferencesFromDefaultSet) const { ScopedPointer defaultSet; if (saveDifferencesFromDefaultSet) { defaultSet = new KeyPressMappingSet (commandManager); defaultSet->resetToDefaultMappings(); } XmlElement* const doc = new XmlElement ("KEYMAPPINGS"); doc->setAttribute ("basedOnDefaults", saveDifferencesFromDefaultSet); int i; for (i = 0; i < mappings.size(); ++i) { const CommandMapping* const cm = mappings.getUnchecked(i); for (int j = 0; j < cm->keypresses.size(); ++j) { if (defaultSet == 0 || ! defaultSet->containsMapping (cm->commandID, cm->keypresses.getReference (j))) { XmlElement* const map = doc->createNewChildElement ("MAPPING"); map->setAttribute ("commandId", String::toHexString ((int) cm->commandID)); map->setAttribute ("description", commandManager->getDescriptionOfCommand (cm->commandID)); map->setAttribute ("key", cm->keypresses.getReference (j).getTextDescription()); } } } if (defaultSet != 0) { for (i = 0; i < defaultSet->mappings.size(); ++i) { const CommandMapping* const cm = defaultSet->mappings.getUnchecked(i); for (int j = 0; j < cm->keypresses.size(); ++j) { if (! containsMapping (cm->commandID, cm->keypresses.getReference (j))) { XmlElement* const map = doc->createNewChildElement ("UNMAPPING"); map->setAttribute ("commandId", String::toHexString ((int) cm->commandID)); map->setAttribute ("description", commandManager->getDescriptionOfCommand (cm->commandID)); map->setAttribute ("key", cm->keypresses.getReference (j).getTextDescription()); } } } } return doc; } bool KeyPressMappingSet::keyPressed (const KeyPress& key, Component* originatingComponent) { bool used = false; const CommandID commandID = findCommandForKeyPress (key); const ApplicationCommandInfo* const ci = commandManager->getCommandForID (commandID); if (ci != 0 && (ci->flags & ApplicationCommandInfo::wantsKeyUpDownCallbacks) == 0) { ApplicationCommandInfo info (0); if (commandManager->getTargetForCommand (commandID, info) != 0 && (info.flags & ApplicationCommandInfo::isDisabled) == 0) { invokeCommand (commandID, key, true, 0, originatingComponent); used = true; } else { if (originatingComponent != 0) originatingComponent->getLookAndFeel().playAlertSound(); } } return used; } bool KeyPressMappingSet::keyStateChanged (const bool /*isKeyDown*/, Component* originatingComponent) { bool used = false; const uint32 now = Time::getMillisecondCounter(); for (int i = mappings.size(); --i >= 0;) { CommandMapping* const cm = mappings.getUnchecked(i); if (cm->wantsKeyUpDownCallbacks) { for (int j = cm->keypresses.size(); --j >= 0;) { const KeyPress key (cm->keypresses.getReference (j)); const bool isDown = key.isCurrentlyDown(); int keyPressEntryIndex = 0; bool wasDown = false; for (int k = keysDown.size(); --k >= 0;) { if (key == keysDown.getUnchecked(k)->key) { keyPressEntryIndex = k; wasDown = true; used = true; break; } } if (isDown != wasDown) { int millisecs = 0; if (isDown) { KeyPressTime* const k = new KeyPressTime(); k->key = key; k->timeWhenPressed = now; keysDown.add (k); } else { const uint32 pressTime = keysDown.getUnchecked (keyPressEntryIndex)->timeWhenPressed; if (now > pressTime) millisecs = now - pressTime; keysDown.remove (keyPressEntryIndex); } invokeCommand (cm->commandID, key, isDown, millisecs, originatingComponent); used = true; } } } } return used; } void KeyPressMappingSet::globalFocusChanged (Component* focusedComponent) { if (focusedComponent != 0) focusedComponent->keyStateChanged (false); } END_JUCE_NAMESPACE /*** End of inlined file: juce_KeyPressMappingSet.cpp ***/ /*** Start of inlined file: juce_ModifierKeys.cpp ***/ BEGIN_JUCE_NAMESPACE ModifierKeys::ModifierKeys (const int flags_) throw() : flags (flags_) { } ModifierKeys::ModifierKeys (const ModifierKeys& other) throw() : flags (other.flags) { } ModifierKeys& ModifierKeys::operator= (const ModifierKeys& other) throw() { flags = other.flags; return *this; } ModifierKeys ModifierKeys::currentModifiers; const ModifierKeys ModifierKeys::getCurrentModifiers() throw() { return currentModifiers; } int ModifierKeys::getNumMouseButtonsDown() const throw() { int num = 0; if (isLeftButtonDown()) ++num; if (isRightButtonDown()) ++num; if (isMiddleButtonDown()) ++num; return num; } END_JUCE_NAMESPACE /*** End of inlined file: juce_ModifierKeys.cpp ***/ /*** Start of inlined file: juce_ComponentAnimator.cpp ***/ BEGIN_JUCE_NAMESPACE class ComponentAnimator::AnimationTask { public: AnimationTask (Component* const comp) : component (comp) { } Component::SafePointer component; Rectangle destination; int msElapsed, msTotal; double startSpeed, midSpeed, endSpeed, lastProgress; double left, top, right, bottom; bool useTimeslice (const int elapsed) { if (component == 0) return false; msElapsed += elapsed; double newProgress = msElapsed / (double) msTotal; if (newProgress >= 0 && newProgress < 1.0) { newProgress = timeToDistance (newProgress); const double delta = (newProgress - lastProgress) / (1.0 - lastProgress); jassert (newProgress >= lastProgress); lastProgress = newProgress; left += (destination.getX() - left) * delta; top += (destination.getY() - top) * delta; right += (destination.getRight() - right) * delta; bottom += (destination.getBottom() - bottom) * delta; if (delta < 1.0) { const Rectangle newBounds (roundToInt (left), roundToInt (top), roundToInt (right - left), roundToInt (bottom - top)); if (newBounds != destination) { component->setBounds (newBounds); return true; } } } component->setBounds (destination); return false; } void moveToFinalDestination() { if (component != 0) component->setBounds (destination); } private: inline double timeToDistance (const double time) const { return (time < 0.5) ? time * (startSpeed + time * (midSpeed - startSpeed)) : 0.5 * (startSpeed + 0.5 * (midSpeed - startSpeed)) + (time - 0.5) * (midSpeed + (time - 0.5) * (endSpeed - midSpeed)); } }; ComponentAnimator::ComponentAnimator() : lastTime (0) { } ComponentAnimator::~ComponentAnimator() { cancelAllAnimations (false); jassert (tasks.size() == 0); } ComponentAnimator::AnimationTask* ComponentAnimator::findTaskFor (Component* const component) const { for (int i = tasks.size(); --i >= 0;) if (component == tasks.getUnchecked(i)->component.getComponent()) return tasks.getUnchecked(i); return 0; } void ComponentAnimator::animateComponent (Component* const component, const Rectangle& finalPosition, const int millisecondsToSpendMoving, const double startSpeed, const double endSpeed) { if (component != 0) { AnimationTask* at = findTaskFor (component); if (at == 0) { at = new AnimationTask (component); tasks.add (at); sendChangeMessage (this); } at->msElapsed = 0; at->lastProgress = 0; at->msTotal = jmax (1, millisecondsToSpendMoving); at->destination = finalPosition; // the speeds must be 0 or greater! jassert (startSpeed >= 0 && endSpeed >= 0) const double invTotalDistance = 4.0 / (startSpeed + endSpeed + 2.0); at->startSpeed = jmax (0.0, startSpeed * invTotalDistance); at->midSpeed = invTotalDistance; at->endSpeed = jmax (0.0, endSpeed * invTotalDistance); at->left = component->getX(); at->top = component->getY(); at->right = component->getRight(); at->bottom = component->getBottom(); if (! isTimerRunning()) { lastTime = Time::getMillisecondCounter(); startTimer (1000 / 50); } } } void ComponentAnimator::cancelAllAnimations (const bool moveComponentsToTheirFinalPositions) { for (int i = tasks.size(); --i >= 0;) { AnimationTask* const at = tasks.getUnchecked(i); if (moveComponentsToTheirFinalPositions) at->moveToFinalDestination(); delete at; tasks.remove (i); sendChangeMessage (this); } } void ComponentAnimator::cancelAnimation (Component* const component, const bool moveComponentToItsFinalPosition) { AnimationTask* const at = findTaskFor (component); if (at != 0) { if (moveComponentToItsFinalPosition) at->moveToFinalDestination(); tasks.removeValue (at); delete at; sendChangeMessage (this); } } const Rectangle ComponentAnimator::getComponentDestination (Component* const component) { AnimationTask* const at = findTaskFor (component); if (at != 0) return at->destination; else if (component != 0) return component->getBounds(); return Rectangle(); } bool ComponentAnimator::isAnimating (Component* component) const { return findTaskFor (component) != 0; } void ComponentAnimator::timerCallback() { const uint32 timeNow = Time::getMillisecondCounter(); if (lastTime == 0 || lastTime == timeNow) lastTime = timeNow; const int elapsed = timeNow - lastTime; for (int i = tasks.size(); --i >= 0;) { AnimationTask* const at = tasks.getUnchecked(i); if (! at->useTimeslice (elapsed)) { tasks.remove (i); delete at; sendChangeMessage (this); } } lastTime = timeNow; if (tasks.size() == 0) stopTimer(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ComponentAnimator.cpp ***/ /*** Start of inlined file: juce_ComponentBoundsConstrainer.cpp ***/ BEGIN_JUCE_NAMESPACE ComponentBoundsConstrainer::ComponentBoundsConstrainer() throw() : minW (0), maxW (0x3fffffff), minH (0), maxH (0x3fffffff), minOffTop (0), minOffLeft (0), minOffBottom (0), minOffRight (0), aspectRatio (0.0) { } ComponentBoundsConstrainer::~ComponentBoundsConstrainer() { } void ComponentBoundsConstrainer::setMinimumWidth (const int minimumWidth) throw() { minW = minimumWidth; } void ComponentBoundsConstrainer::setMaximumWidth (const int maximumWidth) throw() { maxW = maximumWidth; } void ComponentBoundsConstrainer::setMinimumHeight (const int minimumHeight) throw() { minH = minimumHeight; } void ComponentBoundsConstrainer::setMaximumHeight (const int maximumHeight) throw() { maxH = maximumHeight; } void ComponentBoundsConstrainer::setMinimumSize (const int minimumWidth, const int minimumHeight) throw() { jassert (maxW >= minimumWidth); jassert (maxH >= minimumHeight); jassert (minimumWidth > 0 && minimumHeight > 0); minW = minimumWidth; minH = minimumHeight; if (minW > maxW) maxW = minW; if (minH > maxH) maxH = minH; } void ComponentBoundsConstrainer::setMaximumSize (const int maximumWidth, const int maximumHeight) throw() { jassert (maximumWidth >= minW); jassert (maximumHeight >= minH); jassert (maximumWidth > 0 && maximumHeight > 0); maxW = jmax (minW, maximumWidth); maxH = jmax (minH, maximumHeight); } void ComponentBoundsConstrainer::setSizeLimits (const int minimumWidth, const int minimumHeight, const int maximumWidth, const int maximumHeight) throw() { jassert (maximumWidth >= minimumWidth); jassert (maximumHeight >= minimumHeight); jassert (maximumWidth > 0 && maximumHeight > 0); jassert (minimumWidth > 0 && minimumHeight > 0); minW = jmax (0, minimumWidth); minH = jmax (0, minimumHeight); maxW = jmax (minW, maximumWidth); maxH = jmax (minH, maximumHeight); } void ComponentBoundsConstrainer::setMinimumOnscreenAmounts (const int minimumWhenOffTheTop, const int minimumWhenOffTheLeft, const int minimumWhenOffTheBottom, const int minimumWhenOffTheRight) throw() { minOffTop = minimumWhenOffTheTop; minOffLeft = minimumWhenOffTheLeft; minOffBottom = minimumWhenOffTheBottom; minOffRight = minimumWhenOffTheRight; } void ComponentBoundsConstrainer::setFixedAspectRatio (const double widthOverHeight) throw() { aspectRatio = jmax (0.0, widthOverHeight); } double ComponentBoundsConstrainer::getFixedAspectRatio() const throw() { return aspectRatio; } void ComponentBoundsConstrainer::setBoundsForComponent (Component* const component, const Rectangle& targetBounds, const bool isStretchingTop, const bool isStretchingLeft, const bool isStretchingBottom, const bool isStretchingRight) { jassert (component != 0); Rectangle limits, bounds (targetBounds); BorderSize border; Component* const parent = component->getParentComponent(); if (parent == 0) { ComponentPeer* peer = component->getPeer(); if (peer != 0) border = peer->getFrameSize(); limits = Desktop::getInstance().getMonitorAreaContaining (bounds.getCentre()); } else { limits.setSize (parent->getWidth(), parent->getHeight()); } border.addTo (bounds); checkBounds (bounds, border.addedTo (component->getBounds()), limits, isStretchingTop, isStretchingLeft, isStretchingBottom, isStretchingRight); border.subtractFrom (bounds); applyBoundsToComponent (component, bounds); } void ComponentBoundsConstrainer::checkComponentBounds (Component* component) { setBoundsForComponent (component, component->getBounds(), false, false, false, false); } void ComponentBoundsConstrainer::applyBoundsToComponent (Component* component, const Rectangle& bounds) { component->setBounds (bounds); } void ComponentBoundsConstrainer::resizeStart() { } void ComponentBoundsConstrainer::resizeEnd() { } void ComponentBoundsConstrainer::checkBounds (Rectangle& bounds, const Rectangle& old, const Rectangle& limits, const bool isStretchingTop, const bool isStretchingLeft, const bool isStretchingBottom, const bool isStretchingRight) { int x = bounds.getX(); int y = bounds.getY(); int w = bounds.getWidth(); int h = bounds.getHeight(); // constrain the size if it's being stretched.. if (isStretchingLeft) { x = jlimit (old.getRight() - maxW, old.getRight() - minW, x); w = old.getRight() - x; } if (isStretchingRight) { w = jlimit (minW, maxW, w); } if (isStretchingTop) { y = jlimit (old.getBottom() - maxH, old.getBottom() - minH, y); h = old.getBottom() - y; } if (isStretchingBottom) { h = jlimit (minH, maxH, h); } // constrain the aspect ratio if one has been specified.. if (aspectRatio > 0.0 && w > 0 && h > 0) { bool adjustWidth; if ((isStretchingTop || isStretchingBottom) && ! (isStretchingLeft || isStretchingRight)) { adjustWidth = true; } else if ((isStretchingLeft || isStretchingRight) && ! (isStretchingTop || isStretchingBottom)) { adjustWidth = false; } else { const double oldRatio = (old.getHeight() > 0) ? std::abs (old.getWidth() / (double) old.getHeight()) : 0.0; const double newRatio = std::abs (w / (double) h); adjustWidth = (oldRatio > newRatio); } if (adjustWidth) { w = roundToInt (h * aspectRatio); if (w > maxW || w < minW) { w = jlimit (minW, maxW, w); h = roundToInt (w / aspectRatio); } } else { h = roundToInt (w / aspectRatio); if (h > maxH || h < minH) { h = jlimit (minH, maxH, h); w = roundToInt (h * aspectRatio); } } if ((isStretchingTop || isStretchingBottom) && ! (isStretchingLeft || isStretchingRight)) { x = old.getX() + (old.getWidth() - w) / 2; } else if ((isStretchingLeft || isStretchingRight) && ! (isStretchingTop || isStretchingBottom)) { y = old.getY() + (old.getHeight() - h) / 2; } else { if (isStretchingLeft) x = old.getRight() - w; if (isStretchingTop) y = old.getBottom() - h; } } // ...and constrain the position if limits have been set for that. if (minOffTop > 0 || minOffLeft > 0 || minOffBottom > 0 || minOffRight > 0) { if (minOffTop > 0) { const int limit = limits.getY() + jmin (minOffTop - h, 0); if (y < limit) { if (isStretchingTop) h -= (limit - y); y = limit; } } if (minOffLeft > 0) { const int limit = limits.getX() + jmin (minOffLeft - w, 0); if (x < limit) { if (isStretchingLeft) w -= (limit - x); x = limit; } } if (minOffBottom > 0) { const int limit = limits.getBottom() - jmin (minOffBottom, h); if (y > limit) { if (isStretchingBottom) h += (limit - y); else y = limit; } } if (minOffRight > 0) { const int limit = limits.getRight() - jmin (minOffRight, w); if (x > limit) { if (isStretchingRight) w += (limit - x); else x = limit; } } } jassert (w >= 0 && h >= 0); bounds = Rectangle (x, y, w, h); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ComponentBoundsConstrainer.cpp ***/ /*** Start of inlined file: juce_ComponentMovementWatcher.cpp ***/ BEGIN_JUCE_NAMESPACE ComponentMovementWatcher::ComponentMovementWatcher (Component* const component_) : component (component_), lastPeer (0), reentrant (false) { jassert (component != 0); // can't use this with a null pointer.. component->addComponentListener (this); registerWithParentComps(); } ComponentMovementWatcher::~ComponentMovementWatcher() { component->removeComponentListener (this); unregister(); } void ComponentMovementWatcher::componentParentHierarchyChanged (Component&) { // agh! don't delete the target component without deleting this object first! jassert (component != 0); if (! reentrant) { reentrant = true; ComponentPeer* const peer = component->getPeer(); if (peer != lastPeer) { componentPeerChanged(); if (component == 0) return; lastPeer = peer; } unregister(); registerWithParentComps(); reentrant = false; componentMovedOrResized (*component, true, true); } } void ComponentMovementWatcher::componentMovedOrResized (Component&, bool wasMoved, bool wasResized) { // agh! don't delete the target component without deleting this object first! jassert (component != 0); if (wasMoved) { const Point pos (component->relativePositionToOtherComponent (component->getTopLevelComponent(), Point())); wasMoved = lastBounds.getPosition() != pos; lastBounds.setPosition (pos); } wasResized = (lastBounds.getWidth() != component->getWidth() || lastBounds.getHeight() != component->getHeight()); lastBounds.setSize (component->getWidth(), component->getHeight()); if (wasMoved || wasResized) componentMovedOrResized (wasMoved, wasResized); } void ComponentMovementWatcher::registerWithParentComps() { Component* p = component->getParentComponent(); while (p != 0) { p->addComponentListener (this); registeredParentComps.add (p); p = p->getParentComponent(); } } void ComponentMovementWatcher::unregister() { for (int i = registeredParentComps.size(); --i >= 0;) registeredParentComps.getUnchecked(i)->removeComponentListener (this); registeredParentComps.clear(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ComponentMovementWatcher.cpp ***/ /*** Start of inlined file: juce_GroupComponent.cpp ***/ BEGIN_JUCE_NAMESPACE GroupComponent::GroupComponent (const String& componentName, const String& labelText) : Component (componentName), text (labelText), justification (Justification::left) { setInterceptsMouseClicks (false, true); } GroupComponent::~GroupComponent() { } void GroupComponent::setText (const String& newText) { if (text != newText) { text = newText; repaint(); } } const String GroupComponent::getText() const { return text; } void GroupComponent::setTextLabelPosition (const Justification& newJustification) { if (justification != newJustification) { justification = newJustification; repaint(); } } void GroupComponent::paint (Graphics& g) { getLookAndFeel() .drawGroupComponentOutline (g, getWidth(), getHeight(), text, justification, *this); } void GroupComponent::enablementChanged() { repaint(); } void GroupComponent::colourChanged() { repaint(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_GroupComponent.cpp ***/ /*** Start of inlined file: juce_MultiDocumentPanel.cpp ***/ BEGIN_JUCE_NAMESPACE MultiDocumentPanelWindow::MultiDocumentPanelWindow (const Colour& backgroundColour) : DocumentWindow (String::empty, backgroundColour, DocumentWindow::maximiseButton | DocumentWindow::closeButton, false) { } MultiDocumentPanelWindow::~MultiDocumentPanelWindow() { } void MultiDocumentPanelWindow::maximiseButtonPressed() { MultiDocumentPanel* const owner = getOwner(); jassert (owner != 0); // these windows are only designed to be used inside a MultiDocumentPanel! if (owner != 0) owner->setLayoutMode (MultiDocumentPanel::MaximisedWindowsWithTabs); } void MultiDocumentPanelWindow::closeButtonPressed() { MultiDocumentPanel* const owner = getOwner(); jassert (owner != 0); // these windows are only designed to be used inside a MultiDocumentPanel! if (owner != 0) owner->closeDocument (getContentComponent(), true); } void MultiDocumentPanelWindow::activeWindowStatusChanged() { DocumentWindow::activeWindowStatusChanged(); updateOrder(); } void MultiDocumentPanelWindow::broughtToFront() { DocumentWindow::broughtToFront(); updateOrder(); } void MultiDocumentPanelWindow::updateOrder() { MultiDocumentPanel* const owner = getOwner(); if (owner != 0) owner->updateOrder(); } MultiDocumentPanel* MultiDocumentPanelWindow::getOwner() const throw() { // (unable to use the syntax findParentComponentOfClass () because of a VC6 compiler bug) return findParentComponentOfClass ((MultiDocumentPanel*) 0); } class MDITabbedComponentInternal : public TabbedComponent { public: MDITabbedComponentInternal() : TabbedComponent (TabbedButtonBar::TabsAtTop) { } ~MDITabbedComponentInternal() { } void currentTabChanged (int, const String&) { // (unable to use the syntax findParentComponentOfClass () because of a VC6 compiler bug) MultiDocumentPanel* const owner = findParentComponentOfClass ((MultiDocumentPanel*) 0); if (owner != 0) owner->updateOrder(); } }; MultiDocumentPanel::MultiDocumentPanel() : mode (MaximisedWindowsWithTabs), backgroundColour (Colours::lightblue), maximumNumDocuments (0), numDocsBeforeTabsUsed (0) { setOpaque (true); } MultiDocumentPanel::~MultiDocumentPanel() { closeAllDocuments (false); } static bool shouldDeleteComp (Component* const c) { return c->getProperties() ["mdiDocumentDelete_"]; } bool MultiDocumentPanel::closeAllDocuments (const bool checkItsOkToCloseFirst) { while (components.size() > 0) if (! closeDocument (components.getLast(), checkItsOkToCloseFirst)) return false; return true; } MultiDocumentPanelWindow* MultiDocumentPanel::createNewDocumentWindow() { return new MultiDocumentPanelWindow (backgroundColour); } void MultiDocumentPanel::addWindow (Component* component) { MultiDocumentPanelWindow* const dw = createNewDocumentWindow(); dw->setResizable (true, false); dw->setContentComponent (component, false, true); dw->setName (component->getName()); const var bkg (component->getProperties() ["mdiDocumentBkg_"]); dw->setBackgroundColour (bkg.isVoid() ? backgroundColour : Colour ((int) bkg)); int x = 4; Component* const topComp = getChildComponent (getNumChildComponents() - 1); if (topComp != 0 && topComp->getX() == x && topComp->getY() == x) x += 16; dw->setTopLeftPosition (x, x); const var pos (component->getProperties() ["mdiDocumentPos_"]); if (pos.toString().isNotEmpty()) dw->restoreWindowStateFromString (pos.toString()); addAndMakeVisible (dw); dw->toFront (true); } bool MultiDocumentPanel::addDocument (Component* const component, const Colour& docColour, const bool deleteWhenRemoved) { // If you try passing a full DocumentWindow or ResizableWindow in here, you'll end up // with a frame-within-a-frame! Just pass in the bare content component. jassert (dynamic_cast (component) == 0); if (component == 0 || (maximumNumDocuments > 0 && components.size() >= maximumNumDocuments)) return false; components.add (component); component->getProperties().set ("mdiDocumentDelete_", deleteWhenRemoved); component->getProperties().set ("mdiDocumentBkg_", (int) docColour.getARGB()); component->addComponentListener (this); if (mode == FloatingWindows) { if (isFullscreenWhenOneDocument()) { if (components.size() == 1) { addAndMakeVisible (component); } else { if (components.size() == 2) addWindow (components.getFirst()); addWindow (component); } } else { addWindow (component); } } else { if (tabComponent == 0 && components.size() > numDocsBeforeTabsUsed) { addAndMakeVisible (tabComponent = new MDITabbedComponentInternal()); Array temp (components); for (int i = 0; i < temp.size(); ++i) tabComponent->addTab (temp[i]->getName(), docColour, temp[i], false); resized(); } else { if (tabComponent != 0) tabComponent->addTab (component->getName(), docColour, component, false); else addAndMakeVisible (component); } setActiveDocument (component); } resized(); activeDocumentChanged(); return true; } bool MultiDocumentPanel::closeDocument (Component* component, const bool checkItsOkToCloseFirst) { if (components.contains (component)) { if (checkItsOkToCloseFirst && ! tryToCloseDocument (component)) return false; component->removeComponentListener (this); const bool shouldDelete = shouldDeleteComp (component); component->getProperties().remove ("mdiDocumentDelete_"); component->getProperties().remove ("mdiDocumentBkg_"); if (mode == FloatingWindows) { for (int i = getNumChildComponents(); --i >= 0;) { MultiDocumentPanelWindow* const dw = dynamic_cast (getChildComponent (i)); if (dw != 0 && dw->getContentComponent() == component) { dw->setContentComponent (0, false); delete dw; break; } } if (shouldDelete) delete component; components.removeValue (component); if (isFullscreenWhenOneDocument() && components.size() == 1) { for (int i = getNumChildComponents(); --i >= 0;) { MultiDocumentPanelWindow* const dw = dynamic_cast (getChildComponent (i)); if (dw != 0) { dw->setContentComponent (0, false); delete dw; } } addAndMakeVisible (components.getFirst()); } } else { jassert (components.indexOf (component) >= 0); if (tabComponent != 0) { for (int i = tabComponent->getNumTabs(); --i >= 0;) if (tabComponent->getTabContentComponent (i) == component) tabComponent->removeTab (i); } else { removeChildComponent (component); } if (shouldDelete) delete component; if (tabComponent != 0 && tabComponent->getNumTabs() <= numDocsBeforeTabsUsed) tabComponent = 0; components.removeValue (component); if (components.size() > 0 && tabComponent == 0) addAndMakeVisible (components.getFirst()); } resized(); activeDocumentChanged(); } else { jassertfalse; } return true; } int MultiDocumentPanel::getNumDocuments() const throw() { return components.size(); } Component* MultiDocumentPanel::getDocument (const int index) const throw() { return components [index]; } Component* MultiDocumentPanel::getActiveDocument() const throw() { if (mode == FloatingWindows) { for (int i = getNumChildComponents(); --i >= 0;) { MultiDocumentPanelWindow* const dw = dynamic_cast (getChildComponent (i)); if (dw != 0 && dw->isActiveWindow()) return dw->getContentComponent(); } } return components.getLast(); } void MultiDocumentPanel::setActiveDocument (Component* component) { if (mode == FloatingWindows) { component = getContainerComp (component); if (component != 0) component->toFront (true); } else if (tabComponent != 0) { jassert (components.indexOf (component) >= 0); for (int i = tabComponent->getNumTabs(); --i >= 0;) { if (tabComponent->getTabContentComponent (i) == component) { tabComponent->setCurrentTabIndex (i); break; } } } else { component->grabKeyboardFocus(); } } void MultiDocumentPanel::activeDocumentChanged() { } void MultiDocumentPanel::setMaximumNumDocuments (const int newNumber) { maximumNumDocuments = newNumber; } void MultiDocumentPanel::useFullscreenWhenOneDocument (const bool shouldUseTabs) { numDocsBeforeTabsUsed = shouldUseTabs ? 1 : 0; } bool MultiDocumentPanel::isFullscreenWhenOneDocument() const throw() { return numDocsBeforeTabsUsed != 0; } void MultiDocumentPanel::setLayoutMode (const LayoutMode newLayoutMode) { if (mode != newLayoutMode) { mode = newLayoutMode; if (mode == FloatingWindows) { tabComponent = 0; } else { for (int i = getNumChildComponents(); --i >= 0;) { MultiDocumentPanelWindow* const dw = dynamic_cast (getChildComponent (i)); if (dw != 0) { dw->getContentComponent()->getProperties().set ("mdiDocumentPos_", dw->getWindowStateAsString()); dw->setContentComponent (0, false); delete dw; } } } resized(); const Array tempComps (components); components.clear(); for (int i = 0; i < tempComps.size(); ++i) { Component* const c = tempComps.getUnchecked(i); addDocument (c, Colour ((int) c->getProperties().getWithDefault ("mdiDocumentBkg_", (int) Colours::white.getARGB())), shouldDeleteComp (c)); } } } void MultiDocumentPanel::setBackgroundColour (const Colour& newBackgroundColour) { if (backgroundColour != newBackgroundColour) { backgroundColour = newBackgroundColour; setOpaque (newBackgroundColour.isOpaque()); repaint(); } } void MultiDocumentPanel::paint (Graphics& g) { g.fillAll (backgroundColour); } void MultiDocumentPanel::resized() { if (mode == MaximisedWindowsWithTabs || components.size() == numDocsBeforeTabsUsed) { for (int i = getNumChildComponents(); --i >= 0;) getChildComponent (i)->setBounds (getLocalBounds()); } setWantsKeyboardFocus (components.size() == 0); } Component* MultiDocumentPanel::getContainerComp (Component* c) const { if (mode == FloatingWindows) { for (int i = 0; i < getNumChildComponents(); ++i) { MultiDocumentPanelWindow* const dw = dynamic_cast (getChildComponent (i)); if (dw != 0 && dw->getContentComponent() == c) { c = dw; break; } } } return c; } void MultiDocumentPanel::componentNameChanged (Component&) { if (mode == FloatingWindows) { for (int i = 0; i < getNumChildComponents(); ++i) { MultiDocumentPanelWindow* const dw = dynamic_cast (getChildComponent (i)); if (dw != 0) dw->setName (dw->getContentComponent()->getName()); } } else if (tabComponent != 0) { for (int i = tabComponent->getNumTabs(); --i >= 0;) tabComponent->setTabName (i, tabComponent->getTabContentComponent (i)->getName()); } } void MultiDocumentPanel::updateOrder() { const Array oldList (components); if (mode == FloatingWindows) { components.clear(); for (int i = 0; i < getNumChildComponents(); ++i) { MultiDocumentPanelWindow* const dw = dynamic_cast (getChildComponent (i)); if (dw != 0) components.add (dw->getContentComponent()); } } else { if (tabComponent != 0) { Component* const current = tabComponent->getCurrentContentComponent(); if (current != 0) { components.removeValue (current); components.add (current); } } } if (components != oldList) activeDocumentChanged(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_MultiDocumentPanel.cpp ***/ /*** Start of inlined file: juce_ResizableBorderComponent.cpp ***/ BEGIN_JUCE_NAMESPACE ResizableBorderComponent::Zone::Zone (int zoneFlags) throw() : zone (zoneFlags) { } ResizableBorderComponent::Zone::Zone (const ResizableBorderComponent::Zone& other) throw() : zone (other.zone) {} ResizableBorderComponent::Zone& ResizableBorderComponent::Zone::operator= (const ResizableBorderComponent::Zone& other) throw() { zone = other.zone; return *this; } bool ResizableBorderComponent::Zone::operator== (const ResizableBorderComponent::Zone& other) const throw() { return zone == other.zone; } bool ResizableBorderComponent::Zone::operator!= (const ResizableBorderComponent::Zone& other) const throw() { return zone != other.zone; } const ResizableBorderComponent::Zone ResizableBorderComponent::Zone::fromPositionOnBorder (const Rectangle& totalSize, const BorderSize& border, const Point& position) { int z = 0; if (totalSize.contains (position) && ! border.subtractedFrom (totalSize).contains (position)) { const int minW = jmax (totalSize.getWidth() / 10, jmin (10, totalSize.getWidth() / 3)); if (position.getX() < jmax (border.getLeft(), minW)) z |= left; else if (position.getX() >= totalSize.getWidth() - jmax (border.getRight(), minW)) z |= right; const int minH = jmax (totalSize.getHeight() / 10, jmin (10, totalSize.getHeight() / 3)); if (position.getY() < jmax (border.getTop(), minH)) z |= top; else if (position.getY() >= totalSize.getHeight() - jmax (border.getBottom(), minH)) z |= bottom; } return Zone (z); } const MouseCursor ResizableBorderComponent::Zone::getMouseCursor() const throw() { MouseCursor::StandardCursorType mc = MouseCursor::NormalCursor; switch (zone) { case (left | top): mc = MouseCursor::TopLeftCornerResizeCursor; break; case top: mc = MouseCursor::TopEdgeResizeCursor; break; case (right | top): mc = MouseCursor::TopRightCornerResizeCursor; break; case left: mc = MouseCursor::LeftEdgeResizeCursor; break; case right: mc = MouseCursor::RightEdgeResizeCursor; break; case (left | bottom): mc = MouseCursor::BottomLeftCornerResizeCursor; break; case bottom: mc = MouseCursor::BottomEdgeResizeCursor; break; case (right | bottom): mc = MouseCursor::BottomRightCornerResizeCursor; break; default: break; } return mc; } const Rectangle ResizableBorderComponent::Zone::resizeRectangleBy (Rectangle b, const Point& offset) const throw() { if (isDraggingWholeObject()) return b + offset; if (isDraggingLeftEdge()) b.setLeft (b.getX() + offset.getX()); if (isDraggingRightEdge()) b.setWidth (jmax (0, b.getWidth() + offset.getX())); if (isDraggingTopEdge()) b.setTop (b.getY() + offset.getY()); if (isDraggingBottomEdge()) b.setHeight (jmax (0, b.getHeight() + offset.getY())); return b; } const Rectangle ResizableBorderComponent::Zone::resizeRectangleBy (Rectangle b, const Point& offset) const throw() { if (isDraggingWholeObject()) return b + offset; if (isDraggingLeftEdge()) b.setLeft (b.getX() + offset.getX()); if (isDraggingRightEdge()) b.setWidth (jmax (0.0f, b.getWidth() + offset.getX())); if (isDraggingTopEdge()) b.setTop (b.getY() + offset.getY()); if (isDraggingBottomEdge()) b.setHeight (jmax (0.0f, b.getHeight() + offset.getY())); return b; } ResizableBorderComponent::ResizableBorderComponent (Component* const componentToResize, ComponentBoundsConstrainer* const constrainer_) : component (componentToResize), constrainer (constrainer_), borderSize (5), mouseZone (0) { } ResizableBorderComponent::~ResizableBorderComponent() { } void ResizableBorderComponent::paint (Graphics& g) { getLookAndFeel().drawResizableFrame (g, getWidth(), getHeight(), borderSize); } void ResizableBorderComponent::mouseEnter (const MouseEvent& e) { updateMouseZone (e); } void ResizableBorderComponent::mouseMove (const MouseEvent& e) { updateMouseZone (e); } void ResizableBorderComponent::mouseDown (const MouseEvent& e) { if (component == 0) { jassertfalse; // You've deleted the component that this resizer was supposed to be using! return; } updateMouseZone (e); originalBounds = component->getBounds(); if (constrainer != 0) constrainer->resizeStart(); } void ResizableBorderComponent::mouseDrag (const MouseEvent& e) { if (component == 0) { jassertfalse; // You've deleted the component that this resizer was supposed to be using! return; } const Rectangle bounds (mouseZone.resizeRectangleBy (originalBounds, e.getOffsetFromDragStart())); if (constrainer != 0) constrainer->setBoundsForComponent (component, bounds, mouseZone.isDraggingTopEdge(), mouseZone.isDraggingLeftEdge(), mouseZone.isDraggingBottomEdge(), mouseZone.isDraggingRightEdge()); else component->setBounds (bounds); } void ResizableBorderComponent::mouseUp (const MouseEvent&) { if (constrainer != 0) constrainer->resizeEnd(); } bool ResizableBorderComponent::hitTest (int x, int y) { return x < borderSize.getLeft() || x >= getWidth() - borderSize.getRight() || y < borderSize.getTop() || y >= getHeight() - borderSize.getBottom(); } void ResizableBorderComponent::setBorderThickness (const BorderSize& newBorderSize) { if (borderSize != newBorderSize) { borderSize = newBorderSize; repaint(); } } const BorderSize ResizableBorderComponent::getBorderThickness() const { return borderSize; } void ResizableBorderComponent::updateMouseZone (const MouseEvent& e) { Zone newZone (Zone::fromPositionOnBorder (getLocalBounds(), borderSize, e.getPosition())); if (mouseZone != newZone) { mouseZone = newZone; setMouseCursor (newZone.getMouseCursor()); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ResizableBorderComponent.cpp ***/ /*** Start of inlined file: juce_ResizableCornerComponent.cpp ***/ BEGIN_JUCE_NAMESPACE ResizableCornerComponent::ResizableCornerComponent (Component* const componentToResize, ComponentBoundsConstrainer* const constrainer_) : component (componentToResize), constrainer (constrainer_) { setRepaintsOnMouseActivity (true); setMouseCursor (MouseCursor::BottomRightCornerResizeCursor); } ResizableCornerComponent::~ResizableCornerComponent() { } void ResizableCornerComponent::paint (Graphics& g) { getLookAndFeel() .drawCornerResizer (g, getWidth(), getHeight(), isMouseOverOrDragging(), isMouseButtonDown()); } void ResizableCornerComponent::mouseDown (const MouseEvent&) { if (component == 0) { jassertfalse; // You've deleted the component that this resizer is supposed to be controlling! return; } originalBounds = component->getBounds(); if (constrainer != 0) constrainer->resizeStart(); } void ResizableCornerComponent::mouseDrag (const MouseEvent& e) { if (component == 0) { jassertfalse; // You've deleted the component that this resizer is supposed to be controlling! return; } Rectangle r (originalBounds.withSize (originalBounds.getWidth() + e.getDistanceFromDragStartX(), originalBounds.getHeight() + e.getDistanceFromDragStartY())); if (constrainer != 0) constrainer->setBoundsForComponent (component, r, false, false, true, true); else component->setBounds (r); } void ResizableCornerComponent::mouseUp (const MouseEvent&) { if (constrainer != 0) constrainer->resizeStart(); } bool ResizableCornerComponent::hitTest (int x, int y) { if (getWidth() <= 0) return false; const int yAtX = getHeight() - (getHeight() * x / getWidth()); return y >= yAtX - getHeight() / 4; } END_JUCE_NAMESPACE /*** End of inlined file: juce_ResizableCornerComponent.cpp ***/ /*** Start of inlined file: juce_ScrollBar.cpp ***/ BEGIN_JUCE_NAMESPACE class ScrollBar::ScrollbarButton : public Button { public: int direction; ScrollbarButton (const int direction_, ScrollBar& owner_) : Button (String::empty), direction (direction_), owner (owner_) { setWantsKeyboardFocus (false); } ~ScrollbarButton() { } void paintButton (Graphics& g, bool over, bool down) { getLookAndFeel() .drawScrollbarButton (g, owner, getWidth(), getHeight(), direction, owner.isVertical(), over, down); } void clicked() { owner.moveScrollbarInSteps ((direction == 1 || direction == 2) ? 1 : -1); } juce_UseDebuggingNewOperator private: ScrollBar& owner; ScrollbarButton (const ScrollbarButton&); ScrollbarButton& operator= (const ScrollbarButton&); }; ScrollBar::ScrollBar (const bool vertical_, const bool buttonsAreVisible) : totalRange (0.0, 1.0), visibleRange (0.0, 0.1), singleStepSize (0.1), thumbAreaStart (0), thumbAreaSize (0), thumbStart (0), thumbSize (0), initialDelayInMillisecs (100), repeatDelayInMillisecs (50), minimumDelayInMillisecs (10), vertical (vertical_), isDraggingThumb (false), autohides (true) { setButtonVisibility (buttonsAreVisible); setRepaintsOnMouseActivity (true); setFocusContainer (true); } ScrollBar::~ScrollBar() { upButton = 0; downButton = 0; } void ScrollBar::setRangeLimits (const Range& newRangeLimit) { if (totalRange != newRangeLimit) { totalRange = newRangeLimit; setCurrentRange (visibleRange); updateThumbPosition(); } } void ScrollBar::setRangeLimits (const double newMinimum, const double newMaximum) { jassert (newMaximum >= newMinimum); // these can't be the wrong way round! setRangeLimits (Range (newMinimum, newMaximum)); } void ScrollBar::setCurrentRange (const Range& newRange) { const Range constrainedRange (totalRange.constrainRange (newRange)); if (visibleRange != constrainedRange) { visibleRange = constrainedRange; updateThumbPosition(); triggerAsyncUpdate(); } } void ScrollBar::setCurrentRange (const double newStart, const double newSize) { setCurrentRange (Range (newStart, newStart + newSize)); } void ScrollBar::setCurrentRangeStart (const double newStart) { setCurrentRange (visibleRange.movedToStartAt (newStart)); } void ScrollBar::setSingleStepSize (const double newSingleStepSize) { singleStepSize = newSingleStepSize; } void ScrollBar::moveScrollbarInSteps (const int howManySteps) { setCurrentRange (visibleRange + howManySteps * singleStepSize); } void ScrollBar::moveScrollbarInPages (const int howManyPages) { setCurrentRange (visibleRange + howManyPages * visibleRange.getLength()); } void ScrollBar::scrollToTop() { setCurrentRange (visibleRange.movedToStartAt (getMinimumRangeLimit())); } void ScrollBar::scrollToBottom() { setCurrentRange (visibleRange.movedToEndAt (getMaximumRangeLimit())); } void ScrollBar::setButtonRepeatSpeed (const int initialDelayInMillisecs_, const int repeatDelayInMillisecs_, const int minimumDelayInMillisecs_) { initialDelayInMillisecs = initialDelayInMillisecs_; repeatDelayInMillisecs = repeatDelayInMillisecs_; minimumDelayInMillisecs = minimumDelayInMillisecs_; if (upButton != 0) { upButton->setRepeatSpeed (initialDelayInMillisecs, repeatDelayInMillisecs, minimumDelayInMillisecs); downButton->setRepeatSpeed (initialDelayInMillisecs, repeatDelayInMillisecs, minimumDelayInMillisecs); } } void ScrollBar::addListener (Listener* const listener) { listeners.add (listener); } void ScrollBar::removeListener (Listener* const listener) { listeners.remove (listener); } void ScrollBar::handleAsyncUpdate() { double start = visibleRange.getStart(); // (need to use a temp variable for VC7 compatibility) listeners.call (&ScrollBar::Listener::scrollBarMoved, this, start); } void ScrollBar::updateThumbPosition() { int newThumbSize = roundToInt (totalRange.getLength() > 0 ? (visibleRange.getLength() * thumbAreaSize) / totalRange.getLength() : thumbAreaSize); if (newThumbSize < getLookAndFeel().getMinimumScrollbarThumbSize (*this)) newThumbSize = jmin (getLookAndFeel().getMinimumScrollbarThumbSize (*this), thumbAreaSize - 1); if (newThumbSize > thumbAreaSize) newThumbSize = thumbAreaSize; int newThumbStart = thumbAreaStart; if (totalRange.getLength() > visibleRange.getLength()) newThumbStart += roundToInt (((visibleRange.getStart() - totalRange.getStart()) * (thumbAreaSize - newThumbSize)) / (totalRange.getLength() - visibleRange.getLength())); setVisible ((! autohides) || (totalRange.getLength() > visibleRange.getLength() && visibleRange.getLength() > 0.0)); if (thumbStart != newThumbStart || thumbSize != newThumbSize) { const int repaintStart = jmin (thumbStart, newThumbStart) - 4; const int repaintSize = jmax (thumbStart + thumbSize, newThumbStart + newThumbSize) + 8 - repaintStart; if (vertical) repaint (0, repaintStart, getWidth(), repaintSize); else repaint (repaintStart, 0, repaintSize, getHeight()); thumbStart = newThumbStart; thumbSize = newThumbSize; } } void ScrollBar::setOrientation (const bool shouldBeVertical) { if (vertical != shouldBeVertical) { vertical = shouldBeVertical; if (upButton != 0) { upButton->direction = vertical ? 0 : 3; downButton->direction = vertical ? 2 : 1; } updateThumbPosition(); } } void ScrollBar::setButtonVisibility (const bool buttonsAreVisible) { upButton = 0; downButton = 0; if (buttonsAreVisible) { addAndMakeVisible (upButton = new ScrollbarButton (vertical ? 0 : 3, *this)); addAndMakeVisible (downButton = new ScrollbarButton (vertical ? 2 : 1, *this)); setButtonRepeatSpeed (initialDelayInMillisecs, repeatDelayInMillisecs, minimumDelayInMillisecs); } updateThumbPosition(); } void ScrollBar::setAutoHide (const bool shouldHideWhenFullRange) { autohides = shouldHideWhenFullRange; updateThumbPosition(); } bool ScrollBar::autoHides() const throw() { return autohides; } void ScrollBar::paint (Graphics& g) { if (thumbAreaSize > 0) { LookAndFeel& lf = getLookAndFeel(); const int thumb = (thumbAreaSize > lf.getMinimumScrollbarThumbSize (*this)) ? thumbSize : 0; if (vertical) { lf.drawScrollbar (g, *this, 0, thumbAreaStart, getWidth(), thumbAreaSize, vertical, thumbStart, thumb, isMouseOver(), isMouseButtonDown()); } else { lf.drawScrollbar (g, *this, thumbAreaStart, 0, thumbAreaSize, getHeight(), vertical, thumbStart, thumb, isMouseOver(), isMouseButtonDown()); } } } void ScrollBar::lookAndFeelChanged() { setComponentEffect (getLookAndFeel().getScrollbarEffect()); } void ScrollBar::resized() { const int length = ((vertical) ? getHeight() : getWidth()); const int buttonSize = (upButton != 0) ? jmin (getLookAndFeel().getScrollbarButtonSize (*this), (length >> 1)) : 0; if (length < 32 + getLookAndFeel().getMinimumScrollbarThumbSize (*this)) { thumbAreaStart = length >> 1; thumbAreaSize = 0; } else { thumbAreaStart = buttonSize; thumbAreaSize = length - (buttonSize << 1); } if (upButton != 0) { if (vertical) { upButton->setBounds (0, 0, getWidth(), buttonSize); downButton->setBounds (0, thumbAreaStart + thumbAreaSize, getWidth(), buttonSize); } else { upButton->setBounds (0, 0, buttonSize, getHeight()); downButton->setBounds (thumbAreaStart + thumbAreaSize, 0, buttonSize, getHeight()); } } updateThumbPosition(); } void ScrollBar::mouseDown (const MouseEvent& e) { isDraggingThumb = false; lastMousePos = vertical ? e.y : e.x; dragStartMousePos = lastMousePos; dragStartRange = visibleRange.getStart(); if (dragStartMousePos < thumbStart) { moveScrollbarInPages (-1); startTimer (400); } else if (dragStartMousePos >= thumbStart + thumbSize) { moveScrollbarInPages (1); startTimer (400); } else { isDraggingThumb = (thumbAreaSize > getLookAndFeel().getMinimumScrollbarThumbSize (*this)) && (thumbAreaSize > thumbSize); } } void ScrollBar::mouseDrag (const MouseEvent& e) { if (isDraggingThumb) { const int deltaPixels = ((vertical) ? e.y : e.x) - dragStartMousePos; setCurrentRangeStart (dragStartRange + deltaPixels * (totalRange.getLength() - visibleRange.getLength()) / (thumbAreaSize - thumbSize)); } else { lastMousePos = (vertical) ? e.y : e.x; } } void ScrollBar::mouseUp (const MouseEvent&) { isDraggingThumb = false; stopTimer(); repaint(); } void ScrollBar::mouseWheelMove (const MouseEvent&, float wheelIncrementX, float wheelIncrementY) { float increment = vertical ? wheelIncrementY : wheelIncrementX; if (increment < 0) increment = jmin (increment * 10.0f, -1.0f); else if (increment > 0) increment = jmax (increment * 10.0f, 1.0f); setCurrentRange (visibleRange - singleStepSize * increment); } void ScrollBar::timerCallback() { if (isMouseButtonDown()) { startTimer (40); if (lastMousePos < thumbStart) setCurrentRange (visibleRange - visibleRange.getLength()); else if (lastMousePos > thumbStart + thumbSize) setCurrentRangeStart (visibleRange.getEnd()); } else { stopTimer(); } } bool ScrollBar::keyPressed (const KeyPress& key) { if (! isVisible()) return false; if (key.isKeyCode (KeyPress::upKey) || key.isKeyCode (KeyPress::leftKey)) moveScrollbarInSteps (-1); else if (key.isKeyCode (KeyPress::downKey) || key.isKeyCode (KeyPress::rightKey)) moveScrollbarInSteps (1); else if (key.isKeyCode (KeyPress::pageUpKey)) moveScrollbarInPages (-1); else if (key.isKeyCode (KeyPress::pageDownKey)) moveScrollbarInPages (1); else if (key.isKeyCode (KeyPress::homeKey)) scrollToTop(); else if (key.isKeyCode (KeyPress::endKey)) scrollToBottom(); else return false; return true; } END_JUCE_NAMESPACE /*** End of inlined file: juce_ScrollBar.cpp ***/ /*** Start of inlined file: juce_StretchableLayoutManager.cpp ***/ BEGIN_JUCE_NAMESPACE StretchableLayoutManager::StretchableLayoutManager() : totalSize (0) { } StretchableLayoutManager::~StretchableLayoutManager() { } void StretchableLayoutManager::clearAllItems() { items.clear(); totalSize = 0; } void StretchableLayoutManager::setItemLayout (const int itemIndex, const double minimumSize, const double maximumSize, const double preferredSize) { ItemLayoutProperties* layout = getInfoFor (itemIndex); if (layout == 0) { layout = new ItemLayoutProperties(); layout->itemIndex = itemIndex; int i; for (i = 0; i < items.size(); ++i) if (items.getUnchecked (i)->itemIndex > itemIndex) break; items.insert (i, layout); } layout->minSize = minimumSize; layout->maxSize = maximumSize; layout->preferredSize = preferredSize; layout->currentSize = 0; } bool StretchableLayoutManager::getItemLayout (const int itemIndex, double& minimumSize, double& maximumSize, double& preferredSize) const { const ItemLayoutProperties* const layout = getInfoFor (itemIndex); if (layout != 0) { minimumSize = layout->minSize; maximumSize = layout->maxSize; preferredSize = layout->preferredSize; return true; } return false; } void StretchableLayoutManager::setTotalSize (const int newTotalSize) { totalSize = newTotalSize; fitComponentsIntoSpace (0, items.size(), totalSize, 0); } int StretchableLayoutManager::getItemCurrentPosition (const int itemIndex) const { int pos = 0; for (int i = 0; i < itemIndex; ++i) { const ItemLayoutProperties* const layout = getInfoFor (i); if (layout != 0) pos += layout->currentSize; } return pos; } int StretchableLayoutManager::getItemCurrentAbsoluteSize (const int itemIndex) const { const ItemLayoutProperties* const layout = getInfoFor (itemIndex); if (layout != 0) return layout->currentSize; return 0; } double StretchableLayoutManager::getItemCurrentRelativeSize (const int itemIndex) const { const ItemLayoutProperties* const layout = getInfoFor (itemIndex); if (layout != 0) return -layout->currentSize / (double) totalSize; return 0; } void StretchableLayoutManager::setItemPosition (const int itemIndex, int newPosition) { for (int i = items.size(); --i >= 0;) { const ItemLayoutProperties* const layout = items.getUnchecked(i); if (layout->itemIndex == itemIndex) { int realTotalSize = jmax (totalSize, getMinimumSizeOfItems (0, items.size())); const int minSizeAfterThisComp = getMinimumSizeOfItems (i, items.size()); const int maxSizeAfterThisComp = getMaximumSizeOfItems (i + 1, items.size()); newPosition = jmax (newPosition, totalSize - maxSizeAfterThisComp - layout->currentSize); newPosition = jmin (newPosition, realTotalSize - minSizeAfterThisComp); int endPos = fitComponentsIntoSpace (0, i, newPosition, 0); endPos += layout->currentSize; fitComponentsIntoSpace (i + 1, items.size(), totalSize - endPos, endPos); updatePrefSizesToMatchCurrentPositions(); break; } } } void StretchableLayoutManager::layOutComponents (Component** const components, int numComponents, int x, int y, int w, int h, const bool vertically, const bool resizeOtherDimension) { setTotalSize (vertically ? h : w); int pos = vertically ? y : x; for (int i = 0; i < numComponents; ++i) { const ItemLayoutProperties* const layout = getInfoFor (i); if (layout != 0) { Component* const c = components[i]; if (c != 0) { if (i == numComponents - 1) { // if it's the last item, crop it to exactly fit the available space.. if (resizeOtherDimension) { if (vertically) c->setBounds (x, pos, w, jmax (layout->currentSize, h - pos)); else c->setBounds (pos, y, jmax (layout->currentSize, w - pos), h); } else { if (vertically) c->setBounds (c->getX(), pos, c->getWidth(), jmax (layout->currentSize, h - pos)); else c->setBounds (pos, c->getY(), jmax (layout->currentSize, w - pos), c->getHeight()); } } else { if (resizeOtherDimension) { if (vertically) c->setBounds (x, pos, w, layout->currentSize); else c->setBounds (pos, y, layout->currentSize, h); } else { if (vertically) c->setBounds (c->getX(), pos, c->getWidth(), layout->currentSize); else c->setBounds (pos, c->getY(), layout->currentSize, c->getHeight()); } } } pos += layout->currentSize; } } } StretchableLayoutManager::ItemLayoutProperties* StretchableLayoutManager::getInfoFor (const int itemIndex) const { for (int i = items.size(); --i >= 0;) if (items.getUnchecked(i)->itemIndex == itemIndex) return items.getUnchecked(i); return 0; } int StretchableLayoutManager::fitComponentsIntoSpace (const int startIndex, const int endIndex, const int availableSpace, int startPos) { // calculate the total sizes int i; double totalIdealSize = 0.0; int totalMinimums = 0; for (i = startIndex; i < endIndex; ++i) { ItemLayoutProperties* const layout = items.getUnchecked (i); layout->currentSize = sizeToRealSize (layout->minSize, totalSize); totalMinimums += layout->currentSize; totalIdealSize += sizeToRealSize (layout->preferredSize, totalSize); } if (totalIdealSize <= 0) totalIdealSize = 1.0; // now calc the best sizes.. int extraSpace = availableSpace - totalMinimums; while (extraSpace > 0) { int numWantingMoreSpace = 0; int numHavingTakenExtraSpace = 0; // first figure out how many comps want a slice of the extra space.. for (i = startIndex; i < endIndex; ++i) { ItemLayoutProperties* const layout = items.getUnchecked (i); double sizeWanted = sizeToRealSize (layout->preferredSize, totalSize); const int bestSize = jlimit (layout->currentSize, jmax (layout->currentSize, sizeToRealSize (layout->maxSize, totalSize)), roundToInt (sizeWanted * availableSpace / totalIdealSize)); if (bestSize > layout->currentSize) ++numWantingMoreSpace; } // ..share out the extra space.. for (i = startIndex; i < endIndex; ++i) { ItemLayoutProperties* const layout = items.getUnchecked (i); double sizeWanted = sizeToRealSize (layout->preferredSize, totalSize); int bestSize = jlimit (layout->currentSize, jmax (layout->currentSize, sizeToRealSize (layout->maxSize, totalSize)), roundToInt (sizeWanted * availableSpace / totalIdealSize)); const int extraWanted = bestSize - layout->currentSize; if (extraWanted > 0) { const int extraAllowed = jmin (extraWanted, extraSpace / jmax (1, numWantingMoreSpace)); if (extraAllowed > 0) { ++numHavingTakenExtraSpace; --numWantingMoreSpace; layout->currentSize += extraAllowed; extraSpace -= extraAllowed; } } } if (numHavingTakenExtraSpace <= 0) break; } // ..and calculate the end position for (i = startIndex; i < endIndex; ++i) { ItemLayoutProperties* const layout = items.getUnchecked(i); startPos += layout->currentSize; } return startPos; } int StretchableLayoutManager::getMinimumSizeOfItems (const int startIndex, const int endIndex) const { int totalMinimums = 0; for (int i = startIndex; i < endIndex; ++i) totalMinimums += sizeToRealSize (items.getUnchecked (i)->minSize, totalSize); return totalMinimums; } int StretchableLayoutManager::getMaximumSizeOfItems (const int startIndex, const int endIndex) const { int totalMaximums = 0; for (int i = startIndex; i < endIndex; ++i) totalMaximums += sizeToRealSize (items.getUnchecked (i)->maxSize, totalSize); return totalMaximums; } void StretchableLayoutManager::updatePrefSizesToMatchCurrentPositions() { for (int i = 0; i < items.size(); ++i) { ItemLayoutProperties* const layout = items.getUnchecked (i); layout->preferredSize = (layout->preferredSize < 0) ? getItemCurrentRelativeSize (i) : getItemCurrentAbsoluteSize (i); } } int StretchableLayoutManager::sizeToRealSize (double size, int totalSpace) { if (size < 0) size *= -totalSpace; return roundToInt (size); } END_JUCE_NAMESPACE /*** End of inlined file: juce_StretchableLayoutManager.cpp ***/ /*** Start of inlined file: juce_StretchableLayoutResizerBar.cpp ***/ BEGIN_JUCE_NAMESPACE StretchableLayoutResizerBar::StretchableLayoutResizerBar (StretchableLayoutManager* layout_, const int itemIndex_, const bool isVertical_) : layout (layout_), itemIndex (itemIndex_), isVertical (isVertical_) { setRepaintsOnMouseActivity (true); setMouseCursor (MouseCursor (isVertical_ ? MouseCursor::LeftRightResizeCursor : MouseCursor::UpDownResizeCursor)); } StretchableLayoutResizerBar::~StretchableLayoutResizerBar() { } void StretchableLayoutResizerBar::paint (Graphics& g) { getLookAndFeel().drawStretchableLayoutResizerBar (g, getWidth(), getHeight(), isVertical, isMouseOver(), isMouseButtonDown()); } void StretchableLayoutResizerBar::mouseDown (const MouseEvent&) { mouseDownPos = layout->getItemCurrentPosition (itemIndex); } void StretchableLayoutResizerBar::mouseDrag (const MouseEvent& e) { const int desiredPos = mouseDownPos + (isVertical ? e.getDistanceFromDragStartX() : e.getDistanceFromDragStartY()); layout->setItemPosition (itemIndex, desiredPos); hasBeenMoved(); } void StretchableLayoutResizerBar::hasBeenMoved() { if (getParentComponent() != 0) getParentComponent()->resized(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_StretchableLayoutResizerBar.cpp ***/ /*** Start of inlined file: juce_StretchableObjectResizer.cpp ***/ BEGIN_JUCE_NAMESPACE StretchableObjectResizer::StretchableObjectResizer() { } StretchableObjectResizer::~StretchableObjectResizer() { } void StretchableObjectResizer::addItem (const double size, const double minSize, const double maxSize, const int order) { // the order must be >= 0 but less than the maximum integer value. jassert (order >= 0 && order < std::numeric_limits::max()); Item* const item = new Item(); item->size = size; item->minSize = minSize; item->maxSize = maxSize; item->order = order; items.add (item); } double StretchableObjectResizer::getItemSize (const int index) const throw() { const Item* const it = items [index]; return it != 0 ? it->size : 0; } void StretchableObjectResizer::resizeToFit (const double targetSize) { int order = 0; for (;;) { double currentSize = 0; double minSize = 0; double maxSize = 0; int nextHighestOrder = std::numeric_limits::max(); for (int i = 0; i < items.size(); ++i) { const Item* const it = items.getUnchecked(i); currentSize += it->size; if (it->order <= order) { minSize += it->minSize; maxSize += it->maxSize; } else { minSize += it->size; maxSize += it->size; nextHighestOrder = jmin (nextHighestOrder, it->order); } } const double thisIterationTarget = jlimit (minSize, maxSize, targetSize); if (thisIterationTarget >= currentSize) { const double availableExtraSpace = maxSize - currentSize; const double targetAmountOfExtraSpace = thisIterationTarget - currentSize; const double scale = targetAmountOfExtraSpace / availableExtraSpace; for (int i = 0; i < items.size(); ++i) { Item* const it = items.getUnchecked(i); if (it->order <= order) it->size = jmin (it->maxSize, it->size + (it->maxSize - it->size) * scale); } } else { const double amountOfSlack = currentSize - minSize; const double targetAmountOfSlack = thisIterationTarget - minSize; const double scale = targetAmountOfSlack / amountOfSlack; for (int i = 0; i < items.size(); ++i) { Item* const it = items.getUnchecked(i); if (it->order <= order) it->size = jmax (it->minSize, it->minSize + (it->size - it->minSize) * scale); } } if (nextHighestOrder < std::numeric_limits::max()) order = nextHighestOrder; else break; } } END_JUCE_NAMESPACE /*** End of inlined file: juce_StretchableObjectResizer.cpp ***/ /*** Start of inlined file: juce_TabbedButtonBar.cpp ***/ BEGIN_JUCE_NAMESPACE 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&); TabAreaBehindFrontButtonComponent& operator= (const TabAreaBehindFrontButtonComponent&); }; TabbedButtonBar::TabbedButtonBar (const Orientation orientation_) : orientation (orientation_), currentTabIndex (-1) { setInterceptsMouseClicks (false, true); addAndMakeVisible (behindFrontTab = new TabAreaBehindFrontButtonComponent (this)); setFocusContainer (true); } TabbedButtonBar::~TabbedButtonBar() { extraTabsButton = 0; 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; extraTabsButton = 0; 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); delete getTabButton (tabIndex); 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() { extraTabsButton = 0; 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 { extraTabsButton = 0; } int pos = 0; TabBarButton* frontTab = 0; for (i = 0; i < tabs.size(); ++i) { TabBarButton* const tb = getTabButton (i); if (tb != 0) { const int bestLength = roundToInt (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 (getLocalBounds()); 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 (button == extraTabsButton) { 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 /*** End of inlined file: juce_TabbedButtonBar.cpp ***/ /*** Start of inlined file: juce_TabbedComponent.cpp ***/ BEGIN_JUCE_NAMESPACE class TabCompButtonBar : public TabbedButtonBar { public: TabCompButtonBar (TabbedComponent* const owner_, const TabbedButtonBar::Orientation orientation_) : TabbedButtonBar (orientation_), owner (owner_) { } ~TabCompButtonBar() { } void currentTabChanged (int newCurrentTabIndex, const String& newTabName) { owner->changeCallback (newCurrentTabIndex, newTabName); } void popupMenuClickOnTab (int tabIndex, const String& tabName) { owner->popupMenuClickOnTab (tabIndex, tabName); } const Colour getTabBackgroundColour (const int tabIndex) { return owner->tabs->getTabBackgroundColour (tabIndex); } TabBarButton* createTabButton (const String& tabName, int tabIndex) { return owner->createTabButton (tabName, tabIndex); } juce_UseDebuggingNewOperator private: TabbedComponent* const owner; TabCompButtonBar (const TabCompButtonBar&); TabCompButtonBar& operator= (const TabCompButtonBar&); }; TabbedComponent::TabbedComponent (const TabbedButtonBar::Orientation orientation) : panelComponent (0), tabDepth (30), outlineThickness (1), edgeIndent (0) { addAndMakeVisible (tabs = new TabCompButtonBar (this, orientation)); } TabbedComponent::~TabbedComponent() { clearTabs(); delete tabs; } void TabbedComponent::setOrientation (const TabbedButtonBar::Orientation orientation) { tabs->setOrientation (orientation); resized(); } TabbedButtonBar::Orientation TabbedComponent::getOrientation() const throw() { return tabs->getOrientation(); } void TabbedComponent::setTabBarDepth (const int newDepth) { if (tabDepth != newDepth) { tabDepth = newDepth; resized(); } } TabBarButton* TabbedComponent::createTabButton (const String& tabName, const int tabIndex) { return new TabBarButton (tabName, tabs, tabIndex); } const Identifier TabbedComponent::deleteComponentId ("deleteByTabComp_"); void TabbedComponent::clearTabs() { if (panelComponent != 0) { panelComponent->setVisible (false); removeChildComponent (panelComponent); panelComponent = 0; } tabs->clearTabs(); for (int i = contentComponents.size(); --i >= 0;) { Component* const c = contentComponents.getUnchecked(i); // be careful not to delete these components until they've been removed from the tab component jassert (c == 0 || c->isValidComponent()); if (c != 0 && (bool) c->getProperties() [deleteComponentId]) delete c; } contentComponents.clear(); } void TabbedComponent::addTab (const String& tabName, const Colour& tabBackgroundColour, Component* const contentComponent, const bool deleteComponentWhenNotNeeded, const int insertIndex) { contentComponents.insert (insertIndex, contentComponent); if (contentComponent != 0) contentComponent->getProperties().set (deleteComponentId, deleteComponentWhenNotNeeded); tabs->addTab (tabName, tabBackgroundColour, insertIndex); } void TabbedComponent::setTabName (const int tabIndex, const String& newName) { tabs->setTabName (tabIndex, newName); } void TabbedComponent::removeTab (const int tabIndex) { Component* const c = contentComponents [tabIndex]; if (c != 0 && (bool) c->getProperties() [deleteComponentId]) { if (c == panelComponent) panelComponent = 0; delete c; } contentComponents.remove (tabIndex); tabs->removeTab (tabIndex); } int TabbedComponent::getNumTabs() const { return tabs->getNumTabs(); } const StringArray TabbedComponent::getTabNames() const { return tabs->getTabNames(); } Component* TabbedComponent::getTabContentComponent (const int tabIndex) const throw() { return contentComponents [tabIndex]; } const Colour TabbedComponent::getTabBackgroundColour (const int tabIndex) const throw() { return tabs->getTabBackgroundColour (tabIndex); } void TabbedComponent::setTabBackgroundColour (const int tabIndex, const Colour& newColour) { tabs->setTabBackgroundColour (tabIndex, newColour); if (getCurrentTabIndex() == tabIndex) repaint(); } void TabbedComponent::setCurrentTabIndex (const int newTabIndex, const bool sendChangeMessage) { tabs->setCurrentTabIndex (newTabIndex, sendChangeMessage); } int TabbedComponent::getCurrentTabIndex() const { return tabs->getCurrentTabIndex(); } const String& TabbedComponent::getCurrentTabName() const { return tabs->getCurrentTabName(); } void TabbedComponent::setOutline (int thickness) { outlineThickness = thickness; repaint(); } void TabbedComponent::setIndent (const int indentThickness) { edgeIndent = indentThickness; } void TabbedComponent::paint (Graphics& g) { g.fillAll (findColour (backgroundColourId)); const TabbedButtonBar::Orientation o = getOrientation(); int x = 0; int y = 0; int r = getWidth(); int b = getHeight(); if (o == TabbedButtonBar::TabsAtTop) y += tabDepth; else if (o == TabbedButtonBar::TabsAtBottom) b -= tabDepth; else if (o == TabbedButtonBar::TabsAtLeft) x += tabDepth; else if (o == TabbedButtonBar::TabsAtRight) r -= tabDepth; g.reduceClipRegion (x, y, r - x, b - y); g.fillAll (tabs->getTabBackgroundColour (getCurrentTabIndex())); if (outlineThickness > 0) { if (o == TabbedButtonBar::TabsAtTop) --y; else if (o == TabbedButtonBar::TabsAtBottom) ++b; else if (o == TabbedButtonBar::TabsAtLeft) --x; else if (o == TabbedButtonBar::TabsAtRight) ++r; g.setColour (findColour (outlineColourId)); g.drawRect (x, y, r - x, b - y, outlineThickness); } } void TabbedComponent::resized() { const TabbedButtonBar::Orientation o = getOrientation(); const int indent = edgeIndent + outlineThickness; BorderSize indents (indent); if (o == TabbedButtonBar::TabsAtTop) { tabs->setBounds (0, 0, getWidth(), tabDepth); indents.setTop (tabDepth + edgeIndent); } else if (o == TabbedButtonBar::TabsAtBottom) { tabs->setBounds (0, getHeight() - tabDepth, getWidth(), tabDepth); indents.setBottom (tabDepth + edgeIndent); } else if (o == TabbedButtonBar::TabsAtLeft) { tabs->setBounds (0, 0, tabDepth, getHeight()); indents.setLeft (tabDepth + edgeIndent); } else if (o == TabbedButtonBar::TabsAtRight) { tabs->setBounds (getWidth() - tabDepth, 0, tabDepth, getHeight()); indents.setRight (tabDepth + edgeIndent); } const Rectangle bounds (indents.subtractedFrom (getLocalBounds())); for (int i = contentComponents.size(); --i >= 0;) if (contentComponents.getUnchecked (i) != 0) contentComponents.getUnchecked (i)->setBounds (bounds); } void TabbedComponent::lookAndFeelChanged() { for (int i = contentComponents.size(); --i >= 0;) if (contentComponents.getUnchecked (i) != 0) contentComponents.getUnchecked (i)->lookAndFeelChanged(); } void TabbedComponent::changeCallback (const int newCurrentTabIndex, const String& newTabName) { if (panelComponent != 0) { panelComponent->setVisible (false); removeChildComponent (panelComponent); panelComponent = 0; } if (getCurrentTabIndex() >= 0) { panelComponent = contentComponents [getCurrentTabIndex()]; if (panelComponent != 0) { // do these ops as two stages instead of addAndMakeVisible() so that the // component has always got a parent when it gets the visibilityChanged() callback addChildComponent (panelComponent); panelComponent->setVisible (true); panelComponent->toFront (true); } repaint(); } resized(); currentTabChanged (newCurrentTabIndex, newTabName); } void TabbedComponent::currentTabChanged (const int, const String&) { } void TabbedComponent::popupMenuClickOnTab (const int, const String&) { } END_JUCE_NAMESPACE /*** End of inlined file: juce_TabbedComponent.cpp ***/ /*** Start of inlined file: juce_Viewport.cpp ***/ BEGIN_JUCE_NAMESPACE Viewport::Viewport (const String& componentName) : Component (componentName), scrollBarThickness (0), singleStepX (16), singleStepY (16), showHScrollbar (true), showVScrollbar (true), verticalScrollBar (true), horizontalScrollBar (false) { // content holder is used to clip the contents so they don't overlap the scrollbars addAndMakeVisible (&contentHolder); contentHolder.setInterceptsMouseClicks (false, true); addChildComponent (&verticalScrollBar); addChildComponent (&horizontalScrollBar); verticalScrollBar.addListener (this); horizontalScrollBar.addListener (this); setInterceptsMouseClicks (false, true); setWantsKeyboardFocus (true); } Viewport::~Viewport() { contentHolder.deleteAllChildren(); } void Viewport::visibleAreaChanged (int, int, int, int) { } void Viewport::setViewedComponent (Component* const newViewedComponent) { if (contentComp.getComponent() != newViewedComponent) { { ScopedPointer oldCompDeleter (contentComp); contentComp = 0; } contentComp = newViewedComponent; if (contentComp != 0) { contentComp->setTopLeftPosition (0, 0); contentHolder.addAndMakeVisible (contentComp); contentComp->addComponentListener (this); } updateVisibleArea(); } } int Viewport::getMaximumVisibleWidth() const { return contentHolder.getWidth(); } int Viewport::getMaximumVisibleHeight() const { return contentHolder.getHeight(); } void Viewport::setViewPosition (const int xPixelsOffset, const int yPixelsOffset) { if (contentComp != 0) contentComp->setTopLeftPosition (jmax (jmin (0, contentHolder.getWidth() - contentComp->getWidth()), jmin (0, -xPixelsOffset)), jmax (jmin (0, contentHolder.getHeight() - contentComp->getHeight()), jmin (0, -yPixelsOffset))); } void Viewport::setViewPosition (const Point& newPosition) { setViewPosition (newPosition.getX(), newPosition.getY()); } void Viewport::setViewPositionProportionately (const double x, const double y) { if (contentComp != 0) setViewPosition (jmax (0, roundToInt (x * (contentComp->getWidth() - getWidth()))), jmax (0, roundToInt (y * (contentComp->getHeight() - getHeight())))); } bool Viewport::autoScroll (const int mouseX, const int mouseY, const int activeBorderThickness, const int maximumSpeed) { if (contentComp != 0) { int dx = 0, dy = 0; if (horizontalScrollBar.isVisible() || contentComp->getX() < 0 || contentComp->getRight() > getWidth()) { if (mouseX < activeBorderThickness) dx = activeBorderThickness - mouseX; else if (mouseX >= contentHolder.getWidth() - activeBorderThickness) dx = (contentHolder.getWidth() - activeBorderThickness) - mouseX; if (dx < 0) dx = jmax (dx, -maximumSpeed, contentHolder.getWidth() - contentComp->getRight()); else dx = jmin (dx, maximumSpeed, -contentComp->getX()); } if (verticalScrollBar.isVisible() || contentComp->getY() < 0 || contentComp->getBottom() > getHeight()) { if (mouseY < activeBorderThickness) dy = activeBorderThickness - mouseY; else if (mouseY >= contentHolder.getHeight() - activeBorderThickness) dy = (contentHolder.getHeight() - activeBorderThickness) - mouseY; if (dy < 0) dy = jmax (dy, -maximumSpeed, contentHolder.getHeight() - contentComp->getBottom()); else dy = jmin (dy, maximumSpeed, -contentComp->getY()); } if (dx != 0 || dy != 0) { contentComp->setTopLeftPosition (contentComp->getX() + dx, contentComp->getY() + dy); return true; } } return false; } void Viewport::componentMovedOrResized (Component&, bool, bool) { updateVisibleArea(); } void Viewport::resized() { updateVisibleArea(); } void Viewport::updateVisibleArea() { const int scrollbarWidth = getScrollBarThickness(); const bool canShowAnyBars = getWidth() > scrollbarWidth && getHeight() > scrollbarWidth; const bool canShowHBar = showHScrollbar && canShowAnyBars; const bool canShowVBar = showVScrollbar && canShowAnyBars; bool hBarVisible = canShowHBar && ! horizontalScrollBar.autoHides(); bool vBarVisible = canShowVBar && ! verticalScrollBar.autoHides(); Rectangle contentArea (getLocalBounds()); if (contentComp != 0 && ! contentArea.contains (contentComp->getBounds())) { hBarVisible = canShowHBar && (hBarVisible || contentComp->getX() < 0 || contentComp->getRight() > contentArea.getWidth()); vBarVisible = canShowVBar && (vBarVisible || contentComp->getY() < 0 || contentComp->getBottom() > contentArea.getHeight()); if (vBarVisible) contentArea.setWidth (getWidth() - scrollbarWidth); if (hBarVisible) contentArea.setHeight (getHeight() - scrollbarWidth); if (! contentArea.contains (contentComp->getBounds())) { hBarVisible = canShowHBar && (hBarVisible || contentComp->getRight() > contentArea.getWidth()); vBarVisible = canShowVBar && (vBarVisible || contentComp->getBottom() > contentArea.getHeight()); } } if (vBarVisible) contentArea.setWidth (getWidth() - scrollbarWidth); if (hBarVisible) contentArea.setHeight (getHeight() - scrollbarWidth); contentHolder.setBounds (contentArea); Rectangle contentBounds; if (contentComp != 0) contentBounds = contentComp->getBounds(); const Point visibleOrigin (-contentBounds.getPosition()); if (hBarVisible) { horizontalScrollBar.setBounds (0, contentArea.getHeight(), contentArea.getWidth(), scrollbarWidth); horizontalScrollBar.setRangeLimits (0.0, contentBounds.getWidth()); horizontalScrollBar.setCurrentRange (visibleOrigin.getX(), contentArea.getWidth()); horizontalScrollBar.setSingleStepSize (singleStepX); horizontalScrollBar.cancelPendingUpdate(); } if (vBarVisible) { verticalScrollBar.setBounds (contentArea.getWidth(), 0, scrollbarWidth, contentArea.getHeight()); verticalScrollBar.setRangeLimits (0.0, contentBounds.getHeight()); verticalScrollBar.setCurrentRange (visibleOrigin.getY(), contentArea.getHeight()); verticalScrollBar.setSingleStepSize (singleStepY); verticalScrollBar.cancelPendingUpdate(); } // Force the visibility *after* setting the ranges to avoid flicker caused by edge conditions in the numbers. horizontalScrollBar.setVisible (hBarVisible); verticalScrollBar.setVisible (vBarVisible); const Rectangle visibleArea (visibleOrigin.getX(), visibleOrigin.getY(), jmin (contentBounds.getWidth() - visibleOrigin.getX(), contentArea.getWidth()), jmin (contentBounds.getHeight() - visibleOrigin.getY(), contentArea.getHeight())); if (lastVisibleArea != visibleArea) { lastVisibleArea = visibleArea; visibleAreaChanged (visibleArea.getX(), visibleArea.getY(), visibleArea.getWidth(), visibleArea.getHeight()); } horizontalScrollBar.handleUpdateNowIfNeeded(); verticalScrollBar.handleUpdateNowIfNeeded(); } void Viewport::setSingleStepSizes (const int stepX, const int stepY) { if (singleStepX != stepX || singleStepY != stepY) { singleStepX = stepX; singleStepY = stepY; updateVisibleArea(); } } void Viewport::setScrollBarsShown (const bool showVerticalScrollbarIfNeeded, const bool showHorizontalScrollbarIfNeeded) { if (showVScrollbar != showVerticalScrollbarIfNeeded || showHScrollbar != showHorizontalScrollbarIfNeeded) { showVScrollbar = showVerticalScrollbarIfNeeded; showHScrollbar = showHorizontalScrollbarIfNeeded; updateVisibleArea(); } } void Viewport::setScrollBarThickness (const int thickness) { if (scrollBarThickness != thickness) { scrollBarThickness = thickness; updateVisibleArea(); } } int Viewport::getScrollBarThickness() const { return scrollBarThickness > 0 ? scrollBarThickness : getLookAndFeel().getDefaultScrollbarWidth(); } void Viewport::setScrollBarButtonVisibility (const bool buttonsVisible) { verticalScrollBar.setButtonVisibility (buttonsVisible); horizontalScrollBar.setButtonVisibility (buttonsVisible); } void Viewport::scrollBarMoved (ScrollBar* scrollBarThatHasMoved, double newRangeStart) { const int newRangeStartInt = roundToInt (newRangeStart); if (scrollBarThatHasMoved == &horizontalScrollBar) { setViewPosition (newRangeStartInt, getViewPositionY()); } else if (scrollBarThatHasMoved == &verticalScrollBar) { setViewPosition (getViewPositionX(), newRangeStartInt); } } void Viewport::mouseWheelMove (const MouseEvent& e, const float wheelIncrementX, const float wheelIncrementY) { if (! useMouseWheelMoveIfNeeded (e, wheelIncrementX, wheelIncrementY)) Component::mouseWheelMove (e, wheelIncrementX, wheelIncrementY); } bool Viewport::useMouseWheelMoveIfNeeded (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY) { if (! (e.mods.isAltDown() || e.mods.isCtrlDown())) { const bool hasVertBar = verticalScrollBar.isVisible(); const bool hasHorzBar = horizontalScrollBar.isVisible(); if (hasHorzBar || hasVertBar) { if (wheelIncrementX != 0) { wheelIncrementX *= 14.0f * singleStepX; wheelIncrementX = (wheelIncrementX < 0) ? jmin (wheelIncrementX, -1.0f) : jmax (wheelIncrementX, 1.0f); } if (wheelIncrementY != 0) { wheelIncrementY *= 14.0f * singleStepY; wheelIncrementY = (wheelIncrementY < 0) ? jmin (wheelIncrementY, -1.0f) : jmax (wheelIncrementY, 1.0f); } Point pos (getViewPosition()); if (wheelIncrementX != 0 && wheelIncrementY != 0 && hasHorzBar && hasVertBar) { pos.setX (pos.getX() - roundToInt (wheelIncrementX)); pos.setY (pos.getY() - roundToInt (wheelIncrementY)); } else if (hasHorzBar && (wheelIncrementX != 0 || e.mods.isShiftDown() || ! hasVertBar)) { if (wheelIncrementX == 0 && ! hasVertBar) wheelIncrementX = wheelIncrementY; pos.setX (pos.getX() - roundToInt (wheelIncrementX)); } else if (hasVertBar && wheelIncrementY != 0) { pos.setY (pos.getY() - roundToInt (wheelIncrementY)); } if (pos != getViewPosition()) { setViewPosition (pos); return true; } } } return false; } bool Viewport::keyPressed (const KeyPress& key) { const bool isUpDownKey = key.isKeyCode (KeyPress::upKey) || key.isKeyCode (KeyPress::downKey) || key.isKeyCode (KeyPress::pageUpKey) || key.isKeyCode (KeyPress::pageDownKey) || key.isKeyCode (KeyPress::homeKey) || key.isKeyCode (KeyPress::endKey); if (verticalScrollBar.isVisible() && isUpDownKey) return verticalScrollBar.keyPressed (key); const bool isLeftRightKey = key.isKeyCode (KeyPress::leftKey) || key.isKeyCode (KeyPress::rightKey); if (horizontalScrollBar.isVisible() && (isUpDownKey || isLeftRightKey)) return horizontalScrollBar.keyPressed (key); return false; } END_JUCE_NAMESPACE /*** End of inlined file: juce_Viewport.cpp ***/ /*** Start of inlined file: juce_LookAndFeel.cpp ***/ BEGIN_JUCE_NAMESPACE static const Colour createBaseColour (const Colour& buttonColour, const bool hasKeyboardFocus, const bool isMouseOverButton, const bool isButtonDown) throw() { const float sat = hasKeyboardFocus ? 1.3f : 0.9f; const Colour baseColour (buttonColour.withMultipliedSaturation (sat)); if (isButtonDown) return baseColour.contrasting (0.2f); else if (isMouseOverButton) return baseColour.contrasting (0.1f); return baseColour; } LookAndFeel::LookAndFeel() { /* if this fails it means you're trying to create a LookAndFeel object before the static Colours have been initialised. That ain't gonna work. It probably means that you're using a static LookAndFeel object and that your compiler has decided to intialise it before the Colours class. */ jassert (Colours::white == Colour (0xffffffff)); // set up the standard set of colours.. const int textButtonColour = 0xffbbbbff; const int textHighlightColour = 0x401111ee; const int standardOutlineColour = 0xb2808080; static const int standardColours[] = { TextButton::buttonColourId, textButtonColour, TextButton::buttonOnColourId, 0xff4444ff, TextButton::textColourOnId, 0xff000000, TextButton::textColourOffId, 0xff000000, ComboBox::buttonColourId, 0xffbbbbff, ComboBox::outlineColourId, standardOutlineColour, ToggleButton::textColourId, 0xff000000, TextEditor::backgroundColourId, 0xffffffff, TextEditor::textColourId, 0xff000000, TextEditor::highlightColourId, textHighlightColour, TextEditor::highlightedTextColourId, 0xff000000, TextEditor::caretColourId, 0xff000000, TextEditor::outlineColourId, 0x00000000, TextEditor::focusedOutlineColourId, textButtonColour, TextEditor::shadowColourId, 0x38000000, Label::backgroundColourId, 0x00000000, Label::textColourId, 0xff000000, Label::outlineColourId, 0x00000000, ScrollBar::backgroundColourId, 0x00000000, ScrollBar::thumbColourId, 0xffffffff, ScrollBar::trackColourId, 0xffffffff, TreeView::linesColourId, 0x4c000000, TreeView::backgroundColourId, 0x00000000, TreeView::dragAndDropIndicatorColourId, 0x80ff0000, PopupMenu::backgroundColourId, 0xffffffff, PopupMenu::textColourId, 0xff000000, PopupMenu::headerTextColourId, 0xff000000, PopupMenu::highlightedTextColourId, 0xffffffff, PopupMenu::highlightedBackgroundColourId, 0x991111aa, ComboBox::textColourId, 0xff000000, ComboBox::backgroundColourId, 0xffffffff, ComboBox::arrowColourId, 0x99000000, ListBox::backgroundColourId, 0xffffffff, ListBox::outlineColourId, standardOutlineColour, ListBox::textColourId, 0xff000000, Slider::backgroundColourId, 0x00000000, Slider::thumbColourId, textButtonColour, Slider::trackColourId, 0x7fffffff, Slider::rotarySliderFillColourId, 0x7f0000ff, Slider::rotarySliderOutlineColourId, 0x66000000, Slider::textBoxTextColourId, 0xff000000, Slider::textBoxBackgroundColourId, 0xffffffff, Slider::textBoxHighlightColourId, textHighlightColour, Slider::textBoxOutlineColourId, standardOutlineColour, ResizableWindow::backgroundColourId, 0xff777777, //DocumentWindow::textColourId, 0xff000000, // (this is deliberately not set) AlertWindow::backgroundColourId, 0xffededed, AlertWindow::textColourId, 0xff000000, AlertWindow::outlineColourId, 0xff666666, ProgressBar::backgroundColourId, 0xffeeeeee, ProgressBar::foregroundColourId, 0xffaaaaee, TooltipWindow::backgroundColourId, 0xffeeeebb, TooltipWindow::textColourId, 0xff000000, TooltipWindow::outlineColourId, 0x4c000000, TabbedComponent::backgroundColourId, 0x00000000, TabbedComponent::outlineColourId, 0xff777777, TabbedButtonBar::tabOutlineColourId, 0x80000000, TabbedButtonBar::frontOutlineColourId, 0x90000000, Toolbar::backgroundColourId, 0xfff6f8f9, Toolbar::separatorColourId, 0x4c000000, Toolbar::buttonMouseOverBackgroundColourId, 0x4c0000ff, Toolbar::buttonMouseDownBackgroundColourId, 0x800000ff, Toolbar::labelTextColourId, 0xff000000, Toolbar::editingModeOutlineColourId, 0xffff0000, HyperlinkButton::textColourId, 0xcc1111ee, GroupComponent::outlineColourId, 0x66000000, GroupComponent::textColourId, 0xff000000, DirectoryContentsDisplayComponent::highlightColourId, textHighlightColour, DirectoryContentsDisplayComponent::textColourId, 0xff000000, 0x1000440, /*LassoComponent::lassoFillColourId*/ 0x66dddddd, 0x1000441, /*LassoComponent::lassoOutlineColourId*/ 0x99111111, MidiKeyboardComponent::whiteNoteColourId, 0xffffffff, MidiKeyboardComponent::blackNoteColourId, 0xff000000, MidiKeyboardComponent::keySeparatorLineColourId, 0x66000000, MidiKeyboardComponent::mouseOverKeyOverlayColourId, 0x80ffff00, MidiKeyboardComponent::keyDownOverlayColourId, 0xffb6b600, MidiKeyboardComponent::textLabelColourId, 0xff000000, MidiKeyboardComponent::upDownButtonBackgroundColourId, 0xffd3d3d3, MidiKeyboardComponent::upDownButtonArrowColourId, 0xff000000, CodeEditorComponent::backgroundColourId, 0xffffffff, CodeEditorComponent::caretColourId, 0xff000000, CodeEditorComponent::highlightColourId, textHighlightColour, CodeEditorComponent::defaultTextColourId, 0xff000000, ColourSelector::backgroundColourId, 0xffe5e5e5, ColourSelector::labelTextColourId, 0xff000000, KeyMappingEditorComponent::backgroundColourId, 0x00000000, KeyMappingEditorComponent::textColourId, 0xff000000, FileSearchPathListComponent::backgroundColourId, 0xffffffff, FileChooserDialogBox::titleTextColourId, 0xff000000, DrawableButton::textColourId, 0xff000000, }; for (int i = 0; i < numElementsInArray (standardColours); i += 2) setColour (standardColours [i], Colour (standardColours [i + 1])); static String defaultSansName, defaultSerifName, defaultFixedName; if (defaultSansName.isEmpty()) Font::getPlatformDefaultFontNames (defaultSansName, defaultSerifName, defaultFixedName); defaultSans = defaultSansName; defaultSerif = defaultSerifName; defaultFixed = defaultFixedName; } LookAndFeel::~LookAndFeel() { } const Colour LookAndFeel::findColour (const int colourId) const throw() { const int index = colourIds.indexOf (colourId); if (index >= 0) return colours [index]; jassertfalse; return Colours::black; } void LookAndFeel::setColour (const int colourId, const Colour& colour) throw() { const int index = colourIds.indexOf (colourId); if (index >= 0) { colours.set (index, colour); } else { colourIds.add (colourId); colours.add (colour); } } bool LookAndFeel::isColourSpecified (const int colourId) const throw() { return colourIds.contains (colourId); } static LookAndFeel* defaultLF = 0; static LookAndFeel* currentDefaultLF = 0; LookAndFeel& LookAndFeel::getDefaultLookAndFeel() throw() { // if this happens, your app hasn't initialised itself properly.. if you're // trying to hack your own main() function, have a look at // JUCEApplication::initialiseForGUI() jassert (currentDefaultLF != 0); return *currentDefaultLF; } void LookAndFeel::setDefaultLookAndFeel (LookAndFeel* newDefaultLookAndFeel) throw() { if (newDefaultLookAndFeel == 0) { if (defaultLF == 0) defaultLF = new LookAndFeel(); newDefaultLookAndFeel = defaultLF; } currentDefaultLF = newDefaultLookAndFeel; for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;) { Component* const c = Desktop::getInstance().getComponent (i); if (c != 0) c->sendLookAndFeelChange(); } } void LookAndFeel::clearDefaultLookAndFeel() throw() { if (currentDefaultLF == defaultLF) currentDefaultLF = 0; deleteAndZero (defaultLF); } const Typeface::Ptr LookAndFeel::getTypefaceForFont (const Font& font) { String faceName (font.getTypefaceName()); if (faceName == Font::getDefaultSansSerifFontName()) faceName = defaultSans; else if (faceName == Font::getDefaultSerifFontName()) faceName = defaultSerif; else if (faceName == Font::getDefaultMonospacedFontName()) faceName = defaultFixed; Font f (font); f.setTypefaceName (faceName); return Typeface::createSystemTypefaceFor (f); } void LookAndFeel::setDefaultSansSerifTypefaceName (const String& newName) { defaultSans = newName; } const MouseCursor LookAndFeel::getMouseCursorFor (Component& component) { return component.getMouseCursor(); } void LookAndFeel::drawButtonBackground (Graphics& g, Button& button, const Colour& backgroundColour, bool isMouseOverButton, bool isButtonDown) { const int width = button.getWidth(); const int height = button.getHeight(); const float outlineThickness = button.isEnabled() ? ((isButtonDown || isMouseOverButton) ? 1.2f : 0.7f) : 0.4f; const float halfThickness = outlineThickness * 0.5f; const float indentL = button.isConnectedOnLeft() ? 0.1f : halfThickness; const float indentR = button.isConnectedOnRight() ? 0.1f : halfThickness; const float indentT = button.isConnectedOnTop() ? 0.1f : halfThickness; const float indentB = button.isConnectedOnBottom() ? 0.1f : halfThickness; const Colour baseColour (createBaseColour (backgroundColour, button.hasKeyboardFocus (true), isMouseOverButton, isButtonDown) .withMultipliedAlpha (button.isEnabled() ? 1.0f : 0.5f)); drawGlassLozenge (g, indentL, indentT, width - indentL - indentR, height - indentT - indentB, baseColour, outlineThickness, -1.0f, button.isConnectedOnLeft(), button.isConnectedOnRight(), button.isConnectedOnTop(), button.isConnectedOnBottom()); } const Font LookAndFeel::getFontForTextButton (TextButton& button) { return button.getFont(); } void LookAndFeel::drawButtonText (Graphics& g, TextButton& button, bool /*isMouseOverButton*/, bool /*isButtonDown*/) { Font font (getFontForTextButton (button)); g.setFont (font); g.setColour (button.findColour (button.getToggleState() ? TextButton::textColourOnId : TextButton::textColourOffId) .withMultipliedAlpha (button.isEnabled() ? 1.0f : 0.5f)); const int yIndent = jmin (4, button.proportionOfHeight (0.3f)); const int cornerSize = jmin (button.getHeight(), button.getWidth()) / 2; const int fontHeight = roundToInt (font.getHeight() * 0.6f); const int leftIndent = jmin (fontHeight, 2 + cornerSize / (button.isConnectedOnLeft() ? 4 : 2)); const int rightIndent = jmin (fontHeight, 2 + cornerSize / (button.isConnectedOnRight() ? 4 : 2)); g.drawFittedText (button.getButtonText(), leftIndent, yIndent, button.getWidth() - leftIndent - rightIndent, button.getHeight() - yIndent * 2, Justification::centred, 2); } void LookAndFeel::drawTickBox (Graphics& g, Component& component, float x, float y, float w, float h, const bool ticked, const bool isEnabled, const bool isMouseOverButton, const bool isButtonDown) { const float boxSize = w * 0.7f; drawGlassSphere (g, x, y + (h - boxSize) * 0.5f, boxSize, createBaseColour (component.findColour (TextButton::buttonColourId) .withMultipliedAlpha (isEnabled ? 1.0f : 0.5f), true, isMouseOverButton, isButtonDown), isEnabled ? ((isButtonDown || isMouseOverButton) ? 1.1f : 0.5f) : 0.3f); if (ticked) { Path tick; tick.startNewSubPath (1.5f, 3.0f); tick.lineTo (3.0f, 6.0f); tick.lineTo (6.0f, 0.0f); g.setColour (isEnabled ? Colours::black : Colours::grey); const AffineTransform trans (AffineTransform::scale (w / 9.0f, h / 9.0f) .translated (x, y)); g.strokePath (tick, PathStrokeType (2.5f), trans); } } void LookAndFeel::drawToggleButton (Graphics& g, ToggleButton& button, bool isMouseOverButton, bool isButtonDown) { if (button.hasKeyboardFocus (true)) { g.setColour (button.findColour (TextEditor::focusedOutlineColourId)); g.drawRect (0, 0, button.getWidth(), button.getHeight()); } float fontSize = jmin (15.0f, button.getHeight() * 0.75f); const float tickWidth = fontSize * 1.1f; drawTickBox (g, button, 4.0f, (button.getHeight() - tickWidth) * 0.5f, tickWidth, tickWidth, button.getToggleState(), button.isEnabled(), isMouseOverButton, isButtonDown); g.setColour (button.findColour (ToggleButton::textColourId)); g.setFont (fontSize); if (! button.isEnabled()) g.setOpacity (0.5f); const int textX = (int) tickWidth + 5; g.drawFittedText (button.getButtonText(), textX, 0, button.getWidth() - textX - 2, button.getHeight(), Justification::centredLeft, 10); } void LookAndFeel::changeToggleButtonWidthToFitText (ToggleButton& button) { Font font (jmin (15.0f, button.getHeight() * 0.6f)); const int tickWidth = jmin (24, button.getHeight()); button.setSize (font.getStringWidth (button.getButtonText()) + tickWidth + 8, button.getHeight()); } AlertWindow* LookAndFeel::createAlertWindow (const String& title, const String& message, const String& button1, const String& button2, const String& button3, AlertWindow::AlertIconType iconType, int numButtons, Component* associatedComponent) { AlertWindow* aw = new AlertWindow (title, message, iconType, associatedComponent); if (numButtons == 1) { aw->addButton (button1, 0, KeyPress (KeyPress::escapeKey, 0, 0), KeyPress (KeyPress::returnKey, 0, 0)); } else { const KeyPress button1ShortCut (CharacterFunctions::toLowerCase (button1[0]), 0, 0); KeyPress button2ShortCut (CharacterFunctions::toLowerCase (button2[0]), 0, 0); if (button1ShortCut == button2ShortCut) button2ShortCut = KeyPress(); if (numButtons == 2) { aw->addButton (button1, 1, KeyPress (KeyPress::returnKey, 0, 0), button1ShortCut); aw->addButton (button2, 0, KeyPress (KeyPress::escapeKey, 0, 0), button2ShortCut); } else if (numButtons == 3) { aw->addButton (button1, 1, button1ShortCut); aw->addButton (button2, 2, button2ShortCut); aw->addButton (button3, 0, KeyPress (KeyPress::escapeKey, 0, 0)); } } return aw; } void LookAndFeel::drawAlertBox (Graphics& g, AlertWindow& alert, const Rectangle& textArea, TextLayout& textLayout) { g.fillAll (alert.findColour (AlertWindow::backgroundColourId)); int iconSpaceUsed = 0; Justification alignment (Justification::horizontallyCentred); const int iconWidth = 80; int iconSize = jmin (iconWidth + 50, alert.getHeight() + 20); if (alert.containsAnyExtraComponents() || alert.getNumButtons() > 2) iconSize = jmin (iconSize, textArea.getHeight() + 50); const Rectangle iconRect (iconSize / -10, iconSize / -10, iconSize, iconSize); if (alert.getAlertType() != AlertWindow::NoIcon) { Path icon; uint32 colour; char character; if (alert.getAlertType() == AlertWindow::WarningIcon) { colour = 0x55ff5555; character = '!'; icon.addTriangle (iconRect.getX() + iconRect.getWidth() * 0.5f, (float) iconRect.getY(), (float) iconRect.getRight(), (float) iconRect.getBottom(), (float) iconRect.getX(), (float) iconRect.getBottom()); icon = icon.createPathWithRoundedCorners (5.0f); } else { colour = alert.getAlertType() == AlertWindow::InfoIcon ? 0x605555ff : 0x40b69900; character = alert.getAlertType() == AlertWindow::InfoIcon ? 'i' : '?'; icon.addEllipse ((float) iconRect.getX(), (float) iconRect.getY(), (float) iconRect.getWidth(), (float) iconRect.getHeight()); } GlyphArrangement ga; ga.addFittedText (Font (iconRect.getHeight() * 0.9f, Font::bold), String::charToString (character), (float) iconRect.getX(), (float) iconRect.getY(), (float) iconRect.getWidth(), (float) iconRect.getHeight(), Justification::centred, false); ga.createPath (icon); icon.setUsingNonZeroWinding (false); g.setColour (Colour (colour)); g.fillPath (icon); iconSpaceUsed = iconWidth; alignment = Justification::left; } g.setColour (alert.findColour (AlertWindow::textColourId)); textLayout.drawWithin (g, textArea.getX() + iconSpaceUsed, textArea.getY(), textArea.getWidth() - iconSpaceUsed, textArea.getHeight(), alignment.getFlags() | Justification::top); g.setColour (alert.findColour (AlertWindow::outlineColourId)); g.drawRect (0, 0, alert.getWidth(), alert.getHeight()); } int LookAndFeel::getAlertBoxWindowFlags() { return ComponentPeer::windowAppearsOnTaskbar | ComponentPeer::windowHasDropShadow; } int LookAndFeel::getAlertWindowButtonHeight() { return 28; } const Font LookAndFeel::getAlertWindowFont() { return Font (12.0f); } void LookAndFeel::drawProgressBar (Graphics& g, ProgressBar& progressBar, int width, int height, double progress, const String& textToShow) { const Colour background (progressBar.findColour (ProgressBar::backgroundColourId)); const Colour foreground (progressBar.findColour (ProgressBar::foregroundColourId)); g.fillAll (background); if (progress >= 0.0f && progress < 1.0f) { drawGlassLozenge (g, 1.0f, 1.0f, (float) jlimit (0.0, width - 2.0, progress * (width - 2.0)), (float) (height - 2), foreground, 0.5f, 0.0f, true, true, true, true); } else { // spinning bar.. g.setColour (foreground); const int stripeWidth = height * 2; const int position = (Time::getMillisecondCounter() / 15) % stripeWidth; Path p; for (float x = (float) (- position); x < width + stripeWidth; x += stripeWidth) p.addQuadrilateral (x, 0.0f, x + stripeWidth * 0.5f, 0.0f, x, (float) height, x - stripeWidth * 0.5f, (float) height); Image im (Image::ARGB, width, height, true); { Graphics g2 (im); drawGlassLozenge (g2, 1.0f, 1.0f, (float) (width - 2), (float) (height - 2), foreground, 0.5f, 0.0f, true, true, true, true); } g.setTiledImageFill (im, 0, 0, 0.85f); g.fillPath (p); } if (textToShow.isNotEmpty()) { g.setColour (Colour::contrasting (background, foreground)); g.setFont (height * 0.6f); g.drawText (textToShow, 0, 0, width, height, Justification::centred, false); } } void LookAndFeel::drawSpinningWaitAnimation (Graphics& g, const Colour& colour, int x, int y, int w, int h) { const float radius = jmin (w, h) * 0.4f; const float thickness = radius * 0.15f; Path p; p.addRoundedRectangle (radius * 0.4f, thickness * -0.5f, radius * 0.6f, thickness, thickness * 0.5f); const float cx = x + w * 0.5f; const float cy = y + h * 0.5f; const uint32 animationIndex = (Time::getMillisecondCounter() / (1000 / 10)) % 12; for (int i = 0; i < 12; ++i) { const int n = (i + 12 - animationIndex) % 12; g.setColour (colour.withMultipliedAlpha ((n + 1) / 12.0f)); g.fillPath (p, AffineTransform::rotation (i * (float_Pi / 6.0f)) .translated (cx, cy)); } } void LookAndFeel::drawScrollbarButton (Graphics& g, ScrollBar& scrollbar, int width, int height, int buttonDirection, bool /*isScrollbarVertical*/, bool /*isMouseOverButton*/, bool isButtonDown) { Path p; if (buttonDirection == 0) p.addTriangle (width * 0.5f, height * 0.2f, width * 0.1f, height * 0.7f, width * 0.9f, height * 0.7f); else if (buttonDirection == 1) p.addTriangle (width * 0.8f, height * 0.5f, width * 0.3f, height * 0.1f, width * 0.3f, height * 0.9f); else if (buttonDirection == 2) p.addTriangle (width * 0.5f, height * 0.8f, width * 0.1f, height * 0.3f, width * 0.9f, height * 0.3f); else if (buttonDirection == 3) p.addTriangle (width * 0.2f, height * 0.5f, width * 0.7f, height * 0.1f, width * 0.7f, height * 0.9f); if (isButtonDown) g.setColour (scrollbar.findColour (ScrollBar::thumbColourId).contrasting (0.2f)); else g.setColour (scrollbar.findColour (ScrollBar::thumbColourId)); g.fillPath (p); g.setColour (Colour (0x80000000)); g.strokePath (p, PathStrokeType (0.5f)); } void LookAndFeel::drawScrollbar (Graphics& g, ScrollBar& scrollbar, int x, int y, int width, int height, bool isScrollbarVertical, int thumbStartPosition, int thumbSize, bool /*isMouseOver*/, bool /*isMouseDown*/) { g.fillAll (scrollbar.findColour (ScrollBar::backgroundColourId)); Path slotPath, thumbPath; const float slotIndent = jmin (width, height) > 15 ? 1.0f : 0.0f; const float slotIndentx2 = slotIndent * 2.0f; const float thumbIndent = slotIndent + 1.0f; const float thumbIndentx2 = thumbIndent * 2.0f; float gx1 = 0.0f, gy1 = 0.0f, gx2 = 0.0f, gy2 = 0.0f; if (isScrollbarVertical) { slotPath.addRoundedRectangle (x + slotIndent, y + slotIndent, width - slotIndentx2, height - slotIndentx2, (width - slotIndentx2) * 0.5f); if (thumbSize > 0) thumbPath.addRoundedRectangle (x + thumbIndent, thumbStartPosition + thumbIndent, width - thumbIndentx2, thumbSize - thumbIndentx2, (width - thumbIndentx2) * 0.5f); gx1 = (float) x; gx2 = x + width * 0.7f; } else { slotPath.addRoundedRectangle (x + slotIndent, y + slotIndent, width - slotIndentx2, height - slotIndentx2, (height - slotIndentx2) * 0.5f); if (thumbSize > 0) thumbPath.addRoundedRectangle (thumbStartPosition + thumbIndent, y + thumbIndent, thumbSize - thumbIndentx2, height - thumbIndentx2, (height - thumbIndentx2) * 0.5f); gy1 = (float) y; gy2 = y + height * 0.7f; } const Colour thumbColour (scrollbar.findColour (ScrollBar::thumbColourId)); g.setGradientFill (ColourGradient (thumbColour.overlaidWith (Colour (0x44000000)), gx1, gy1, thumbColour.overlaidWith (Colour (0x19000000)), gx2, gy2, false)); g.fillPath (slotPath); if (isScrollbarVertical) { gx1 = x + width * 0.6f; gx2 = (float) x + width; } else { gy1 = y + height * 0.6f; gy2 = (float) y + height; } g.setGradientFill (ColourGradient (Colours::transparentBlack,gx1, gy1, Colour (0x19000000), gx2, gy2, false)); g.fillPath (slotPath); g.setColour (thumbColour); g.fillPath (thumbPath); g.setGradientFill (ColourGradient (Colour (0x10000000), gx1, gy1, Colours::transparentBlack, gx2, gy2, false)); g.saveState(); if (isScrollbarVertical) g.reduceClipRegion (x + width / 2, y, width, height); else g.reduceClipRegion (x, y + height / 2, width, height); g.fillPath (thumbPath); g.restoreState(); g.setColour (Colour (0x4c000000)); g.strokePath (thumbPath, PathStrokeType (0.4f)); } ImageEffectFilter* LookAndFeel::getScrollbarEffect() { return 0; } int LookAndFeel::getMinimumScrollbarThumbSize (ScrollBar& scrollbar) { return jmin (scrollbar.getWidth(), scrollbar.getHeight()) * 2; } int LookAndFeel::getDefaultScrollbarWidth() { return 18; } int LookAndFeel::getScrollbarButtonSize (ScrollBar& scrollbar) { return 2 + (scrollbar.isVertical() ? scrollbar.getWidth() : scrollbar.getHeight()); } const Path LookAndFeel::getTickShape (const float height) { static const unsigned char tickShapeData[] = { 109,0,224,168,68,0,0,119,67,108,0,224,172,68,0,128,146,67,113,0,192,148,68,0,0,219,67,0,96,110,68,0,224,56,68,113,0,64,51,68,0,32,130,68,0,64,20,68,0,224, 162,68,108,0,128,3,68,0,128,168,68,113,0,128,221,67,0,192,175,68,0,0,207,67,0,32,179,68,113,0,0,201,67,0,224,173,68,0,0,181,67,0,224,161,68,108,0,128,168,67, 0,128,154,68,113,0,128,141,67,0,192,138,68,0,128,108,67,0,64,131,68,113,0,0,62,67,0,128,119,68,0,0,5,67,0,128,114,68,113,0,0,102,67,0,192,88,68,0,128,155, 67,0,192,88,68,113,0,0,190,67,0,192,88,68,0,128,232,67,0,224,131,68,108,0,128,246,67,0,192,139,68,113,0,64,33,68,0,128,87,68,0,0,93,68,0,224,26,68,113,0, 96,140,68,0,128,188,67,0,224,168,68,0,0,119,67,99,101 }; Path p; p.loadPathFromData (tickShapeData, sizeof (tickShapeData)); p.scaleToFit (0, 0, height * 2.0f, height, true); return p; } const Path LookAndFeel::getCrossShape (const float height) { static const unsigned char crossShapeData[] = { 109,0,0,17,68,0,96,145,68,108,0,192,13,68,0,192,147,68,113,0,0,213,67,0,64,174,68,0,0,168,67,0,64,174,68,113,0,0,104,67,0,64,174,68,0,0,5,67,0,64, 153,68,113,0,0,18,67,0,64,153,68,0,0,24,67,0,64,153,68,113,0,0,135,67,0,64,153,68,0,128,207,67,0,224,130,68,108,0,0,220,67,0,0,126,68,108,0,0,204,67, 0,128,117,68,113,0,0,138,67,0,64,82,68,0,0,138,67,0,192,57,68,113,0,0,138,67,0,192,37,68,0,128,210,67,0,64,10,68,113,0,128,220,67,0,64,45,68,0,0,8, 68,0,128,78,68,108,0,192,14,68,0,0,87,68,108,0,64,20,68,0,0,80,68,113,0,192,57,68,0,0,32,68,0,128,88,68,0,0,32,68,113,0,64,112,68,0,0,32,68,0, 128,124,68,0,64,68,68,113,0,0,121,68,0,192,67,68,0,128,119,68,0,192,67,68,113,0,192,108,68,0,192,67,68,0,32,89,68,0,96,82,68,113,0,128,69,68,0,0,97,68, 0,0,56,68,0,64,115,68,108,0,64,49,68,0,128,124,68,108,0,192,55,68,0,96,129,68,113,0,0,92,68,0,224,146,68,0,192,129,68,0,224,146,68,113,0,64,110,68,0,64, 168,68,0,64,87,68,0,64,168,68,113,0,128,66,68,0,64,168,68,0,64,27,68,0,32,150,68,99,101 }; Path p; p.loadPathFromData (crossShapeData, sizeof (crossShapeData)); p.scaleToFit (0, 0, height * 2.0f, height, true); return p; } void LookAndFeel::drawTreeviewPlusMinusBox (Graphics& g, int x, int y, int w, int h, bool isPlus, bool /*isMouseOver*/) { const int boxSize = ((jmin (16, w, h) << 1) / 3) | 1; x += (w - boxSize) >> 1; y += (h - boxSize) >> 1; w = boxSize; h = boxSize; g.setColour (Colour (0xe5ffffff)); g.fillRect (x, y, w, h); g.setColour (Colour (0x80000000)); g.drawRect (x, y, w, h); const float size = boxSize / 2 + 1.0f; const float centre = (float) (boxSize / 2); g.fillRect (x + (w - size) * 0.5f, y + centre, size, 1.0f); if (isPlus) g.fillRect (x + centre, y + (h - size) * 0.5f, 1.0f, size); } void LookAndFeel::drawBubble (Graphics& g, float tipX, float tipY, float boxX, float boxY, float boxW, float boxH) { int side = 0; if (tipX < boxX) side = 1; else if (tipX > boxX + boxW) side = 3; else if (tipY > boxY + boxH) side = 2; const float indent = 2.0f; Path p; p.addBubble (boxX + indent, boxY + indent, boxW - indent * 2.0f, boxH - indent * 2.0f, 5.0f, tipX, tipY, side, 0.5f, jmin (15.0f, boxW * 0.3f, boxH * 0.3f)); //xxx need to take comp as param for colour g.setColour (findColour (TooltipWindow::backgroundColourId).withAlpha (0.9f)); g.fillPath (p); //xxx as above g.setColour (findColour (TooltipWindow::textColourId).withAlpha (0.4f)); g.strokePath (p, PathStrokeType (1.33f)); } const Font LookAndFeel::getPopupMenuFont() { return Font (17.0f); } void LookAndFeel::getIdealPopupMenuItemSize (const String& text, const bool isSeparator, int standardMenuItemHeight, int& idealWidth, int& idealHeight) { if (isSeparator) { idealWidth = 50; idealHeight = standardMenuItemHeight > 0 ? standardMenuItemHeight / 2 : 10; } else { Font font (getPopupMenuFont()); if (standardMenuItemHeight > 0 && font.getHeight() > standardMenuItemHeight / 1.3f) font.setHeight (standardMenuItemHeight / 1.3f); idealHeight = standardMenuItemHeight > 0 ? standardMenuItemHeight : roundToInt (font.getHeight() * 1.3f); idealWidth = font.getStringWidth (text) + idealHeight * 2; } } void LookAndFeel::drawPopupMenuBackground (Graphics& g, int width, int height) { const Colour background (findColour (PopupMenu::backgroundColourId)); g.fillAll (background); g.setColour (background.overlaidWith (Colour (0x2badd8e6))); for (int i = 0; i < height; i += 3) g.fillRect (0, i, width, 1); #if ! JUCE_MAC g.setColour (findColour (PopupMenu::textColourId).withAlpha (0.6f)); g.drawRect (0, 0, width, height); #endif } void LookAndFeel::drawPopupMenuUpDownArrow (Graphics& g, int width, int height, bool isScrollUpArrow) { const Colour background (findColour (PopupMenu::backgroundColourId)); g.setGradientFill (ColourGradient (background, 0.0f, height * 0.5f, background.withAlpha (0.0f), 0.0f, isScrollUpArrow ? ((float) height) : 0.0f, false)); g.fillRect (1, 1, width - 2, height - 2); const float hw = width * 0.5f; const float arrowW = height * 0.3f; const float y1 = height * (isScrollUpArrow ? 0.6f : 0.3f); const float y2 = height * (isScrollUpArrow ? 0.3f : 0.6f); Path p; p.addTriangle (hw - arrowW, y1, hw + arrowW, y1, hw, y2); g.setColour (findColour (PopupMenu::textColourId).withAlpha (0.5f)); g.fillPath (p); } void LookAndFeel::drawPopupMenuItem (Graphics& g, int width, int height, const bool isSeparator, const bool isActive, const bool isHighlighted, const bool isTicked, const bool hasSubMenu, const String& text, const String& shortcutKeyText, Image* image, const Colour* const textColourToUse) { const float halfH = height * 0.5f; if (isSeparator) { const float separatorIndent = 5.5f; g.setColour (Colour (0x33000000)); g.drawLine (separatorIndent, halfH, width - separatorIndent, halfH); g.setColour (Colour (0x66ffffff)); g.drawLine (separatorIndent, halfH + 1.0f, width - separatorIndent, halfH + 1.0f); } else { Colour textColour (findColour (PopupMenu::textColourId)); if (textColourToUse != 0) textColour = *textColourToUse; if (isHighlighted) { g.setColour (findColour (PopupMenu::highlightedBackgroundColourId)); g.fillRect (1, 1, width - 2, height - 2); g.setColour (findColour (PopupMenu::highlightedTextColourId)); } else { g.setColour (textColour); } if (! isActive) g.setOpacity (0.3f); Font font (getPopupMenuFont()); if (font.getHeight() > height / 1.3f) font.setHeight (height / 1.3f); g.setFont (font); const int leftBorder = (height * 5) / 4; const int rightBorder = 4; if (image != 0) { g.drawImageWithin (*image, 2, 1, leftBorder - 4, height - 2, RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, false); } else if (isTicked) { const Path tick (getTickShape (1.0f)); const float th = font.getAscent(); const float ty = halfH - th * 0.5f; g.fillPath (tick, tick.getTransformToScaleToFit (2.0f, ty, (float) (leftBorder - 4), th, true)); } g.drawFittedText (text, leftBorder, 0, width - (leftBorder + rightBorder), height, Justification::centredLeft, 1); if (shortcutKeyText.isNotEmpty()) { Font f2 (font); f2.setHeight (f2.getHeight() * 0.75f); f2.setHorizontalScale (0.95f); g.setFont (f2); g.drawText (shortcutKeyText, leftBorder, 0, width - (leftBorder + rightBorder + 4), height, Justification::centredRight, true); } if (hasSubMenu) { const float arrowH = 0.6f * getPopupMenuFont().getAscent(); const float x = width - height * 0.6f; Path p; p.addTriangle (x, halfH - arrowH * 0.5f, x, halfH + arrowH * 0.5f, x + arrowH * 0.6f, halfH); g.fillPath (p); } } } int LookAndFeel::getMenuWindowFlags() { return ComponentPeer::windowHasDropShadow; } void LookAndFeel::drawMenuBarBackground (Graphics& g, int width, int height, bool, MenuBarComponent& menuBar) { const Colour baseColour (createBaseColour (menuBar.findColour (PopupMenu::backgroundColourId), false, false, false)); if (menuBar.isEnabled()) { drawShinyButtonShape (g, -4.0f, 0.0f, width + 8.0f, (float) height, 0.0f, baseColour, 0.4f, true, true, true, true); } else { g.fillAll (baseColour); } } const Font LookAndFeel::getMenuBarFont (MenuBarComponent& menuBar, int /*itemIndex*/, const String& /*itemText*/) { return Font (menuBar.getHeight() * 0.7f); } int LookAndFeel::getMenuBarItemWidth (MenuBarComponent& menuBar, int itemIndex, const String& itemText) { return getMenuBarFont (menuBar, itemIndex, itemText) .getStringWidth (itemText) + menuBar.getHeight(); } void LookAndFeel::drawMenuBarItem (Graphics& g, int width, int height, int itemIndex, const String& itemText, bool isMouseOverItem, bool isMenuOpen, bool /*isMouseOverBar*/, MenuBarComponent& menuBar) { if (! menuBar.isEnabled()) { g.setColour (menuBar.findColour (PopupMenu::textColourId) .withMultipliedAlpha (0.5f)); } else if (isMenuOpen || isMouseOverItem) { g.fillAll (menuBar.findColour (PopupMenu::highlightedBackgroundColourId)); g.setColour (menuBar.findColour (PopupMenu::highlightedTextColourId)); } else { g.setColour (menuBar.findColour (PopupMenu::textColourId)); } g.setFont (getMenuBarFont (menuBar, itemIndex, itemText)); g.drawFittedText (itemText, 0, 0, width, height, Justification::centred, 1); } void LookAndFeel::fillTextEditorBackground (Graphics& g, int /*width*/, int /*height*/, TextEditor& textEditor) { g.fillAll (textEditor.findColour (TextEditor::backgroundColourId)); } void LookAndFeel::drawTextEditorOutline (Graphics& g, int width, int height, TextEditor& textEditor) { if (textEditor.isEnabled()) { if (textEditor.hasKeyboardFocus (true) && ! textEditor.isReadOnly()) { const int border = 2; g.setColour (textEditor.findColour (TextEditor::focusedOutlineColourId)); g.drawRect (0, 0, width, height, border); g.setOpacity (1.0f); const Colour shadowColour (textEditor.findColour (TextEditor::shadowColourId).withMultipliedAlpha (0.75f)); g.drawBevel (0, 0, width, height + 2, border + 2, shadowColour, shadowColour); } else { g.setColour (textEditor.findColour (TextEditor::outlineColourId)); g.drawRect (0, 0, width, height); g.setOpacity (1.0f); const Colour shadowColour (textEditor.findColour (TextEditor::shadowColourId)); g.drawBevel (0, 0, width, height + 2, 3, shadowColour, shadowColour); } } } void LookAndFeel::drawComboBox (Graphics& g, int width, int height, const bool isButtonDown, int buttonX, int buttonY, int buttonW, int buttonH, ComboBox& box) { g.fillAll (box.findColour (ComboBox::backgroundColourId)); if (box.isEnabled() && box.hasKeyboardFocus (false)) { g.setColour (box.findColour (TextButton::buttonColourId)); g.drawRect (0, 0, width, height, 2); } else { g.setColour (box.findColour (ComboBox::outlineColourId)); g.drawRect (0, 0, width, height); } const float outlineThickness = box.isEnabled() ? (isButtonDown ? 1.2f : 0.5f) : 0.3f; const Colour baseColour (createBaseColour (box.findColour (ComboBox::buttonColourId), box.hasKeyboardFocus (true), false, isButtonDown) .withMultipliedAlpha (box.isEnabled() ? 1.0f : 0.5f)); drawGlassLozenge (g, buttonX + outlineThickness, buttonY + outlineThickness, buttonW - outlineThickness * 2.0f, buttonH - outlineThickness * 2.0f, baseColour, outlineThickness, -1.0f, true, true, true, true); if (box.isEnabled()) { const float arrowX = 0.3f; const float arrowH = 0.2f; Path p; p.addTriangle (buttonX + buttonW * 0.5f, buttonY + buttonH * (0.45f - arrowH), buttonX + buttonW * (1.0f - arrowX), buttonY + buttonH * 0.45f, buttonX + buttonW * arrowX, buttonY + buttonH * 0.45f); p.addTriangle (buttonX + buttonW * 0.5f, buttonY + buttonH * (0.55f + arrowH), buttonX + buttonW * (1.0f - arrowX), buttonY + buttonH * 0.55f, buttonX + buttonW * arrowX, buttonY + buttonH * 0.55f); g.setColour (box.findColour (ComboBox::arrowColourId)); g.fillPath (p); } } const Font LookAndFeel::getComboBoxFont (ComboBox& box) { return Font (jmin (15.0f, box.getHeight() * 0.85f)); } Label* LookAndFeel::createComboBoxTextBox (ComboBox&) { return new Label (String::empty, String::empty); } void LookAndFeel::positionComboBoxText (ComboBox& box, Label& label) { label.setBounds (1, 1, box.getWidth() + 3 - box.getHeight(), box.getHeight() - 2); label.setFont (getComboBoxFont (box)); } void LookAndFeel::drawLabel (Graphics& g, Label& label) { g.fillAll (label.findColour (Label::backgroundColourId)); if (! label.isBeingEdited()) { const float alpha = label.isEnabled() ? 1.0f : 0.5f; g.setColour (label.findColour (Label::textColourId).withMultipliedAlpha (alpha)); g.setFont (label.getFont()); g.drawFittedText (label.getText(), label.getHorizontalBorderSize(), label.getVerticalBorderSize(), label.getWidth() - 2 * label.getHorizontalBorderSize(), label.getHeight() - 2 * label.getVerticalBorderSize(), label.getJustificationType(), jmax (1, (int) (label.getHeight() / label.getFont().getHeight())), label.getMinimumHorizontalScale()); g.setColour (label.findColour (Label::outlineColourId).withMultipliedAlpha (alpha)); g.drawRect (0, 0, label.getWidth(), label.getHeight()); } else if (label.isEnabled()) { g.setColour (label.findColour (Label::outlineColourId)); g.drawRect (0, 0, label.getWidth(), label.getHeight()); } } void LookAndFeel::drawLinearSliderBackground (Graphics& g, int x, int y, int width, int height, float /*sliderPos*/, float /*minSliderPos*/, float /*maxSliderPos*/, const Slider::SliderStyle /*style*/, Slider& slider) { const float sliderRadius = (float) (getSliderThumbRadius (slider) - 2); const Colour trackColour (slider.findColour (Slider::trackColourId)); const Colour gradCol1 (trackColour.overlaidWith (Colours::black.withAlpha (slider.isEnabled() ? 0.25f : 0.13f))); const Colour gradCol2 (trackColour.overlaidWith (Colour (0x14000000))); Path indent; if (slider.isHorizontal()) { const float iy = y + height * 0.5f - sliderRadius * 0.5f; const float ih = sliderRadius; g.setGradientFill (ColourGradient (gradCol1, 0.0f, iy, gradCol2, 0.0f, iy + ih, false)); indent.addRoundedRectangle (x - sliderRadius * 0.5f, iy, width + sliderRadius, ih, 5.0f); g.fillPath (indent); } else { const float ix = x + width * 0.5f - sliderRadius * 0.5f; const float iw = sliderRadius; g.setGradientFill (ColourGradient (gradCol1, ix, 0.0f, gradCol2, ix + iw, 0.0f, false)); indent.addRoundedRectangle (ix, y - sliderRadius * 0.5f, iw, height + sliderRadius, 5.0f); g.fillPath (indent); } g.setColour (Colour (0x4c000000)); g.strokePath (indent, PathStrokeType (0.5f)); } void LookAndFeel::drawLinearSliderThumb (Graphics& g, int x, int y, int width, int height, float sliderPos, float minSliderPos, float maxSliderPos, const Slider::SliderStyle style, Slider& slider) { const float sliderRadius = (float) (getSliderThumbRadius (slider) - 2); Colour knobColour (createBaseColour (slider.findColour (Slider::thumbColourId), slider.hasKeyboardFocus (false) && slider.isEnabled(), slider.isMouseOverOrDragging() && slider.isEnabled(), slider.isMouseButtonDown() && slider.isEnabled())); const float outlineThickness = slider.isEnabled() ? 0.8f : 0.3f; if (style == Slider::LinearHorizontal || style == Slider::LinearVertical) { float kx, ky; if (style == Slider::LinearVertical) { kx = x + width * 0.5f; ky = sliderPos; } else { kx = sliderPos; ky = y + height * 0.5f; } drawGlassSphere (g, kx - sliderRadius, ky - sliderRadius, sliderRadius * 2.0f, knobColour, outlineThickness); } else { if (style == Slider::ThreeValueVertical) { drawGlassSphere (g, x + width * 0.5f - sliderRadius, sliderPos - sliderRadius, sliderRadius * 2.0f, knobColour, outlineThickness); } else if (style == Slider::ThreeValueHorizontal) { drawGlassSphere (g,sliderPos - sliderRadius, y + height * 0.5f - sliderRadius, sliderRadius * 2.0f, knobColour, outlineThickness); } if (style == Slider::TwoValueVertical || style == Slider::ThreeValueVertical) { const float sr = jmin (sliderRadius, width * 0.4f); drawGlassPointer (g, jmax (0.0f, x + width * 0.5f - sliderRadius * 2.0f), minSliderPos - sliderRadius, sliderRadius * 2.0f, knobColour, outlineThickness, 1); drawGlassPointer (g, jmin (x + width - sliderRadius * 2.0f, x + width * 0.5f), maxSliderPos - sr, sliderRadius * 2.0f, knobColour, outlineThickness, 3); } else if (style == Slider::TwoValueHorizontal || style == Slider::ThreeValueHorizontal) { const float sr = jmin (sliderRadius, height * 0.4f); drawGlassPointer (g, minSliderPos - sr, jmax (0.0f, y + height * 0.5f - sliderRadius * 2.0f), sliderRadius * 2.0f, knobColour, outlineThickness, 2); drawGlassPointer (g, maxSliderPos - sliderRadius, jmin (y + height - sliderRadius * 2.0f, y + height * 0.5f), sliderRadius * 2.0f, knobColour, outlineThickness, 4); } } } void LookAndFeel::drawLinearSlider (Graphics& g, int x, int y, int width, int height, float sliderPos, float minSliderPos, float maxSliderPos, const Slider::SliderStyle style, Slider& slider) { g.fillAll (slider.findColour (Slider::backgroundColourId)); if (style == Slider::LinearBar) { const bool isMouseOver = slider.isMouseOverOrDragging() && slider.isEnabled(); Colour baseColour (createBaseColour (slider.findColour (Slider::thumbColourId) .withMultipliedSaturation (slider.isEnabled() ? 1.0f : 0.5f), false, isMouseOver, isMouseOver || slider.isMouseButtonDown())); drawShinyButtonShape (g, (float) x, (float) y, sliderPos - (float) x, (float) height, 0.0f, baseColour, slider.isEnabled() ? 0.9f : 0.3f, true, true, true, true); } else { drawLinearSliderBackground (g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, style, slider); drawLinearSliderThumb (g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, style, slider); } } int LookAndFeel::getSliderThumbRadius (Slider& slider) { return jmin (7, slider.getHeight() / 2, slider.getWidth() / 2) + 2; } void LookAndFeel::drawRotarySlider (Graphics& g, int x, int y, int width, int height, float sliderPos, const float rotaryStartAngle, const float rotaryEndAngle, Slider& slider) { const float radius = jmin (width / 2, height / 2) - 2.0f; const float centreX = x + width * 0.5f; const float centreY = y + height * 0.5f; const float rx = centreX - radius; const float ry = centreY - radius; const float rw = radius * 2.0f; const float angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle); const bool isMouseOver = slider.isMouseOverOrDragging() && slider.isEnabled(); if (radius > 12.0f) { if (slider.isEnabled()) g.setColour (slider.findColour (Slider::rotarySliderFillColourId).withAlpha (isMouseOver ? 1.0f : 0.7f)); else g.setColour (Colour (0x80808080)); const float thickness = 0.7f; { Path filledArc; filledArc.addPieSegment (rx, ry, rw, rw, rotaryStartAngle, angle, thickness); g.fillPath (filledArc); } if (thickness > 0) { const float innerRadius = radius * 0.2f; Path p; p.addTriangle (-innerRadius, 0.0f, 0.0f, -radius * thickness * 1.1f, innerRadius, 0.0f); p.addEllipse (-innerRadius, -innerRadius, innerRadius * 2.0f, innerRadius * 2.0f); g.fillPath (p, AffineTransform::rotation (angle).translated (centreX, centreY)); } if (slider.isEnabled()) g.setColour (slider.findColour (Slider::rotarySliderOutlineColourId)); else g.setColour (Colour (0x80808080)); Path outlineArc; outlineArc.addPieSegment (rx, ry, rw, rw, rotaryStartAngle, rotaryEndAngle, thickness); outlineArc.closeSubPath(); g.strokePath (outlineArc, PathStrokeType (slider.isEnabled() ? (isMouseOver ? 2.0f : 1.2f) : 0.3f)); } else { if (slider.isEnabled()) g.setColour (slider.findColour (Slider::rotarySliderFillColourId).withAlpha (isMouseOver ? 1.0f : 0.7f)); else g.setColour (Colour (0x80808080)); Path p; p.addEllipse (-0.4f * rw, -0.4f * rw, rw * 0.8f, rw * 0.8f); PathStrokeType (rw * 0.1f).createStrokedPath (p, p); p.addLineSegment (Line (0.0f, 0.0f, 0.0f, -radius), rw * 0.2f); g.fillPath (p, AffineTransform::rotation (angle).translated (centreX, centreY)); } } Button* LookAndFeel::createSliderButton (const bool isIncrement) { return new TextButton (isIncrement ? "+" : "-", String::empty); } class SliderLabelComp : public Label { public: SliderLabelComp() : Label (String::empty, String::empty) {} ~SliderLabelComp() {} void mouseWheelMove (const MouseEvent&, float, float) {} }; Label* LookAndFeel::createSliderTextBox (Slider& slider) { Label* const l = new SliderLabelComp(); l->setJustificationType (Justification::centred); l->setColour (Label::textColourId, slider.findColour (Slider::textBoxTextColourId)); l->setColour (Label::backgroundColourId, (slider.getSliderStyle() == Slider::LinearBar) ? Colours::transparentBlack : slider.findColour (Slider::textBoxBackgroundColourId)); l->setColour (Label::outlineColourId, slider.findColour (Slider::textBoxOutlineColourId)); l->setColour (TextEditor::textColourId, slider.findColour (Slider::textBoxTextColourId)); l->setColour (TextEditor::backgroundColourId, slider.findColour (Slider::textBoxBackgroundColourId) .withAlpha (slider.getSliderStyle() == Slider::LinearBar ? 0.7f : 1.0f)); l->setColour (TextEditor::outlineColourId, slider.findColour (Slider::textBoxOutlineColourId)); return l; } ImageEffectFilter* LookAndFeel::getSliderEffect() { return 0; } static const TextLayout layoutTooltipText (const String& text) throw() { const float tooltipFontSize = 12.0f; const int maxToolTipWidth = 400; const Font f (tooltipFontSize, Font::bold); TextLayout tl (text, f); tl.layout (maxToolTipWidth, Justification::left, true); return tl; } void LookAndFeel::getTooltipSize (const String& tipText, int& width, int& height) { const TextLayout tl (layoutTooltipText (tipText)); width = tl.getWidth() + 14; height = tl.getHeight() + 6; } void LookAndFeel::drawTooltip (Graphics& g, const String& text, int width, int height) { g.fillAll (findColour (TooltipWindow::backgroundColourId)); const Colour textCol (findColour (TooltipWindow::textColourId)); #if ! JUCE_MAC // The mac windows already have a non-optional 1 pix outline, so don't double it here.. g.setColour (findColour (TooltipWindow::outlineColourId)); g.drawRect (0, 0, width, height, 1); #endif const TextLayout tl (layoutTooltipText (text)); g.setColour (findColour (TooltipWindow::textColourId)); tl.drawWithin (g, 0, 0, width, height, Justification::centred); } Button* LookAndFeel::createFilenameComponentBrowseButton (const String& text) { return new TextButton (text, TRANS("click to browse for a different file")); } void LookAndFeel::layoutFilenameComponent (FilenameComponent& filenameComp, ComboBox* filenameBox, Button* browseButton) { browseButton->setSize (80, filenameComp.getHeight()); TextButton* const tb = dynamic_cast (browseButton); if (tb != 0) tb->changeWidthToFitText(); browseButton->setTopRightPosition (filenameComp.getWidth(), 0); filenameBox->setBounds (0, 0, browseButton->getX(), filenameComp.getHeight()); } void LookAndFeel::drawImageButton (Graphics& g, Image* image, int imageX, int imageY, int imageW, int imageH, const Colour& overlayColour, float imageOpacity, ImageButton& button) { if (! button.isEnabled()) imageOpacity *= 0.3f; if (! overlayColour.isOpaque()) { g.setOpacity (imageOpacity); g.drawImage (*image, imageX, imageY, imageW, imageH, 0, 0, image->getWidth(), image->getHeight(), false); } if (! overlayColour.isTransparent()) { g.setColour (overlayColour); g.drawImage (*image, imageX, imageY, imageW, imageH, 0, 0, image->getWidth(), image->getHeight(), true); } } void LookAndFeel::drawCornerResizer (Graphics& g, int w, int h, bool /*isMouseOver*/, bool /*isMouseDragging*/) { const float lineThickness = jmin (w, h) * 0.075f; for (float i = 0.0f; i < 1.0f; i += 0.3f) { g.setColour (Colours::lightgrey); g.drawLine (w * i, h + 1.0f, w + 1.0f, h * i, lineThickness); g.setColour (Colours::darkgrey); g.drawLine (w * i + lineThickness, h + 1.0f, w + 1.0f, h * i + lineThickness, lineThickness); } } void LookAndFeel::drawResizableFrame (Graphics&, int /*w*/, int /*h*/, const BorderSize& /*borders*/) { } void LookAndFeel::fillResizableWindowBackground (Graphics& g, int /*w*/, int /*h*/, const BorderSize& /*border*/, ResizableWindow& window) { g.fillAll (window.getBackgroundColour()); } void LookAndFeel::drawResizableWindowBorder (Graphics& g, int w, int h, const BorderSize& border, ResizableWindow&) { g.setColour (Colour (0x80000000)); g.drawRect (0, 0, w, h); g.setColour (Colour (0x19000000)); g.drawRect (border.getLeft() - 1, border.getTop() - 1, w + 2 - border.getLeftAndRight(), h + 2 - border.getTopAndBottom()); } void LookAndFeel::drawDocumentWindowTitleBar (DocumentWindow& window, Graphics& g, int w, int h, int titleSpaceX, int titleSpaceW, const Image* icon, bool drawTitleTextOnLeft) { const bool isActive = window.isActiveWindow(); g.setGradientFill (ColourGradient (window.getBackgroundColour(), 0.0f, 0.0f, window.getBackgroundColour().contrasting (isActive ? 0.15f : 0.05f), 0.0f, (float) h, false)); g.fillAll(); Font font (h * 0.65f, Font::bold); g.setFont (font); int textW = font.getStringWidth (window.getName()); int iconW = 0; int iconH = 0; if (icon != 0) { iconH = (int) font.getHeight(); iconW = icon->getWidth() * iconH / icon->getHeight() + 4; } textW = jmin (titleSpaceW, textW + iconW); int textX = drawTitleTextOnLeft ? titleSpaceX : jmax (titleSpaceX, (w - textW) / 2); if (textX + textW > titleSpaceX + titleSpaceW) textX = titleSpaceX + titleSpaceW - textW; if (icon != 0) { g.setOpacity (isActive ? 1.0f : 0.6f); g.drawImageWithin (*icon, textX, (h - iconH) / 2, iconW, iconH, RectanglePlacement::centred, false); textX += iconW; textW -= iconW; } if (window.isColourSpecified (DocumentWindow::textColourId) || isColourSpecified (DocumentWindow::textColourId)) g.setColour (findColour (DocumentWindow::textColourId)); else g.setColour (window.getBackgroundColour().contrasting (isActive ? 0.7f : 0.4f)); g.drawText (window.getName(), textX, 0, textW, h, Justification::centredLeft, true); } class GlassWindowButton : public Button { public: GlassWindowButton (const String& name, const Colour& col, const Path& normalShape_, const Path& toggledShape_) throw() : Button (name), colour (col), normalShape (normalShape_), toggledShape (toggledShape_) { } ~GlassWindowButton() { } void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) { float alpha = isMouseOverButton ? (isButtonDown ? 1.0f : 0.8f) : 0.55f; if (! isEnabled()) alpha *= 0.5f; float x = 0, y = 0, diam; if (getWidth() < getHeight()) { diam = (float) getWidth(); y = (getHeight() - getWidth()) * 0.5f; } else { diam = (float) getHeight(); y = (getWidth() - getHeight()) * 0.5f; } x += diam * 0.05f; y += diam * 0.05f; diam *= 0.9f; g.setGradientFill (ColourGradient (Colour::greyLevel (0.9f).withAlpha (alpha), 0, y + diam, Colour::greyLevel (0.6f).withAlpha (alpha), 0, y, false)); g.fillEllipse (x, y, diam, diam); x += 2.0f; y += 2.0f; diam -= 4.0f; LookAndFeel::drawGlassSphere (g, x, y, diam, colour.withAlpha (alpha), 1.0f); Path& p = getToggleState() ? toggledShape : normalShape; const AffineTransform t (p.getTransformToScaleToFit (x + diam * 0.3f, y + diam * 0.3f, diam * 0.4f, diam * 0.4f, true)); g.setColour (Colours::black.withAlpha (alpha * 0.6f)); g.fillPath (p, t); } juce_UseDebuggingNewOperator private: Colour colour; Path normalShape, toggledShape; GlassWindowButton (const GlassWindowButton&); GlassWindowButton& operator= (const GlassWindowButton&); }; Button* LookAndFeel::createDocumentWindowButton (int buttonType) { Path shape; const float crossThickness = 0.25f; if (buttonType == DocumentWindow::closeButton) { shape.addLineSegment (Line (0.0f, 0.0f, 1.0f, 1.0f), crossThickness * 1.4f); shape.addLineSegment (Line (1.0f, 0.0f, 0.0f, 1.0f), crossThickness * 1.4f); return new GlassWindowButton ("close", Colour (0xffdd1100), shape, shape); } else if (buttonType == DocumentWindow::minimiseButton) { shape.addLineSegment (Line (0.0f, 0.5f, 1.0f, 0.5f), crossThickness); return new GlassWindowButton ("minimise", Colour (0xffaa8811), shape, shape); } else if (buttonType == DocumentWindow::maximiseButton) { shape.addLineSegment (Line (0.5f, 0.0f, 0.5f, 1.0f), crossThickness); shape.addLineSegment (Line (0.0f, 0.5f, 1.0f, 0.5f), crossThickness); Path fullscreenShape; fullscreenShape.startNewSubPath (45.0f, 100.0f); fullscreenShape.lineTo (0.0f, 100.0f); fullscreenShape.lineTo (0.0f, 0.0f); fullscreenShape.lineTo (100.0f, 0.0f); fullscreenShape.lineTo (100.0f, 45.0f); fullscreenShape.addRectangle (45.0f, 45.0f, 100.0f, 100.0f); PathStrokeType (30.0f).createStrokedPath (fullscreenShape, fullscreenShape); return new GlassWindowButton ("maximise", Colour (0xff119911), shape, fullscreenShape); } jassertfalse; return 0; } void LookAndFeel::positionDocumentWindowButtons (DocumentWindow&, int titleBarX, int titleBarY, int titleBarW, int titleBarH, Button* minimiseButton, Button* maximiseButton, Button* closeButton, bool positionTitleBarButtonsOnLeft) { const int buttonW = titleBarH - titleBarH / 8; int x = positionTitleBarButtonsOnLeft ? titleBarX + 4 : titleBarX + titleBarW - buttonW - buttonW / 4; if (closeButton != 0) { closeButton->setBounds (x, titleBarY, buttonW, titleBarH); x += positionTitleBarButtonsOnLeft ? buttonW : -(buttonW + buttonW / 4); } if (positionTitleBarButtonsOnLeft) swapVariables (minimiseButton, maximiseButton); if (maximiseButton != 0) { maximiseButton->setBounds (x, titleBarY, buttonW, titleBarH); x += positionTitleBarButtonsOnLeft ? buttonW : -buttonW; } if (minimiseButton != 0) minimiseButton->setBounds (x, titleBarY, buttonW, titleBarH); } int LookAndFeel::getDefaultMenuBarHeight() { return 24; } DropShadower* LookAndFeel::createDropShadowerForComponent (Component*) { return new DropShadower (0.4f, 1, 5, 10); } void LookAndFeel::drawStretchableLayoutResizerBar (Graphics& g, int w, int h, bool /*isVerticalBar*/, bool isMouseOver, bool isMouseDragging) { float alpha = 0.5f; if (isMouseOver || isMouseDragging) { g.fillAll (Colour (0x190000ff)); alpha = 1.0f; } const float cx = w * 0.5f; const float cy = h * 0.5f; const float cr = jmin (w, h) * 0.4f; g.setGradientFill (ColourGradient (Colours::white.withAlpha (alpha), cx + cr * 0.1f, cy + cr, Colours::black.withAlpha (alpha), cx, cy - cr * 4.0f, true)); g.fillEllipse (cx - cr, cy - cr, cr * 2.0f, cr * 2.0f); } void LookAndFeel::drawGroupComponentOutline (Graphics& g, int width, int height, const String& text, const Justification& position, GroupComponent& group) { const float textH = 15.0f; const float indent = 3.0f; const float textEdgeGap = 4.0f; float cs = 5.0f; Font f (textH); Path p; float x = indent; float y = f.getAscent() - 3.0f; float w = jmax (0.0f, width - x * 2.0f); float h = jmax (0.0f, height - y - indent); cs = jmin (cs, w * 0.5f, h * 0.5f); const float cs2 = 2.0f * cs; float textW = text.isEmpty() ? 0 : jlimit (0.0f, jmax (0.0f, w - cs2 - textEdgeGap * 2), f.getStringWidth (text) + textEdgeGap * 2.0f); float textX = cs + textEdgeGap; if (position.testFlags (Justification::horizontallyCentred)) textX = cs + (w - cs2 - textW) * 0.5f; else if (position.testFlags (Justification::right)) textX = w - cs - textW - textEdgeGap; p.startNewSubPath (x + textX + textW, y); p.lineTo (x + w - cs, y); p.addArc (x + w - cs2, y, cs2, cs2, 0, float_Pi * 0.5f); p.lineTo (x + w, y + h - cs); p.addArc (x + w - cs2, y + h - cs2, cs2, cs2, float_Pi * 0.5f, float_Pi); p.lineTo (x + cs, y + h); p.addArc (x, y + h - cs2, cs2, cs2, float_Pi, float_Pi * 1.5f); p.lineTo (x, y + cs); p.addArc (x, y, cs2, cs2, float_Pi * 1.5f, float_Pi * 2.0f); p.lineTo (x + textX, y); const float alpha = group.isEnabled() ? 1.0f : 0.5f; g.setColour (group.findColour (GroupComponent::outlineColourId) .withMultipliedAlpha (alpha)); g.strokePath (p, PathStrokeType (2.0f)); g.setColour (group.findColour (GroupComponent::textColourId) .withMultipliedAlpha (alpha)); g.setFont (f); g.drawText (text, roundToInt (x + textX), 0, roundToInt (textW), roundToInt (textH), Justification::centred, true); } int LookAndFeel::getTabButtonOverlap (int tabDepth) { return 1 + tabDepth / 3; } int LookAndFeel::getTabButtonSpaceAroundImage() { return 4; } void LookAndFeel::createTabButtonShape (Path& p, int width, int height, int /*tabIndex*/, const String& /*text*/, Button& /*button*/, TabbedButtonBar::Orientation orientation, const bool /*isMouseOver*/, const bool /*isMouseDown*/, const bool /*isFrontTab*/) { const float w = (float) width; const float h = (float) height; float length = w; float depth = h; if (orientation == TabbedButtonBar::TabsAtLeft || orientation == TabbedButtonBar::TabsAtRight) { swapVariables (length, depth); } const float indent = (float) getTabButtonOverlap ((int) depth); const float overhang = 4.0f; if (orientation == TabbedButtonBar::TabsAtLeft) { p.startNewSubPath (w, 0.0f); p.lineTo (0.0f, indent); p.lineTo (0.0f, h - indent); p.lineTo (w, h); p.lineTo (w + overhang, h + overhang); p.lineTo (w + overhang, -overhang); } else if (orientation == TabbedButtonBar::TabsAtRight) { p.startNewSubPath (0.0f, 0.0f); p.lineTo (w, indent); p.lineTo (w, h - indent); p.lineTo (0.0f, h); p.lineTo (-overhang, h + overhang); p.lineTo (-overhang, -overhang); } else if (orientation == TabbedButtonBar::TabsAtBottom) { p.startNewSubPath (0.0f, 0.0f); p.lineTo (indent, h); p.lineTo (w - indent, h); p.lineTo (w, 0.0f); p.lineTo (w + overhang, -overhang); p.lineTo (-overhang, -overhang); } else { p.startNewSubPath (0.0f, h); p.lineTo (indent, 0.0f); p.lineTo (w - indent, 0.0f); p.lineTo (w, h); p.lineTo (w + overhang, h + overhang); p.lineTo (-overhang, h + overhang); } p.closeSubPath(); p = p.createPathWithRoundedCorners (3.0f); } void LookAndFeel::fillTabButtonShape (Graphics& g, const Path& path, const Colour& preferredColour, int /*tabIndex*/, const String& /*text*/, Button& button, TabbedButtonBar::Orientation /*orientation*/, const bool /*isMouseOver*/, const bool /*isMouseDown*/, const bool isFrontTab) { g.setColour (isFrontTab ? preferredColour : preferredColour.withMultipliedAlpha (0.9f)); g.fillPath (path); g.setColour (button.findColour (isFrontTab ? TabbedButtonBar::frontOutlineColourId : TabbedButtonBar::tabOutlineColourId, false) .withMultipliedAlpha (button.isEnabled() ? 1.0f : 0.5f)); g.strokePath (path, PathStrokeType (isFrontTab ? 1.0f : 0.5f)); } void LookAndFeel::drawTabButtonText (Graphics& g, int x, int y, int w, int h, const Colour& preferredBackgroundColour, int /*tabIndex*/, const String& text, Button& button, TabbedButtonBar::Orientation orientation, const bool isMouseOver, const bool isMouseDown, const bool isFrontTab) { int length = w; int depth = h; if (orientation == TabbedButtonBar::TabsAtLeft || orientation == TabbedButtonBar::TabsAtRight) { swapVariables (length, depth); } Font font (depth * 0.6f); font.setUnderline (button.hasKeyboardFocus (false)); GlyphArrangement textLayout; textLayout.addFittedText (font, text.trim(), 0.0f, 0.0f, (float) length, (float) depth, Justification::centred, jmax (1, depth / 12)); AffineTransform transform; if (orientation == TabbedButtonBar::TabsAtLeft) { transform = transform.rotated (float_Pi * -0.5f) .translated ((float) x, (float) (y + h)); } else if (orientation == TabbedButtonBar::TabsAtRight) { transform = transform.rotated (float_Pi * 0.5f) .translated ((float) (x + w), (float) y); } else { transform = transform.translated ((float) x, (float) y); } if (isFrontTab && (button.isColourSpecified (TabbedButtonBar::frontTextColourId) || isColourSpecified (TabbedButtonBar::frontTextColourId))) g.setColour (findColour (TabbedButtonBar::frontTextColourId)); else if (button.isColourSpecified (TabbedButtonBar::tabTextColourId) || isColourSpecified (TabbedButtonBar::tabTextColourId)) g.setColour (findColour (TabbedButtonBar::tabTextColourId)); else g.setColour (preferredBackgroundColour.contrasting()); if (! (isMouseOver || isMouseDown)) g.setOpacity (0.8f); if (! button.isEnabled()) g.setOpacity (0.3f); textLayout.draw (g, transform); } int LookAndFeel::getTabButtonBestWidth (int /*tabIndex*/, const String& text, int tabDepth, Button&) { Font f (tabDepth * 0.6f); return f.getStringWidth (text.trim()) + getTabButtonOverlap (tabDepth) * 2; } void LookAndFeel::drawTabButton (Graphics& g, int w, int h, const Colour& preferredColour, int tabIndex, const String& text, Button& button, TabbedButtonBar::Orientation orientation, const bool isMouseOver, const bool isMouseDown, const bool isFrontTab) { int length = w; int depth = h; if (orientation == TabbedButtonBar::TabsAtLeft || orientation == TabbedButtonBar::TabsAtRight) { swapVariables (length, depth); } Path tabShape; createTabButtonShape (tabShape, w, h, tabIndex, text, button, orientation, isMouseOver, isMouseDown, isFrontTab); fillTabButtonShape (g, tabShape, preferredColour, tabIndex, text, button, orientation, isMouseOver, isMouseDown, isFrontTab); const int indent = getTabButtonOverlap (depth); int x = 0, y = 0; if (orientation == TabbedButtonBar::TabsAtLeft || orientation == TabbedButtonBar::TabsAtRight) { y += indent; h -= indent * 2; } else { x += indent; w -= indent * 2; } drawTabButtonText (g, x, y, w, h, preferredColour, tabIndex, text, button, orientation, isMouseOver, isMouseDown, isFrontTab); } void LookAndFeel::drawTabAreaBehindFrontButton (Graphics& g, int w, int h, TabbedButtonBar& tabBar, TabbedButtonBar::Orientation orientation) { const float shadowSize = 0.2f; float x1 = 0.0f, y1 = 0.0f, x2 = 0.0f, y2 = 0.0f; Rectangle shadowRect; if (orientation == TabbedButtonBar::TabsAtLeft) { x1 = (float) w; x2 = w * (1.0f - shadowSize); shadowRect.setBounds ((int) x2, 0, w - (int) x2, h); } else if (orientation == TabbedButtonBar::TabsAtRight) { x2 = w * shadowSize; shadowRect.setBounds (0, 0, (int) x2, h); } else if (orientation == TabbedButtonBar::TabsAtBottom) { y2 = h * shadowSize; shadowRect.setBounds (0, 0, w, (int) y2); } else { y1 = (float) h; y2 = h * (1.0f - shadowSize); shadowRect.setBounds (0, (int) y2, w, h - (int) y2); } g.setGradientFill (ColourGradient (Colours::black.withAlpha (tabBar.isEnabled() ? 0.3f : 0.15f), x1, y1, Colours::transparentBlack, x2, y2, false)); shadowRect.expand (2, 2); g.fillRect (shadowRect); g.setColour (Colour (0x80000000)); if (orientation == TabbedButtonBar::TabsAtLeft) { g.fillRect (w - 1, 0, 1, h); } else if (orientation == TabbedButtonBar::TabsAtRight) { g.fillRect (0, 0, 1, h); } else if (orientation == TabbedButtonBar::TabsAtBottom) { g.fillRect (0, 0, w, 1); } else { g.fillRect (0, h - 1, w, 1); } } Button* LookAndFeel::createTabBarExtrasButton() { const float thickness = 7.0f; const float indent = 22.0f; Path p; p.addEllipse (-10.0f, -10.0f, 120.0f, 120.0f); DrawablePath ellipse; ellipse.setPath (p); ellipse.setFill (Colour (0x99ffffff)); p.clear(); p.addEllipse (0.0f, 0.0f, 100.0f, 100.0f); p.addRectangle (indent, 50.0f - thickness, 100.0f - indent * 2.0f, thickness * 2.0f); p.addRectangle (50.0f - thickness, indent, thickness * 2.0f, 50.0f - indent - thickness); p.addRectangle (50.0f - thickness, 50.0f + thickness, thickness * 2.0f, 50.0f - indent - thickness); p.setUsingNonZeroWinding (false); DrawablePath dp; dp.setPath (p); dp.setFill (Colour (0x59000000)); DrawableComposite normalImage; normalImage.insertDrawable (ellipse); normalImage.insertDrawable (dp); dp.setFill (Colour (0xcc000000)); DrawableComposite overImage; overImage.insertDrawable (ellipse); overImage.insertDrawable (dp); DrawableButton* db = new DrawableButton ("tabs", DrawableButton::ImageFitted); db->setImages (&normalImage, &overImage, 0); return db; } void LookAndFeel::drawTableHeaderBackground (Graphics& g, TableHeaderComponent& header) { g.fillAll (Colours::white); const int w = header.getWidth(); const int h = header.getHeight(); g.setGradientFill (ColourGradient (Colour (0xffe8ebf9), 0.0f, h * 0.5f, Colour (0xfff6f8f9), 0.0f, h - 1.0f, false)); g.fillRect (0, h / 2, w, h); g.setColour (Colour (0x33000000)); g.fillRect (0, h - 1, w, 1); for (int i = header.getNumColumns (true); --i >= 0;) g.fillRect (header.getColumnPosition (i).getRight() - 1, 0, 1, h - 1); } void LookAndFeel::drawTableHeaderColumn (Graphics& g, const String& columnName, int /*columnId*/, int width, int height, bool isMouseOver, bool isMouseDown, int columnFlags) { if (isMouseDown) g.fillAll (Colour (0x8899aadd)); else if (isMouseOver) g.fillAll (Colour (0x5599aadd)); int rightOfText = width - 4; if ((columnFlags & (TableHeaderComponent::sortedForwards | TableHeaderComponent::sortedBackwards)) != 0) { const float top = height * ((columnFlags & TableHeaderComponent::sortedForwards) != 0 ? 0.35f : (1.0f - 0.35f)); const float bottom = height - top; const float w = height * 0.5f; const float x = rightOfText - (w * 1.25f); rightOfText = (int) x; Path sortArrow; sortArrow.addTriangle (x, bottom, x + w * 0.5f, top, x + w, bottom); g.setColour (Colour (0x99000000)); g.fillPath (sortArrow); } g.setColour (Colours::black); g.setFont (height * 0.5f, Font::bold); const int textX = 4; g.drawFittedText (columnName, textX, 0, rightOfText - textX, height, Justification::centredLeft, 1); } void LookAndFeel::paintToolbarBackground (Graphics& g, int w, int h, Toolbar& toolbar) { const Colour background (toolbar.findColour (Toolbar::backgroundColourId)); g.setGradientFill (ColourGradient (background, 0.0f, 0.0f, background.darker (0.1f), toolbar.isVertical() ? w - 1.0f : 0.0f, toolbar.isVertical() ? 0.0f : h - 1.0f, false)); g.fillAll(); } Button* LookAndFeel::createToolbarMissingItemsButton (Toolbar& /*toolbar*/) { return createTabBarExtrasButton(); } void LookAndFeel::paintToolbarButtonBackground (Graphics& g, int /*width*/, int /*height*/, bool isMouseOver, bool isMouseDown, ToolbarItemComponent& component) { if (isMouseDown) g.fillAll (component.findColour (Toolbar::buttonMouseDownBackgroundColourId, true)); else if (isMouseOver) g.fillAll (component.findColour (Toolbar::buttonMouseOverBackgroundColourId, true)); } void LookAndFeel::paintToolbarButtonLabel (Graphics& g, int x, int y, int width, int height, const String& text, ToolbarItemComponent& component) { g.setColour (component.findColour (Toolbar::labelTextColourId, true) .withAlpha (component.isEnabled() ? 1.0f : 0.25f)); const float fontHeight = jmin (14.0f, height * 0.85f); g.setFont (fontHeight); g.drawFittedText (text, x, y, width, height, Justification::centred, jmax (1, height / (int) fontHeight)); } void LookAndFeel::drawPropertyPanelSectionHeader (Graphics& g, const String& name, bool isOpen, int width, int height) { const int buttonSize = (height * 3) / 4; const int buttonIndent = (height - buttonSize) / 2; drawTreeviewPlusMinusBox (g, buttonIndent, buttonIndent, buttonSize, buttonSize, ! isOpen, false); const int textX = buttonIndent * 2 + buttonSize + 2; g.setColour (Colours::black); g.setFont (height * 0.7f, Font::bold); g.drawText (name, textX, 0, width - textX - 4, height, Justification::centredLeft, true); } void LookAndFeel::drawPropertyComponentBackground (Graphics& g, int width, int height, PropertyComponent&) { g.setColour (Colour (0x66ffffff)); g.fillRect (0, 0, width, height - 1); } void LookAndFeel::drawPropertyComponentLabel (Graphics& g, int, int height, PropertyComponent& component) { g.setColour (Colours::black); if (! component.isEnabled()) g.setOpacity (0.6f); g.setFont (jmin (height, 24) * 0.65f); const Rectangle r (getPropertyComponentContentPosition (component)); g.drawFittedText (component.getName(), 3, r.getY(), r.getX() - 5, r.getHeight(), Justification::centredLeft, 2); } const Rectangle LookAndFeel::getPropertyComponentContentPosition (PropertyComponent& component) { return Rectangle (component.getWidth() / 3, 1, component.getWidth() - component.getWidth() / 3 - 1, component.getHeight() - 3); } void LookAndFeel::drawCallOutBoxBackground (CallOutBox& box, Graphics& g, const Path& path) { Image content (Image::ARGB, box.getWidth(), box.getHeight(), true); { Graphics g2 (content); g2.setColour (Colour::greyLevel (0.23f).withAlpha (0.9f)); g2.fillPath (path); g2.setColour (Colours::white.withAlpha (0.8f)); g2.strokePath (path, PathStrokeType (2.0f)); } DropShadowEffect shadow; shadow.setShadowProperties (5.0f, 0.4f, 0, 2); shadow.applyEffect (content, g); } void LookAndFeel::createFileChooserHeaderText (const String& title, const String& instructions, GlyphArrangement& text, int width) { text.clear(); text.addJustifiedText (Font (17.0f, Font::bold), title, 8.0f, 22.0f, width - 16.0f, Justification::centred); text.addJustifiedText (Font (14.0f), instructions, 8.0f, 24.0f + 16.0f, width - 16.0f, Justification::centred); } void LookAndFeel::drawFileBrowserRow (Graphics& g, int width, int height, const String& filename, Image* icon, const String& fileSizeDescription, const String& fileTimeDescription, const bool isDirectory, const bool isItemSelected, const int /*itemIndex*/) { if (isItemSelected) g.fillAll (findColour (DirectoryContentsDisplayComponent::highlightColourId)); g.setColour (findColour (DirectoryContentsDisplayComponent::textColourId)); g.setFont (height * 0.7f); Image im; if (icon != 0) im = *icon; if (im.isNull()) im = isDirectory ? getDefaultFolderImage() : getDefaultDocumentFileImage(); const int x = 32; if (im.isValid()) { g.drawImageWithin (im, 2, 2, x - 4, height - 4, RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, false); } if (width > 450 && ! isDirectory) { const int sizeX = roundToInt (width * 0.7f); const int dateX = roundToInt (width * 0.8f); g.drawFittedText (filename, x, 0, sizeX - x, height, Justification::centredLeft, 1); g.setFont (height * 0.5f); g.setColour (Colours::darkgrey); if (! isDirectory) { g.drawFittedText (fileSizeDescription, sizeX, 0, dateX - sizeX - 8, height, Justification::centredRight, 1); g.drawFittedText (fileTimeDescription, dateX, 0, width - 8 - dateX, height, Justification::centredRight, 1); } } else { g.drawFittedText (filename, x, 0, width - x, height, Justification::centredLeft, 1); } } Button* LookAndFeel::createFileBrowserGoUpButton() { DrawableButton* goUpButton = new DrawableButton ("up", DrawableButton::ImageOnButtonBackground); Path arrowPath; arrowPath.addArrow (Line (50.0f, 100.0f, 50.0f, 0.0f), 40.0f, 100.0f, 50.0f); DrawablePath arrowImage; arrowImage.setFill (Colours::black.withAlpha (0.4f)); arrowImage.setPath (arrowPath); goUpButton->setImages (&arrowImage); return goUpButton; } void LookAndFeel::layoutFileBrowserComponent (FileBrowserComponent& browserComp, DirectoryContentsDisplayComponent* fileListComponent, FilePreviewComponent* previewComp, ComboBox* currentPathBox, TextEditor* filenameBox, Button* goUpButton) { const int x = 8; int w = browserComp.getWidth() - x - x; if (previewComp != 0) { const int previewWidth = w / 3; previewComp->setBounds (x + w - previewWidth, 0, previewWidth, browserComp.getHeight()); w -= previewWidth + 4; } int y = 4; const int controlsHeight = 22; const int bottomSectionHeight = controlsHeight + 8; const int upButtonWidth = 50; currentPathBox->setBounds (x, y, w - upButtonWidth - 6, controlsHeight); goUpButton->setBounds (x + w - upButtonWidth, y, upButtonWidth, controlsHeight); y += controlsHeight + 4; Component* const listAsComp = dynamic_cast (fileListComponent); listAsComp->setBounds (x, y, w, browserComp.getHeight() - y - bottomSectionHeight); y = listAsComp->getBottom() + 4; filenameBox->setBounds (x + 50, y, w - 50, controlsHeight); } const Image LookAndFeel::getDefaultFolderImage() { static const unsigned char foldericon_png[] = { 137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,32,0,0,0,28,8,6,0,0,0,0,194,189,34,0,0,0,4,103,65,77,65,0,0,175,200,55,5, 138,233,0,0,0,25,116,69,88,116,83,111,102,116,119,97,114,101,0,65,100,111,98,101,32,73,109,97,103,101,82,101,97,100,121,113,201,101,60,0,0,9,46,73,68,65,84,120,218,98,252,255,255,63,3,50,240,41,95,192, 197,205,198,32,202,204,202,33,241,254,235,47,133,47,191,24,180,213,164,133,152,69,24,222,44,234,42,77,188,245,31,170,129,145,145,145,1,29,128,164,226,91,86,113,252,248,207,200,171,37,39,204,239,170,43, 254,206,218,88,231,61,62,61,0,1,196,2,149,96,116,200,158,102,194,202,201,227,197,193,206,166,194,204,193,33,195,202,204,38,42,197,197,42,196,193,202,33,240,241,231,15,134,151,95,127,9,2,149,22,0,241,47, 152,230,128,134,245,204,63,191,188,103,83,144,16,16,228,229,102,151,76,239,217,32,199,204,198,169,205,254,159,65,245,203,79,6,169,131,151,30,47,1,42,91,10,196,127,208,236,101,76,235,90,43,101,160,40,242, 19,32,128,64,78,98,52,12,41,149,145,215,52,89,162,38,35,107,39,196,203,203,192,206,194,206,192,197,198,202,192,203,197,198,192,205,193,206,240,252,227,103,134,139,55,175,191,127,243,242,78,219,187,207, 63,215,255,98,23,48,228,227,96,83,98,102,102,85,225,224,228,80,20,224,230,86,226,225,228,150,103,101,97,101,230,227,228,96,224,0,234,191,243,252,5,195,222,19,199,38,191,127,112,161,83,66,199,86,141,131, 149,69,146,133,153,69,137,149,133,89,157,141,131,77,83,140,143,243,219,255,31,159,123,0,2,136,69,90,207,129,157,71,68,42,66,71,73,209,210,81,91,27,24,142,140,12,127,255,253,103,0,185,236,31,3,144,6,50, 148,68,216,25,216,24,117,4,239,11,243,214,49,50,51,84,178,48,114,240,112,177,114,177,240,115,113,49,241,112,112,48,176,179,178,51,176,48,49,3,85,255,99,248,253,247,15,195,247,159,191,25,30,191,126,253, 71,74,76,200,66,75,197,119,138,168,144,160,150,168,0,183,160,152,32,15,175,188,184,32,199,175,191,127,25,214,31,184,120,247,236,209,253,159,0,2,136,133,95,70,93,74,88,80,196,83,69,66,130,149,9,104,219, 151,31,191,193,150,194,146,6,136,102,102,98,100,16,227,231,103,16,23,210,230,101,101,102,100,248,255,143,137,225,223,63,6,6,22,102,38,134,239,191,126,49,220,123,241,134,225,227,247,175,64,7,252,101,96, 97,249,207,192,193,198,200,160,171,34,192,108,165,235,104,42,204,207,101,42,194,199,197,192,199,201,198,192,197,193,202,192,198,202,194,176,247,194,3,134,155,183,110,61,188,127,124,221,19,128,0,92,146, 49,14,64,64,16,69,63,153,85,16,52,18,74,71,112,6,87,119,0,165,160,86,138,32,172,216,29,49,182,84,253,169,94,94,230,127,17,87,133,34,146,174,3,88,126,240,219,164,147,113,31,145,244,152,112,179,211,130, 34,31,203,113,162,233,6,36,49,163,174,74,124,140,60,141,144,165,161,220,228,25,3,24,105,255,17,168,101,1,139,245,188,93,104,251,73,239,235,50,90,189,111,175,0,98,249,254,254,249,175,239,223,190,126,6, 5,27,19,47,90,170,102,0,249,158,129,129,141,133,25,228,20,6,38,38,72,74,7,185,243,243,247,239,12,23,31,60,98,228,231,253,207,144,227,107,206,32,202,199,193,240,249,251,127,134,95,191,255,49,124,249,250, 159,225,237,239,95,12,63,127,1,35,229,31,194,71,32,71,63,123,251,245,223,197,27,183,159,189,187,178,103,61,80,232,59,64,0,177,48,252,5,134,225,255,191,223,126,254,250,13,182,132,1,41,167,176,3,53,128, 188,254,226,253,103,96,212,252,96,120,247,249,203,255,79,223,191,254,255,250,235,199,191,239,63,191,255,87,145,17,100,73,116,181,100,252,249,243,63,195,149,123,223,193,14,132,101,55,96,52,3,125,255,15, 204,254,15,132,160,232,253,13,20,124,248,226,227,223,23,207,30,221,120,119,255,226,109,160,210,31,0,1,196,242,231,219,135,175,140,255,126,190,7,197,37,35,19,34,216,65,248,211,143,111,255,79,223,121,240, 255,211,183,79,76,220,156,172,12,236,204,140,140,252,124,28,140,250,226,82,140,106,82,34,140,124,156,156,12,175,222,253,1,90,4,137,162,63,127,33,161,6,178,242,215,239,255,224,160,255,15,198,12,64,7,48, 128,211,200,253,151,111,254,254,248,240,236,44,80,217,71,80,246,4,8,32,160,31,255,255,100,102,248,243,238,199,159,63,16,221,16,19,128,248,31,195,181,199,207,254,255,253,247,133,49,212,78,27,104,8,11,40, 94,25,184,216,89,129,108,38,70,144,242,183,31,17,105,230,63,148,248,15,97,49,252,248,249,15,20,85,72,105,9,148,187,254,49,220,127,254,242,207,243,75,135,14,128,130,31,84,64,1,4,16,203,247,143,175,127, 48,253,254,246,234,7,48,206,96,137,13,4,64,65,248,234,195,7,6,7,3,57,70,33,46,97,134,111,63,254,50,252,5,250,244,51,216,103,255,192,185,0,150,91,80,44,135,242,127,253,129,164,23,24,96,102,250,207,112, 255,213,219,255,247,31,63,188,251,246,201,173,199,176,2,13,32,128,88,62,188,121,241,243,211,231,207,31,126,2,147,236,63,168,6,144,193,223,190,255,254,207,198,198,192,40,35,44,206,240,252,205,79,6,132, 223,24,224,150,32,251,28,25,128,211,29,19,170,24,51,48,88,111,61,127,206,248,254,245,179,139,192,18,247,219,239,239,95,192,249,9,32,128,88,126,124,249,248,231,203,183,111,159,128,33,240,15,24,68,160,180, 2,204,223,140,12,111,63,127,102,16,228,229,4,6,53,35,195,31,176,119,25,112,3,70,84,55,0,203,50,112,33,134,108,249,103,160,7,159,189,126,253,235,235,227,203,7,255,255,251,247,13,86,63,0,4,16,168,46,248, 199,250,231,243,235,159,191,126,254,248,245,251,47,23,11,51,51,48,184,152,24,94,127,250,248,95,68,136,151,241,243,55,96,208,51,160,218,255,31,139,27,144,197,254,98,201,202,79,223,124,96,120,245,232,250, 185,119,143,174,95,250,243,243,219,119,152,60,64,0,129,2,234,223,183,215,15,95,48,254,255,253,3,146,109,192,229,5,195,135,47,159,25,248,184,121,24,126,0,227,29,88,240,49,252,101,36,14,255,1,90,249,7,156, 222,17,24,24,164,12,207,223,189,99,248,250,252,230,97,96,229,245,2,104,231,111,152,3,0,2,8,228,128,191,15,239,220,120,255,255,223,159,47,160,116,0,42,44,222,124,250,244,239,207,255,63,12,236,108,236,64, 67,65,81,0,52,244,63,113,248,47,52,10,96,14,98,2,230,191,119,223,127,48,60,121,254,248,235,151,55,207,46,1,163,252,35,114,128,1,4,16,40,10,254,191,121,249,252,199,175,159,63,191,254,2,230,45,118,22,22, 134,219,207,94,252,231,224,100,103,250,247,15,148,32,64,85,12,34,14,254,227,72,6,255,225,9,240,63,138,26,46,96,214,189,249,244,37,195,139,167,143,30,124,253,246,253,9,40,245,255,71,202,30,0,1,196,2,226, 0,243,232,159,239,63,127,124,253,11,202,94,64,169,23,31,62,50,138,137,242,49,50,0,211,195,223,255,80,7,252,199,159,6,224,137,145,9,146,231,153,160,165,218,23,96,29,240,244,237,59,134,111,175,31,95,250, 252,230,241,83,244,182,1,64,0,177,192,28,14,76,132,31,128,169,19,88,220,126,253,207,206,198,196,32,38,36,0,244,61,11,176,148,251,139,145,3,208,29,0,178,16,82,228,66,42,174,223,192,26,8,152,162,25,222, 125,248,200,240,242,253,39,134,151,79,238,126,254,242,242,238,177,15,47,30,190,5,215,242,72,0,32,128,224,14,96,254,255,231,61,168,92,123,241,254,253,127,1,62,78,6,78,110,78,134,223,64,195,254,50,98,183, 24,36,12,202,179,224,202,9,88,228,253,132,90,250,246,211,71,134,55,175,94,254,122,255,250,249,247,15,175,159,126,249,251,237,195,135,95,175,110,31,122,117,251,244,49,160,150,111,255,209,218,128,0,1,152, 44,183,21,0,65,32,136,110,247,254,255,243,122,9,187,64,105,174,74,22,138,25,173,80,208,194,188,238,156,151,217,217,15,32,182,197,37,83,201,4,31,243,178,169,232,242,214,224,223,252,103,175,35,85,1,41,129, 228,148,142,8,214,30,32,149,6,161,204,109,182,53,236,184,156,78,142,147,195,153,89,35,198,3,87,166,249,220,227,198,59,218,48,252,223,185,111,30,1,132,228,128,127,31,222,124,248,248,27,24,152,28,60,220, 220,12,44,172,172,224,224,103,5,102,98,144,133,160,236,244,229,231,47,134,239,223,127,49,188,121,251,158,225,241,179,103,12,31,223,189,254,251,227,221,139,55,191,62,188,120,246,235,205,189,59,207,238, 94,58,241,228,254,109,144,101,159,128,248,51,40,9,32,97,80,217,255,15,221,1,0,1,4,143,130,207,159,191,126,252,246,234,213,111,94,126,94,118,73,94,9,198,127,64,223,126,252,246,147,225,243,215,239,12,223, 128,229,198,251,15,239,24,62,189,126,249,227,203,171,135,47,63,189,122,252,228,235,155,199,247,95,63,188,118,227,197,227,123,247,127,255,250,249,30,104,198,7,32,126,11,181,252,7,212,183,160,4,247,7,155, 197,48,0,16,64,112,7,60,121,241,238,189,16,207,15,134,63,63,216,25,95,125,248,198,112,227,241,27,134,15,239,223,50,124,126,245,228,253,143,55,143,158,191,123,116,237,226,171,135,55,175,126,253,252,225, 229,183,47,159,95,254,253,245,227,253,175,159,223,223,193,124,7,181,20,84,105,252,70,143,103,124,0,32,128,224,14,224,102,253,251,81,144,253,223,235,167,207,30,254,124,127,231,252,155,143,175,159,188,250, 246,254,249,125,96,60,62,248,250,233,253,147,119,207,238,221,6,150,214,175,129,106,191,130,18,19,146,133,120,125,72,8,0,4,16,34,27,190,121,112,251,3,211,159,69,143,110,223,229,120,255,232,230,221,215, 79,239,62,4,102,203,207,72,241,9,11,218,63,72,89,137,20,207,98,100,93,16,0,8,32,70,144,1,64,14,168,209,199,7,196,194,160,166,27,212,135,95,96,65,10,173,95,254,34,219,6,51,128,88,7,96,235,21,129,0,64,0, 193,28,192,8,174,53,33,152,1,155,133,184,12,196,165,4,151,133,232,0,32,192,0,151,97,210,163,246,134,208,52,0,0,0,0,73,69,78,68,174,66,96,130,0,0}; return ImageCache::getFromMemory (foldericon_png, sizeof (foldericon_png)); } const Image LookAndFeel::getDefaultDocumentFileImage() { static const unsigned char fileicon_png[] = { 137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,32,0,0,0,32,8,6,0,0,0,115,122,122,244,0,0,0,4,103,65,77,65,0,0,175,200,55,5, 138,233,0,0,0,25,116,69,88,116,83,111,102,116,119,97,114,101,0,65,100,111,98,101,32,73,109,97,103,101,82,101,97,100,121,113,201,101,60,0,0,4,99,73,68,65,84,120,218,98,252,255,255,63,3,12,48,50,50,50,1, 169,127,200,98,148,2,160,153,204,64,243,254,226,146,7,8,32,22,52,203,255,107,233,233,91,76,93,176,184,232,239,239,95,127,24,40,112,8,19,51,203,255,179,23,175,108,1,90,190,28,104,54,43,80,232,207,127,44, 62,3,8,32,6,144,24,84,156,25,132,189,252,3,146,255,83,9,220,127,254,242,134,162,138,170,10,208,92,144,3,152,97,118,33,99,128,0,98,66,114,11,200,1,92,255,254,252,225,32,215,215,32,127,64,240,127,80,60, 50,40,72,136,169,47,95,179,118,130,136,148,140,0,40,80,128,33,193,136,174,7,32,128,144,29,192,8,117,41,59,209,22,66,241,191,255,16,12,244,19,195,63,48,134,240,255,0,9,115,125,93,239,252,130,130,108,168, 249,44,232,102,0,4,16,19,22,62,51,33,11,255,195,44,4,211,255,25,96,16,33,6,117,24,56,226,25,24,202,139,10,75,226,51,115,66,160,105,13,197,17,0,1,196,68,172,79,255,33,91,206,192,192,128,176,22,17,10,200, 234,32,161,240,31,24,10,255,24,152,153,153,184,39,244,247,117,107,234,234,105,131,66,1,154,224,193,0,32,128,240,58,0,22,180,255,144,18,13,40,136,33,113,140,36,255,15,17,26,48,12,81,15,145,255,254,251, 31,131,0,59,171,84,81,73,105,33,208,216,191,200,161,12,16,64,44,248,131,251,63,10,31,198,253,143,38,6,83,7,11,33,228,232,2,123,4,202,226,228,96,151,132,166,49,144,35,126,131,196,0,2,136,5,103,60,51,252, 71,49,12,213,130,255,168,226,232,150,254,255,15,143,6,80,202,3,133,16,200,198,63,127,193,229,17,39,16,127,135,217,7,16,64,88,67,0,28,143,255,25,225,46,135,249,18,155,133,240,178,4,205,145,8,62,52,186, 32,234,152,160,118,194,179,35,64,0,177,96,11,123,144,236,95,104,92,162,228,113,36,11,81,125,140,112,56,186,131,96,226,176,172,137,148,229,193,0,32,128,88,112,167,248,255,112,223,48,34,165,110,6,124,190, 253,143,61,106,192,9,19,73,28,25,0,4,16,206,40,248,251,15,45,104,209,130,21,51,222,145,18,238,127,180,68,8,244,250,95,164,16,66,6,0,1,196,130,45,253,195,12,250,135,53,206,255,195,131,18,213,98,236,81, 243,31,154,11,144,115,8,50,0,8,32,156,81,0,203,227,12,80,223,98,230,4,68,72,96,38,78,84,11,65,9,250,47,146,3,145,1,64,0,97,117,192,95,112,34,68,138,130,255,176,224,251,143,226,51,6,6,68,29,192,136,20, 77,200,69,54,35,3,36,49,255,69,77,132,112,0,16,64,44,56,139,94,36,7,96,102,59,164,108,249,31,181,82,98,64,203,174,255,144,234,142,127,88,146,33,64,0,97,205,134,240,120,67,75,76,136,224,198,140,22,6,44, 142,66,201,41,255,177,231,2,128,0,194,25,5,255,254,161,134,192,127,6,28,229,0,129,242,1,150,56,33,81,138,209,28,96,0,8,32,172,81,0,78,3,104,190,68,182,224,31,146,197,224,56,6,146,140,176,202,135,17,169, 96,130,40,64,56,0,139,93,0,1,132,61,10,64,248,31,106,156,162,199,55,204,65,255,144,178,38,74,84,252,71,51,239,63,246,68,8,16,64,44,216,74,1,88,217,13,203,191,32,1,80,58,7,133,224,127,6,68,114,6,241,65, 81,197,8,101,255,71,114,33,92,237,127,228,52,128,233,2,128,0,98,193,149,3,64,117,193,255,127,255,81,75,191,127,168,5,18,136,255,31,45,161,49,32,151,134,72,252,127,12,216,203,98,128,0,98,193,210,144,135, 248,30,201,242,127,208,252,140,145,27,160,113,206,136,148,197,192,121,159,17,53,184,225,149,17,22,23,0,4,16,11,182,150,237,63,168,207,96,142,248,143,163,72,6,203,253,67,13,61,6,104,14,66,46,17,254,65, 19,40,182,16,0,8,32,22,108,109,235,255,176,234,24,35,79,255,199,222,30,64,81,135,90,35,194,211,4,142,92,0,16,64,88,29,0,107,7,254,251,247,31,53,78,241,54,207,80,29,135,209,96,249,143,189,46,0,8,32,116, 7,252,101,102,103,103,228,103,99,96,248,193,198,137,53,248,49,125,204,128,225,227,255,88,18,54,47,176,25,202,205,195,205,6,109,11,194,149,0,4,16,35,204,85,208,254,27,159,128,176,176,142,166,182,142,21, 48,4,248,129,41,143,13,217,16,70,52,95,147,0,254,0,187,69,95,223,188,122,125,235,206,141,107,7,129,252,247,64,123,193,237,66,128,0,66,118,0,168,189,198,3,196,252,32,135,64,105,54,228,230,19,185,29,100, 168,175,191,0,241,7,32,254,4,196,159,129,246,254,2,73,2,4,16,11,90,72,125,135,210,63,161,138,153,169,212,75,255,15,117,196,15,40,134,119,215,1,2,12,0,187,0,132,247,216,161,197,124,0,0,0,0,73,69,78,68, 174,66,96,130,0,0}; return ImageCache::getFromMemory (fileicon_png, sizeof (fileicon_png)); } void LookAndFeel::playAlertSound() { PlatformUtilities::beep(); } void LookAndFeel::drawLevelMeter (Graphics& g, int width, int height, float level) { g.setColour (Colours::white.withAlpha (0.7f)); g.fillRoundedRectangle (0.0f, 0.0f, (float) width, (float) height, 3.0f); g.setColour (Colours::black.withAlpha (0.2f)); g.drawRoundedRectangle (1.0f, 1.0f, width - 2.0f, height - 2.0f, 3.0f, 1.0f); const int totalBlocks = 7; const int numBlocks = roundToInt (totalBlocks * level); const float w = (width - 6.0f) / (float) totalBlocks; for (int i = 0; i < totalBlocks; ++i) { if (i >= numBlocks) g.setColour (Colours::lightblue.withAlpha (0.6f)); else g.setColour (i < totalBlocks - 1 ? Colours::blue.withAlpha (0.5f) : Colours::red); g.fillRoundedRectangle (3.0f + i * w + w * 0.1f, 3.0f, w * 0.8f, height - 6.0f, w * 0.4f); } } void LookAndFeel::drawKeymapChangeButton (Graphics& g, int width, int height, Button& button, const String& keyDescription) { const Colour textColour (button.findColour (KeyMappingEditorComponent::textColourId, true)); if (keyDescription.isNotEmpty()) { if (button.isEnabled()) { const float alpha = button.isDown() ? 0.3f : (button.isOver() ? 0.15f : 0.08f); g.fillAll (textColour.withAlpha (alpha)); g.setOpacity (0.3f); g.drawBevel (0, 0, width, height, 2); } g.setColour (textColour); g.setFont (height * 0.6f); g.drawFittedText (keyDescription, 3, 0, width - 6, height, Justification::centred, 1); } else { const float thickness = 7.0f; const float indent = 22.0f; Path p; p.addEllipse (0.0f, 0.0f, 100.0f, 100.0f); p.addRectangle (indent, 50.0f - thickness, 100.0f - indent * 2.0f, thickness * 2.0f); p.addRectangle (50.0f - thickness, indent, thickness * 2.0f, 50.0f - indent - thickness); p.addRectangle (50.0f - thickness, 50.0f + thickness, thickness * 2.0f, 50.0f - indent - thickness); p.setUsingNonZeroWinding (false); g.setColour (textColour.withAlpha (button.isDown() ? 0.7f : (button.isOver() ? 0.5f : 0.3f))); g.fillPath (p, p.getTransformToScaleToFit (2.0f, 2.0f, width - 4.0f, height - 4.0f, true)); } if (button.hasKeyboardFocus (false)) { g.setColour (textColour.withAlpha (0.4f)); g.drawRect (0, 0, width, height); } } static void createRoundedPath (Path& p, const float x, const float y, const float w, const float h, const float cs, const bool curveTopLeft, const bool curveTopRight, const bool curveBottomLeft, const bool curveBottomRight) throw() { const float cs2 = 2.0f * cs; if (curveTopLeft) { p.startNewSubPath (x, y + cs); p.addArc (x, y, cs2, cs2, float_Pi * 1.5f, float_Pi * 2.0f); } else { p.startNewSubPath (x, y); } if (curveTopRight) { p.lineTo (x + w - cs, y); p.addArc (x + w - cs2, y, cs2, cs2, 0.0f, float_Pi * 0.5f); } else { p.lineTo (x + w, y); } if (curveBottomRight) { p.lineTo (x + w, y + h - cs); p.addArc (x + w - cs2, y + h - cs2, cs2, cs2, float_Pi * 0.5f, float_Pi); } else { p.lineTo (x + w, y + h); } if (curveBottomLeft) { p.lineTo (x + cs, y + h); p.addArc (x, y + h - cs2, cs2, cs2, float_Pi, float_Pi * 1.5f); } else { p.lineTo (x, y + h); } p.closeSubPath(); } void LookAndFeel::drawShinyButtonShape (Graphics& g, float x, float y, float w, float h, float maxCornerSize, const Colour& baseColour, const float strokeWidth, const bool flatOnLeft, const bool flatOnRight, const bool flatOnTop, const bool flatOnBottom) throw() { if (w <= strokeWidth * 1.1f || h <= strokeWidth * 1.1f) return; const float cs = jmin (maxCornerSize, w * 0.5f, h * 0.5f); Path outline; createRoundedPath (outline, x, y, w, h, cs, ! (flatOnLeft || flatOnTop), ! (flatOnRight || flatOnTop), ! (flatOnLeft || flatOnBottom), ! (flatOnRight || flatOnBottom)); ColourGradient cg (baseColour, 0.0f, y, baseColour.overlaidWith (Colour (0x070000ff)), 0.0f, y + h, false); cg.addColour (0.5, baseColour.overlaidWith (Colour (0x33ffffff))); cg.addColour (0.51, baseColour.overlaidWith (Colour (0x110000ff))); g.setGradientFill (cg); g.fillPath (outline); g.setColour (Colour (0x80000000)); g.strokePath (outline, PathStrokeType (strokeWidth)); } void LookAndFeel::drawGlassSphere (Graphics& g, const float x, const float y, const float diameter, const Colour& colour, const float outlineThickness) throw() { if (diameter <= outlineThickness) return; Path p; p.addEllipse (x, y, diameter, diameter); { ColourGradient cg (Colours::white.overlaidWith (colour.withMultipliedAlpha (0.3f)), 0, y, Colours::white.overlaidWith (colour.withMultipliedAlpha (0.3f)), 0, y + diameter, false); cg.addColour (0.4, Colours::white.overlaidWith (colour)); g.setGradientFill (cg); g.fillPath (p); } g.setGradientFill (ColourGradient (Colours::white, 0, y + diameter * 0.06f, Colours::transparentWhite, 0, y + diameter * 0.3f, false)); g.fillEllipse (x + diameter * 0.2f, y + diameter * 0.05f, diameter * 0.6f, diameter * 0.4f); ColourGradient cg (Colours::transparentBlack, x + diameter * 0.5f, y + diameter * 0.5f, Colours::black.withAlpha (0.5f * outlineThickness * colour.getFloatAlpha()), x, y + diameter * 0.5f, true); cg.addColour (0.7, Colours::transparentBlack); cg.addColour (0.8, Colours::black.withAlpha (0.1f * outlineThickness)); g.setGradientFill (cg); g.fillPath (p); g.setColour (Colours::black.withAlpha (0.5f * colour.getFloatAlpha())); g.drawEllipse (x, y, diameter, diameter, outlineThickness); } void LookAndFeel::drawGlassPointer (Graphics& g, const float x, const float y, const float diameter, const Colour& colour, const float outlineThickness, const int direction) throw() { if (diameter <= outlineThickness) return; Path p; p.startNewSubPath (x + diameter * 0.5f, y); p.lineTo (x + diameter, y + diameter * 0.6f); p.lineTo (x + diameter, y + diameter); p.lineTo (x, y + diameter); p.lineTo (x, y + diameter * 0.6f); p.closeSubPath(); p.applyTransform (AffineTransform::rotation (direction * (float_Pi * 0.5f), x + diameter * 0.5f, y + diameter * 0.5f)); { ColourGradient cg (Colours::white.overlaidWith (colour.withMultipliedAlpha (0.3f)), 0, y, Colours::white.overlaidWith (colour.withMultipliedAlpha (0.3f)), 0, y + diameter, false); cg.addColour (0.4, Colours::white.overlaidWith (colour)); g.setGradientFill (cg); g.fillPath (p); } ColourGradient cg (Colours::transparentBlack, x + diameter * 0.5f, y + diameter * 0.5f, Colours::black.withAlpha (0.5f * outlineThickness * colour.getFloatAlpha()), x - diameter * 0.2f, y + diameter * 0.5f, true); cg.addColour (0.5, Colours::transparentBlack); cg.addColour (0.7, Colours::black.withAlpha (0.07f * outlineThickness)); g.setGradientFill (cg); g.fillPath (p); g.setColour (Colours::black.withAlpha (0.5f * colour.getFloatAlpha())); g.strokePath (p, PathStrokeType (outlineThickness)); } void LookAndFeel::drawGlassLozenge (Graphics& g, const float x, const float y, const float width, const float height, const Colour& colour, const float outlineThickness, const float cornerSize, const bool flatOnLeft, const bool flatOnRight, const bool flatOnTop, const bool flatOnBottom) throw() { if (width <= outlineThickness || height <= outlineThickness) return; const int intX = (int) x; const int intY = (int) y; const int intW = (int) width; const int intH = (int) height; const float cs = cornerSize < 0 ? jmin (width * 0.5f, height * 0.5f) : cornerSize; const float edgeBlurRadius = height * 0.75f + (height - cs * 2.0f); const int intEdge = (int) edgeBlurRadius; Path outline; createRoundedPath (outline, x, y, width, height, cs, ! (flatOnLeft || flatOnTop), ! (flatOnRight || flatOnTop), ! (flatOnLeft || flatOnBottom), ! (flatOnRight || flatOnBottom)); { ColourGradient cg (colour.darker (0.2f), 0, y, colour.darker (0.2f), 0, y + height, false); cg.addColour (0.03, colour.withMultipliedAlpha (0.3f)); cg.addColour (0.4, colour); cg.addColour (0.97, colour.withMultipliedAlpha (0.3f)); g.setGradientFill (cg); g.fillPath (outline); } ColourGradient cg (Colours::transparentBlack, x + edgeBlurRadius, y + height * 0.5f, colour.darker (0.2f), x, y + height * 0.5f, true); cg.addColour (jlimit (0.0, 1.0, 1.0 - (cs * 0.5f) / edgeBlurRadius), Colours::transparentBlack); cg.addColour (jlimit (0.0, 1.0, 1.0 - (cs * 0.25f) / edgeBlurRadius), colour.darker (0.2f).withMultipliedAlpha (0.3f)); if (! (flatOnLeft || flatOnTop || flatOnBottom)) { g.saveState(); g.setGradientFill (cg); g.reduceClipRegion (intX, intY, intEdge, intH); g.fillPath (outline); g.restoreState(); } if (! (flatOnRight || flatOnTop || flatOnBottom)) { cg.point1.setX (x + width - edgeBlurRadius); cg.point2.setX (x + width); g.saveState(); g.setGradientFill (cg); g.reduceClipRegion (intX + intW - intEdge, intY, 2 + intEdge, intH); g.fillPath (outline); g.restoreState(); } { const float leftIndent = flatOnLeft ? 0.0f : cs * 0.4f; const float rightIndent = flatOnRight ? 0.0f : cs * 0.4f; Path highlight; createRoundedPath (highlight, x + leftIndent, y + cs * 0.1f, width - (leftIndent + rightIndent), height * 0.4f, cs * 0.4f, ! (flatOnLeft || flatOnTop), ! (flatOnRight || flatOnTop), ! (flatOnLeft || flatOnBottom), ! (flatOnRight || flatOnBottom)); g.setGradientFill (ColourGradient (colour.brighter (10.0f), 0, y + height * 0.06f, Colours::transparentWhite, 0, y + height * 0.4f, false)); g.fillPath (highlight); } g.setColour (colour.darker().withMultipliedAlpha (1.5f)); g.strokePath (outline, PathStrokeType (outlineThickness)); } END_JUCE_NAMESPACE /*** End of inlined file: juce_LookAndFeel.cpp ***/ /*** Start of inlined file: juce_OldSchoolLookAndFeel.cpp ***/ BEGIN_JUCE_NAMESPACE OldSchoolLookAndFeel::OldSchoolLookAndFeel() { setColour (TextButton::buttonColourId, Colour (0xffbbbbff)); setColour (ListBox::outlineColourId, findColour (ComboBox::outlineColourId)); setColour (ScrollBar::thumbColourId, Colour (0xffbbbbdd)); setColour (ScrollBar::backgroundColourId, Colours::transparentBlack); setColour (Slider::thumbColourId, Colours::white); setColour (Slider::trackColourId, Colour (0x7f000000)); setColour (Slider::textBoxOutlineColourId, Colours::grey); setColour (ProgressBar::backgroundColourId, Colours::white.withAlpha (0.6f)); setColour (ProgressBar::foregroundColourId, Colours::green.withAlpha (0.7f)); setColour (PopupMenu::backgroundColourId, Colour (0xffeef5f8)); setColour (PopupMenu::highlightedBackgroundColourId, Colour (0xbfa4c2ce)); setColour (PopupMenu::highlightedTextColourId, Colours::black); setColour (TextEditor::focusedOutlineColourId, findColour (TextButton::buttonColourId)); scrollbarShadow.setShadowProperties (2.2f, 0.5f, 0, 0); } OldSchoolLookAndFeel::~OldSchoolLookAndFeel() { } void OldSchoolLookAndFeel::drawButtonBackground (Graphics& g, Button& button, const Colour& backgroundColour, bool isMouseOverButton, bool isButtonDown) { const int width = button.getWidth(); const int height = button.getHeight(); const float indent = 2.0f; const int cornerSize = jmin (roundToInt (width * 0.4f), roundToInt (height * 0.4f)); Path p; p.addRoundedRectangle (indent, indent, width - indent * 2.0f, height - indent * 2.0f, (float) cornerSize); Colour bc (backgroundColour.withMultipliedSaturation (0.3f)); if (isMouseOverButton) { if (isButtonDown) bc = bc.brighter(); else if (bc.getBrightness() > 0.5f) bc = bc.darker (0.1f); else bc = bc.brighter (0.1f); } g.setColour (bc); g.fillPath (p); g.setColour (bc.contrasting().withAlpha ((isMouseOverButton) ? 0.6f : 0.4f)); g.strokePath (p, PathStrokeType ((isMouseOverButton) ? 2.0f : 1.4f)); } void OldSchoolLookAndFeel::drawTickBox (Graphics& g, Component& /*component*/, float x, float y, float w, float h, const bool ticked, const bool isEnabled, const bool /*isMouseOverButton*/, const bool isButtonDown) { Path box; box.addRoundedRectangle (0.0f, 2.0f, 6.0f, 6.0f, 1.0f); g.setColour (isEnabled ? Colours::blue.withAlpha (isButtonDown ? 0.3f : 0.1f) : Colours::lightgrey.withAlpha (0.1f)); AffineTransform trans (AffineTransform::scale (w / 9.0f, h / 9.0f).translated (x, y)); g.fillPath (box, trans); g.setColour (Colours::black.withAlpha (0.6f)); g.strokePath (box, PathStrokeType (0.9f), trans); if (ticked) { Path tick; tick.startNewSubPath (1.5f, 3.0f); tick.lineTo (3.0f, 6.0f); tick.lineTo (6.0f, 0.0f); g.setColour (isEnabled ? Colours::black : Colours::grey); g.strokePath (tick, PathStrokeType (2.5f), trans); } } void OldSchoolLookAndFeel::drawToggleButton (Graphics& g, ToggleButton& button, bool isMouseOverButton, bool isButtonDown) { if (button.hasKeyboardFocus (true)) { g.setColour (button.findColour (TextEditor::focusedOutlineColourId)); g.drawRect (0, 0, button.getWidth(), button.getHeight()); } const int tickWidth = jmin (20, button.getHeight() - 4); drawTickBox (g, button, 4.0f, (button.getHeight() - tickWidth) * 0.5f, (float) tickWidth, (float) tickWidth, button.getToggleState(), button.isEnabled(), isMouseOverButton, isButtonDown); g.setColour (button.findColour (ToggleButton::textColourId)); g.setFont (jmin (15.0f, button.getHeight() * 0.6f)); if (! button.isEnabled()) g.setOpacity (0.5f); const int textX = tickWidth + 5; g.drawFittedText (button.getButtonText(), textX, 4, button.getWidth() - textX - 2, button.getHeight() - 8, Justification::centredLeft, 10); } void OldSchoolLookAndFeel::drawProgressBar (Graphics& g, ProgressBar& progressBar, int width, int height, double progress, const String& textToShow) { if (progress < 0 || progress >= 1.0) { LookAndFeel::drawProgressBar (g, progressBar, width, height, progress, textToShow); } else { const Colour background (progressBar.findColour (ProgressBar::backgroundColourId)); const Colour foreground (progressBar.findColour (ProgressBar::foregroundColourId)); g.fillAll (background); g.setColour (foreground); g.fillRect (1, 1, jlimit (0, width - 2, roundToInt (progress * (width - 2))), height - 2); if (textToShow.isNotEmpty()) { g.setColour (Colour::contrasting (background, foreground)); g.setFont (height * 0.6f); g.drawText (textToShow, 0, 0, width, height, Justification::centred, false); } } } void OldSchoolLookAndFeel::drawScrollbarButton (Graphics& g, ScrollBar& bar, int width, int height, int buttonDirection, bool isScrollbarVertical, bool isMouseOverButton, bool isButtonDown) { if (isScrollbarVertical) width -= 2; else height -= 2; Path p; if (buttonDirection == 0) p.addTriangle (width * 0.5f, height * 0.2f, width * 0.1f, height * 0.7f, width * 0.9f, height * 0.7f); else if (buttonDirection == 1) p.addTriangle (width * 0.8f, height * 0.5f, width * 0.3f, height * 0.1f, width * 0.3f, height * 0.9f); else if (buttonDirection == 2) p.addTriangle (width * 0.5f, height * 0.8f, width * 0.1f, height * 0.3f, width * 0.9f, height * 0.3f); else if (buttonDirection == 3) p.addTriangle (width * 0.2f, height * 0.5f, width * 0.7f, height * 0.1f, width * 0.7f, height * 0.9f); if (isButtonDown) g.setColour (Colours::white); else if (isMouseOverButton) g.setColour (Colours::white.withAlpha (0.7f)); else g.setColour (bar.findColour (ScrollBar::thumbColourId).withAlpha (0.5f)); g.fillPath (p); g.setColour (Colours::black.withAlpha (0.5f)); g.strokePath (p, PathStrokeType (0.5f)); } void OldSchoolLookAndFeel::drawScrollbar (Graphics& g, ScrollBar& bar, int x, int y, int width, int height, bool isScrollbarVertical, int thumbStartPosition, int thumbSize, bool isMouseOver, bool isMouseDown) { g.fillAll (bar.findColour (ScrollBar::backgroundColourId)); g.setColour (bar.findColour (ScrollBar::thumbColourId) .withAlpha ((isMouseOver || isMouseDown) ? 0.4f : 0.15f)); if (thumbSize > 0.0f) { Rectangle thumb; if (isScrollbarVertical) { width -= 2; g.fillRect (x + roundToInt (width * 0.35f), y, roundToInt (width * 0.3f), height); thumb.setBounds (x + 1, thumbStartPosition, width - 2, thumbSize); } else { height -= 2; g.fillRect (x, y + roundToInt (height * 0.35f), width, roundToInt (height * 0.3f)); thumb.setBounds (thumbStartPosition, y + 1, thumbSize, height - 2); } g.setColour (bar.findColour (ScrollBar::thumbColourId) .withAlpha ((isMouseOver || isMouseDown) ? 0.95f : 0.7f)); g.fillRect (thumb); g.setColour (Colours::black.withAlpha ((isMouseOver || isMouseDown) ? 0.4f : 0.25f)); g.drawRect (thumb.getX(), thumb.getY(), thumb.getWidth(), thumb.getHeight()); if (thumbSize > 16) { for (int i = 3; --i >= 0;) { const float linePos = thumbStartPosition + thumbSize / 2 + (i - 1) * 4.0f; g.setColour (Colours::black.withAlpha (0.15f)); if (isScrollbarVertical) { g.drawLine (x + width * 0.2f, linePos, width * 0.8f, linePos); g.setColour (Colours::white.withAlpha (0.15f)); g.drawLine (width * 0.2f, linePos - 1, width * 0.8f, linePos - 1); } else { g.drawLine (linePos, height * 0.2f, linePos, height * 0.8f); g.setColour (Colours::white.withAlpha (0.15f)); g.drawLine (linePos - 1, height * 0.2f, linePos - 1, height * 0.8f); } } } } } ImageEffectFilter* OldSchoolLookAndFeel::getScrollbarEffect() { return &scrollbarShadow; } void OldSchoolLookAndFeel::drawPopupMenuBackground (Graphics& g, int width, int height) { g.fillAll (findColour (PopupMenu::backgroundColourId)); g.setColour (Colours::black.withAlpha (0.6f)); g.drawRect (0, 0, width, height); } void OldSchoolLookAndFeel::drawMenuBarBackground (Graphics& g, int /*width*/, int /*height*/, bool, MenuBarComponent& menuBar) { g.fillAll (menuBar.findColour (PopupMenu::backgroundColourId)); } void OldSchoolLookAndFeel::drawTextEditorOutline (Graphics& g, int width, int height, TextEditor& textEditor) { if (textEditor.isEnabled()) { g.setColour (textEditor.findColour (TextEditor::outlineColourId)); g.drawRect (0, 0, width, height); } } void OldSchoolLookAndFeel::drawComboBox (Graphics& g, int width, int height, const bool isButtonDown, int buttonX, int buttonY, int buttonW, int buttonH, ComboBox& box) { g.fillAll (box.findColour (ComboBox::backgroundColourId)); g.setColour (box.findColour ((isButtonDown) ? ComboBox::buttonColourId : ComboBox::backgroundColourId)); g.fillRect (buttonX, buttonY, buttonW, buttonH); g.setColour (box.findColour (ComboBox::outlineColourId)); g.drawRect (0, 0, width, height); const float arrowX = 0.2f; const float arrowH = 0.3f; if (box.isEnabled()) { Path p; p.addTriangle (buttonX + buttonW * 0.5f, buttonY + buttonH * (0.45f - arrowH), buttonX + buttonW * (1.0f - arrowX), buttonY + buttonH * 0.45f, buttonX + buttonW * arrowX, buttonY + buttonH * 0.45f); p.addTriangle (buttonX + buttonW * 0.5f, buttonY + buttonH * (0.55f + arrowH), buttonX + buttonW * (1.0f - arrowX), buttonY + buttonH * 0.55f, buttonX + buttonW * arrowX, buttonY + buttonH * 0.55f); g.setColour (box.findColour ((isButtonDown) ? ComboBox::backgroundColourId : ComboBox::buttonColourId)); g.fillPath (p); } } const Font OldSchoolLookAndFeel::getComboBoxFont (ComboBox& box) { Font f (jmin (15.0f, box.getHeight() * 0.85f)); f.setHorizontalScale (0.9f); return f; } static void drawTriangle (Graphics& g, float x1, float y1, float x2, float y2, float x3, float y3, const Colour& fill, const Colour& outline) throw() { Path p; p.addTriangle (x1, y1, x2, y2, x3, y3); g.setColour (fill); g.fillPath (p); g.setColour (outline); g.strokePath (p, PathStrokeType (0.3f)); } void OldSchoolLookAndFeel::drawLinearSlider (Graphics& g, int x, int y, int w, int h, float sliderPos, float minSliderPos, float maxSliderPos, const Slider::SliderStyle style, Slider& slider) { g.fillAll (slider.findColour (Slider::backgroundColourId)); if (style == Slider::LinearBar) { g.setColour (slider.findColour (Slider::thumbColourId)); g.fillRect (x, y, (int) sliderPos - x, h); g.setColour (slider.findColour (Slider::textBoxTextColourId).withMultipliedAlpha (0.5f)); g.drawRect (x, y, (int) sliderPos - x, h); } else { g.setColour (slider.findColour (Slider::trackColourId) .withMultipliedAlpha (slider.isEnabled() ? 1.0f : 0.3f)); if (slider.isHorizontal()) { g.fillRect (x, y + roundToInt (h * 0.6f), w, roundToInt (h * 0.2f)); } else { g.fillRect (x + roundToInt (w * 0.5f - jmin (3.0f, w * 0.1f)), y, jmin (4, roundToInt (w * 0.2f)), h); } float alpha = 0.35f; if (slider.isEnabled()) alpha = slider.isMouseOverOrDragging() ? 1.0f : 0.7f; const Colour fill (slider.findColour (Slider::thumbColourId).withAlpha (alpha)); const Colour outline (Colours::black.withAlpha (slider.isEnabled() ? 0.7f : 0.35f)); if (style == Slider::TwoValueVertical || style == Slider::ThreeValueVertical) { drawTriangle (g, x + w * 0.5f + jmin (4.0f, w * 0.3f), minSliderPos, x + w * 0.5f - jmin (8.0f, w * 0.4f), minSliderPos - 7.0f, x + w * 0.5f - jmin (8.0f, w * 0.4f), minSliderPos, fill, outline); drawTriangle (g, x + w * 0.5f + jmin (4.0f, w * 0.3f), maxSliderPos, x + w * 0.5f - jmin (8.0f, w * 0.4f), maxSliderPos, x + w * 0.5f - jmin (8.0f, w * 0.4f), maxSliderPos + 7.0f, fill, outline); } else if (style == Slider::TwoValueHorizontal || style == Slider::ThreeValueHorizontal) { drawTriangle (g, minSliderPos, y + h * 0.6f - jmin (4.0f, h * 0.3f), minSliderPos - 7.0f, y + h * 0.9f , minSliderPos, y + h * 0.9f, fill, outline); drawTriangle (g, maxSliderPos, y + h * 0.6f - jmin (4.0f, h * 0.3f), maxSliderPos, y + h * 0.9f, maxSliderPos + 7.0f, y + h * 0.9f, fill, outline); } if (style == Slider::LinearHorizontal || style == Slider::ThreeValueHorizontal) { drawTriangle (g, sliderPos, y + h * 0.9f, sliderPos - 7.0f, y + h * 0.2f, sliderPos + 7.0f, y + h * 0.2f, fill, outline); } else if (style == Slider::LinearVertical || style == Slider::ThreeValueVertical) { drawTriangle (g, x + w * 0.5f - jmin (4.0f, w * 0.3f), sliderPos, x + w * 0.5f + jmin (8.0f, w * 0.4f), sliderPos - 7.0f, x + w * 0.5f + jmin (8.0f, w * 0.4f), sliderPos + 7.0f, fill, outline); } } } Button* OldSchoolLookAndFeel::createSliderButton (const bool isIncrement) { if (isIncrement) return new ArrowButton ("u", 0.75f, Colours::white.withAlpha (0.8f)); else return new ArrowButton ("d", 0.25f, Colours::white.withAlpha (0.8f)); } ImageEffectFilter* OldSchoolLookAndFeel::getSliderEffect() { return &scrollbarShadow; } int OldSchoolLookAndFeel::getSliderThumbRadius (Slider&) { return 8; } void OldSchoolLookAndFeel::drawCornerResizer (Graphics& g, int w, int h, bool isMouseOver, bool isMouseDragging) { g.setColour ((isMouseOver || isMouseDragging) ? Colours::lightgrey : Colours::darkgrey); const float lineThickness = jmin (w, h) * 0.1f; for (float i = 0.0f; i < 1.0f; i += 0.3f) { g.drawLine (w * i, h + 1.0f, w + 1.0f, h * i, lineThickness); } } Button* OldSchoolLookAndFeel::createDocumentWindowButton (int buttonType) { Path shape; if (buttonType == DocumentWindow::closeButton) { shape.addLineSegment (Line (0.0f, 0.0f, 1.0f, 1.0f), 0.35f); shape.addLineSegment (Line (1.0f, 0.0f, 0.0f, 1.0f), 0.35f); ShapeButton* const b = new ShapeButton ("close", Colour (0x7fff3333), Colour (0xd7ff3333), Colour (0xf7ff3333)); b->setShape (shape, true, true, true); return b; } else if (buttonType == DocumentWindow::minimiseButton) { shape.addLineSegment (Line (0.0f, 0.5f, 1.0f, 0.5f), 0.25f); DrawableButton* b = new DrawableButton ("minimise", DrawableButton::ImageFitted); DrawablePath dp; dp.setPath (shape); dp.setFill (Colours::black.withAlpha (0.3f)); b->setImages (&dp); return b; } else if (buttonType == DocumentWindow::maximiseButton) { shape.addLineSegment (Line (0.5f, 0.0f, 0.5f, 1.0f), 0.25f); shape.addLineSegment (Line (0.0f, 0.5f, 1.0f, 0.5f), 0.25f); DrawableButton* b = new DrawableButton ("maximise", DrawableButton::ImageFitted); DrawablePath dp; dp.setPath (shape); dp.setFill (Colours::black.withAlpha (0.3f)); b->setImages (&dp); return b; } jassertfalse; return 0; } void OldSchoolLookAndFeel::positionDocumentWindowButtons (DocumentWindow&, int titleBarX, int titleBarY, int titleBarW, int titleBarH, Button* minimiseButton, Button* maximiseButton, Button* closeButton, bool positionTitleBarButtonsOnLeft) { titleBarY += titleBarH / 8; titleBarH -= titleBarH / 4; const int buttonW = titleBarH; int x = positionTitleBarButtonsOnLeft ? titleBarX + 4 : titleBarX + titleBarW - buttonW - 4; if (closeButton != 0) { closeButton->setBounds (x, titleBarY, buttonW, titleBarH); x += positionTitleBarButtonsOnLeft ? buttonW + buttonW / 5 : -(buttonW + buttonW / 5); } if (positionTitleBarButtonsOnLeft) swapVariables (minimiseButton, maximiseButton); if (maximiseButton != 0) { maximiseButton->setBounds (x, titleBarY - 2, buttonW, titleBarH); x += positionTitleBarButtonsOnLeft ? buttonW : -buttonW; } if (minimiseButton != 0) minimiseButton->setBounds (x, titleBarY - 2, buttonW, titleBarH); } END_JUCE_NAMESPACE /*** End of inlined file: juce_OldSchoolLookAndFeel.cpp ***/ /*** Start of inlined file: juce_MenuBarComponent.cpp ***/ BEGIN_JUCE_NAMESPACE MenuBarComponent::MenuBarComponent (MenuBarModel* model_) : model (0), itemUnderMouse (-1), currentPopupIndex (-1), topLevelIndexClicked (0), lastMouseX (0), lastMouseY (0) { setRepaintsOnMouseActivity (true); setWantsKeyboardFocus (false); setMouseClickGrabsKeyboardFocus (false); setModel (model_); } MenuBarComponent::~MenuBarComponent() { setModel (0); Desktop::getInstance().removeGlobalMouseListener (this); } MenuBarModel* MenuBarComponent::getModel() const throw() { return model; } void MenuBarComponent::setModel (MenuBarModel* const newModel) { if (model != newModel) { if (model != 0) model->removeListener (this); model = newModel; if (model != 0) model->addListener (this); repaint(); menuBarItemsChanged (0); } } void MenuBarComponent::paint (Graphics& g) { const bool isMouseOverBar = currentPopupIndex >= 0 || itemUnderMouse >= 0 || isMouseOver(); getLookAndFeel().drawMenuBarBackground (g, getWidth(), getHeight(), isMouseOverBar, *this); if (model != 0) { for (int i = 0; i < menuNames.size(); ++i) { g.saveState(); g.setOrigin (xPositions [i], 0); g.reduceClipRegion (0, 0, xPositions[i + 1] - xPositions[i], getHeight()); getLookAndFeel().drawMenuBarItem (g, xPositions[i + 1] - xPositions[i], getHeight(), i, menuNames[i], i == itemUnderMouse, i == currentPopupIndex, isMouseOverBar, *this); g.restoreState(); } } } void MenuBarComponent::resized() { xPositions.clear(); int x = 2; xPositions.add (x); for (int i = 0; i < menuNames.size(); ++i) { x += getLookAndFeel().getMenuBarItemWidth (*this, i, menuNames[i]); xPositions.add (x); } } int MenuBarComponent::getItemAt (const int x, const int y) { for (int i = 0; i < xPositions.size(); ++i) if (x >= xPositions[i] && x < xPositions[i + 1]) return reallyContains (x, y, true) ? i : -1; return -1; } void MenuBarComponent::repaintMenuItem (int index) { if (((unsigned int) index) < (unsigned int) xPositions.size()) { const int x1 = xPositions [index]; const int x2 = xPositions [index + 1]; repaint (x1 - 2, 0, x2 - x1 + 4, getHeight()); } } void MenuBarComponent::setItemUnderMouse (const int index) { if (itemUnderMouse != index) { repaintMenuItem (itemUnderMouse); itemUnderMouse = index; repaintMenuItem (itemUnderMouse); } } void MenuBarComponent::setOpenItem (int index) { if (currentPopupIndex != index) { repaintMenuItem (currentPopupIndex); currentPopupIndex = index; repaintMenuItem (currentPopupIndex); if (index >= 0) Desktop::getInstance().addGlobalMouseListener (this); else Desktop::getInstance().removeGlobalMouseListener (this); } } void MenuBarComponent::updateItemUnderMouse (int x, int y) { setItemUnderMouse (getItemAt (x, y)); } class MenuBarComponent::AsyncCallback : public ModalComponentManager::Callback { public: AsyncCallback (MenuBarComponent* const bar_, const int topLevelIndex_) : bar (bar_), topLevelIndex (topLevelIndex_) { } ~AsyncCallback() {} void modalStateFinished (int returnValue) { if (bar != 0) bar->menuDismissed (topLevelIndex, returnValue); } private: Component::SafePointer bar; const int topLevelIndex; AsyncCallback (const AsyncCallback&); AsyncCallback& operator= (const AsyncCallback&); }; void MenuBarComponent::showMenu (int index) { if (index != currentPopupIndex) { PopupMenu::dismissAllActiveMenus(); menuBarItemsChanged (0); setOpenItem (index); setItemUnderMouse (index); if (index >= 0) { PopupMenu m (model->getMenuForIndex (itemUnderMouse, menuNames [itemUnderMouse])); if (m.lookAndFeel == 0) m.setLookAndFeel (&getLookAndFeel()); const Rectangle itemPos (xPositions [index], 0, xPositions [index + 1] - xPositions [index], getHeight()); m.showMenu (itemPos + getScreenPosition(), 0, itemPos.getWidth(), 0, 0, true, this, new AsyncCallback (this, index)); } } } void MenuBarComponent::menuDismissed (int topLevelIndex, int itemId) { topLevelIndexClicked = topLevelIndex; postCommandMessage (itemId); } void MenuBarComponent::handleCommandMessage (int commandId) { const Point mousePos (getMouseXYRelative()); updateItemUnderMouse (mousePos.getX(), mousePos.getY()); if (! isCurrentlyBlockedByAnotherModalComponent()) setOpenItem (-1); if (commandId != 0 && model != 0) model->menuItemSelected (commandId, topLevelIndexClicked); } void MenuBarComponent::mouseEnter (const MouseEvent& e) { if (e.eventComponent == this) updateItemUnderMouse (e.x, e.y); } void MenuBarComponent::mouseExit (const MouseEvent& e) { if (e.eventComponent == this) updateItemUnderMouse (e.x, e.y); } void MenuBarComponent::mouseDown (const MouseEvent& e) { if (currentPopupIndex < 0) { const MouseEvent e2 (e.getEventRelativeTo (this)); updateItemUnderMouse (e2.x, e2.y); currentPopupIndex = -2; showMenu (itemUnderMouse); } } void MenuBarComponent::mouseDrag (const MouseEvent& e) { const MouseEvent e2 (e.getEventRelativeTo (this)); const int item = getItemAt (e2.x, e2.y); if (item >= 0) showMenu (item); } void MenuBarComponent::mouseUp (const MouseEvent& e) { const MouseEvent e2 (e.getEventRelativeTo (this)); updateItemUnderMouse (e2.x, e2.y); if (itemUnderMouse < 0 && getLocalBounds().contains (e2.x, e2.y)) { setOpenItem (-1); PopupMenu::dismissAllActiveMenus(); } } void MenuBarComponent::mouseMove (const MouseEvent& e) { const MouseEvent e2 (e.getEventRelativeTo (this)); if (lastMouseX != e2.x || lastMouseY != e2.y) { if (currentPopupIndex >= 0) { const int item = getItemAt (e2.x, e2.y); if (item >= 0) showMenu (item); } else { updateItemUnderMouse (e2.x, e2.y); } lastMouseX = e2.x; lastMouseY = e2.y; } } bool MenuBarComponent::keyPressed (const KeyPress& key) { bool used = false; const int numMenus = menuNames.size(); const int currentIndex = jlimit (0, menuNames.size() - 1, currentPopupIndex); if (key.isKeyCode (KeyPress::leftKey)) { showMenu ((currentIndex + numMenus - 1) % numMenus); used = true; } else if (key.isKeyCode (KeyPress::rightKey)) { showMenu ((currentIndex + 1) % numMenus); used = true; } return used; } void MenuBarComponent::menuBarItemsChanged (MenuBarModel* /*menuBarModel*/) { StringArray newNames; if (model != 0) newNames = model->getMenuBarNames(); if (newNames != menuNames) { menuNames = newNames; repaint(); resized(); } } void MenuBarComponent::menuCommandInvoked (MenuBarModel* /*menuBarModel*/, const ApplicationCommandTarget::InvocationInfo& info) { if (model == 0 || (info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) != 0) return; for (int i = 0; i < menuNames.size(); ++i) { const PopupMenu menu (model->getMenuForIndex (i, menuNames [i])); if (menu.containsCommandItem (info.commandID)) { setItemUnderMouse (i); startTimer (200); break; } } } void MenuBarComponent::timerCallback() { stopTimer(); const Point mousePos (getMouseXYRelative()); updateItemUnderMouse (mousePos.getX(), mousePos.getY()); } END_JUCE_NAMESPACE /*** End of inlined file: juce_MenuBarComponent.cpp ***/ /*** Start of inlined file: juce_MenuBarModel.cpp ***/ BEGIN_JUCE_NAMESPACE MenuBarModel::MenuBarModel() throw() : manager (0) { } MenuBarModel::~MenuBarModel() { setApplicationCommandManagerToWatch (0); } void MenuBarModel::menuItemsChanged() { triggerAsyncUpdate(); } void MenuBarModel::setApplicationCommandManagerToWatch (ApplicationCommandManager* const newManager) throw() { if (manager != newManager) { if (manager != 0) manager->removeListener (this); manager = newManager; if (manager != 0) manager->addListener (this); } } void MenuBarModel::addListener (Listener* const newListener) throw() { listeners.add (newListener); } void MenuBarModel::removeListener (Listener* const listenerToRemove) throw() { // Trying to remove a listener that isn't on the list! // If this assertion happens because this object is a dangling pointer, make sure you've not // deleted this menu model while it's still being used by something (e.g. by a MenuBarComponent) jassert (listeners.contains (listenerToRemove)); listeners.remove (listenerToRemove); } void MenuBarModel::handleAsyncUpdate() { listeners.call (&MenuBarModel::Listener::menuBarItemsChanged, this); } void MenuBarModel::applicationCommandInvoked (const ApplicationCommandTarget::InvocationInfo& info) { listeners.call (&MenuBarModel::Listener::menuCommandInvoked, this, info); } void MenuBarModel::applicationCommandListChanged() { menuItemsChanged(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_MenuBarModel.cpp ***/ /*** Start of inlined file: juce_PopupMenu.cpp ***/ BEGIN_JUCE_NAMESPACE class PopupMenu::Item { public: Item() : itemId (0), active (true), isSeparator (true), isTicked (false), usesColour (false), customComp (0), commandManager (0) { } Item (const int itemId_, const String& text_, const bool active_, const bool isTicked_, const Image& im, const Colour& textColour_, const bool usesColour_, PopupMenuCustomComponent* const customComp_, const PopupMenu* const subMenu_, ApplicationCommandManager* const commandManager_) : itemId (itemId_), text (text_), textColour (textColour_), active (active_), isSeparator (false), isTicked (isTicked_), usesColour (usesColour_), image (im), customComp (customComp_), commandManager (commandManager_) { if (subMenu_ != 0) subMenu = new PopupMenu (*subMenu_); if (commandManager_ != 0 && itemId_ != 0) { String shortcutKey; Array keyPresses (commandManager_->getKeyMappings() ->getKeyPressesAssignedToCommand (itemId_)); for (int i = 0; i < keyPresses.size(); ++i) { const String key (keyPresses.getReference(i).getTextDescription()); if (shortcutKey.isNotEmpty()) shortcutKey << ", "; if (key.length() == 1) shortcutKey << "shortcut: '" << key << '\''; else shortcutKey << key; } shortcutKey = shortcutKey.trim(); if (shortcutKey.isNotEmpty()) text << "" << shortcutKey; } } Item (const Item& other) : itemId (other.itemId), text (other.text), textColour (other.textColour), active (other.active), isSeparator (other.isSeparator), isTicked (other.isTicked), usesColour (other.usesColour), image (other.image), customComp (other.customComp), commandManager (other.commandManager) { if (other.subMenu != 0) subMenu = new PopupMenu (*(other.subMenu)); } ~Item() { customComp = 0; } bool canBeTriggered() const throw() { return active && ! (isSeparator || (subMenu != 0)); } bool hasActiveSubMenu() const throw() { return active && (subMenu != 0); } const int itemId; String text; const Colour textColour; const bool active, isSeparator, isTicked, usesColour; Image image; ReferenceCountedObjectPtr customComp; ScopedPointer subMenu; ApplicationCommandManager* const commandManager; juce_UseDebuggingNewOperator private: Item& operator= (const Item&); }; class PopupMenu::ItemComponent : public Component { public: ItemComponent (const PopupMenu::Item& itemInfo_) : itemInfo (itemInfo_), isHighlighted (false) { if (itemInfo.customComp != 0) addAndMakeVisible (itemInfo.customComp); } ~ItemComponent() { if (itemInfo.customComp != 0) removeChildComponent (itemInfo.customComp); } void getIdealSize (int& idealWidth, int& idealHeight, const int standardItemHeight) { if (itemInfo.customComp != 0) { itemInfo.customComp->getIdealSize (idealWidth, idealHeight); } else { getLookAndFeel().getIdealPopupMenuItemSize (itemInfo.text, itemInfo.isSeparator, standardItemHeight, idealWidth, idealHeight); } } void paint (Graphics& g) { if (itemInfo.customComp == 0) { String mainText (itemInfo.text); String endText; const int endIndex = mainText.indexOf (""); if (endIndex >= 0) { endText = mainText.substring (endIndex + 5).trim(); mainText = mainText.substring (0, endIndex); } getLookAndFeel() .drawPopupMenuItem (g, getWidth(), getHeight(), itemInfo.isSeparator, itemInfo.active, isHighlighted, itemInfo.isTicked, itemInfo.subMenu != 0, mainText, endText, itemInfo.image.isValid() ? &itemInfo.image : 0, itemInfo.usesColour ? &(itemInfo.textColour) : 0); } } void resized() { if (getNumChildComponents() > 0) getChildComponent(0)->setBounds (2, 0, getWidth() - 4, getHeight()); } void setHighlighted (bool shouldBeHighlighted) { shouldBeHighlighted = shouldBeHighlighted && itemInfo.active; if (isHighlighted != shouldBeHighlighted) { isHighlighted = shouldBeHighlighted; if (itemInfo.customComp != 0) { itemInfo.customComp->isHighlighted = shouldBeHighlighted; itemInfo.customComp->repaint(); } repaint(); } } PopupMenu::Item itemInfo; juce_UseDebuggingNewOperator private: bool isHighlighted; ItemComponent (const ItemComponent&); ItemComponent& operator= (const ItemComponent&); }; namespace PopupMenuSettings { static const int scrollZone = 24; static const int borderSize = 2; static const int timerInterval = 50; static const int dismissCommandId = 0x6287345f; } class PopupMenu::Window : public Component, private Timer { public: Window() : Component ("menu"), owner (0), currentChild (0), activeSubMenu (0), managerOfChosenCommand (0), minimumWidth (0), maximumNumColumns (7), standardItemHeight (0), isOver (false), hasBeenOver (false), isDown (false), needsToScroll (false), hideOnExit (false), disableMouseMoves (false), hasAnyJuceCompHadFocus (false), numColumns (0), contentHeight (0), childYOffset (0), timeEnteredCurrentChildComp (0), scrollAcceleration (1.0) { menuCreationTime = lastFocused = lastScroll = Time::getMillisecondCounter(); setWantsKeyboardFocus (true); setMouseClickGrabsKeyboardFocus (false); setOpaque (true); setAlwaysOnTop (true); Desktop::getInstance().addGlobalMouseListener (this); getActiveWindows().add (this); } ~Window() { getActiveWindows().removeValue (this); Desktop::getInstance().removeGlobalMouseListener (this); jassert (activeSubMenu == 0 || activeSubMenu->isValidComponent()); activeSubMenu = 0; deleteAllChildren(); } static Window* create (const PopupMenu& menu, const bool dismissOnMouseUp, Window* const owner_, const Rectangle& target, const int minimumWidth, const int maximumNumColumns, const int standardItemHeight, const bool alignToRectangle, const int itemIdThatMustBeVisible, ApplicationCommandManager** managerOfChosenCommand, Component* const componentAttachedTo) { if (menu.items.size() > 0) { int totalItems = 0; ScopedPointer mw (new Window()); mw->setLookAndFeel (menu.lookAndFeel); mw->setWantsKeyboardFocus (false); mw->minimumWidth = minimumWidth; mw->maximumNumColumns = maximumNumColumns; mw->standardItemHeight = standardItemHeight; mw->dismissOnMouseUp = dismissOnMouseUp; for (int i = 0; i < menu.items.size(); ++i) { PopupMenu::Item* const item = menu.items.getUnchecked(i); mw->addItem (*item); ++totalItems; } if (totalItems > 0) { mw->owner = owner_; mw->managerOfChosenCommand = managerOfChosenCommand; mw->componentAttachedTo = componentAttachedTo; mw->componentAttachedToOriginal = componentAttachedTo; mw->calculateWindowPos (target, alignToRectangle); mw->setTopLeftPosition (mw->windowPos.getX(), mw->windowPos.getY()); mw->updateYPositions(); if (itemIdThatMustBeVisible != 0) { const int y = target.getY() - mw->windowPos.getY(); mw->ensureItemIsVisible (itemIdThatMustBeVisible, (((unsigned int) y) < (unsigned int) mw->windowPos.getHeight()) ? y : -1); } mw->resizeToBestWindowPos(); mw->addToDesktop (ComponentPeer::windowIsTemporary | mw->getLookAndFeel().getMenuWindowFlags()); return mw.release(); } } return 0; } void paint (Graphics& g) { getLookAndFeel().drawPopupMenuBackground (g, getWidth(), getHeight()); } void paintOverChildren (Graphics& g) { if (isScrolling()) { LookAndFeel& lf = getLookAndFeel(); if (isScrollZoneActive (false)) lf.drawPopupMenuUpDownArrow (g, getWidth(), PopupMenuSettings::scrollZone, true); if (isScrollZoneActive (true)) { g.setOrigin (0, getHeight() - PopupMenuSettings::scrollZone); lf.drawPopupMenuUpDownArrow (g, getWidth(), PopupMenuSettings::scrollZone, false); } } } bool isScrollZoneActive (bool bottomOne) const { return isScrolling() && (bottomOne ? childYOffset < contentHeight - windowPos.getHeight() : childYOffset > 0); } void addItem (const PopupMenu::Item& item) { PopupMenu::ItemComponent* const mic = new PopupMenu::ItemComponent (item); addAndMakeVisible (mic); int itemW = 80; int itemH = 16; mic->getIdealSize (itemW, itemH, standardItemHeight); mic->setSize (itemW, jlimit (2, 600, itemH)); mic->addMouseListener (this, false); } // hide this and all sub-comps void hide (const PopupMenu::Item* const item) { if (isVisible()) { jassert (activeSubMenu == 0 || activeSubMenu->isValidComponent()); activeSubMenu = 0; currentChild = 0; exitModalState (item != 0 ? item->itemId : 0); setVisible (false); if (item != 0 && item->commandManager != 0 && item->itemId != 0) { *managerOfChosenCommand = item->commandManager; } } } void dismissMenu (const PopupMenu::Item* const item) { if (owner != 0) { owner->dismissMenu (item); } else { if (item != 0) { // need a copy of this on the stack as the one passed in will get deleted during this call const PopupMenu::Item mi (*item); hide (&mi); } else { hide (0); } } } void mouseMove (const MouseEvent&) { timerCallback(); } void mouseDown (const MouseEvent&) { timerCallback(); } void mouseDrag (const MouseEvent&) { timerCallback(); } void mouseUp (const MouseEvent&) { timerCallback(); } void mouseWheelMove (const MouseEvent&, float /*amountX*/, float amountY) { alterChildYPos (roundToInt (-10.0f * amountY * PopupMenuSettings::scrollZone)); lastMouse = Point (-1, -1); } bool keyPressed (const KeyPress& key) { if (key.isKeyCode (KeyPress::downKey)) { selectNextItem (1); } else if (key.isKeyCode (KeyPress::upKey)) { selectNextItem (-1); } else if (key.isKeyCode (KeyPress::leftKey)) { if (owner != 0) { Component::SafePointer parentWindow (owner); PopupMenu::ItemComponent* currentChildOfParent = parentWindow->currentChild; hide (0); if (parentWindow != 0) parentWindow->setCurrentlyHighlightedChild (currentChildOfParent); disableTimerUntilMouseMoves(); } else if (componentAttachedTo != 0) { componentAttachedTo->keyPressed (key); } } else if (key.isKeyCode (KeyPress::rightKey)) { disableTimerUntilMouseMoves(); if (showSubMenuFor (currentChild)) { jassert (activeSubMenu == 0 || activeSubMenu->isValidComponent()); if (activeSubMenu != 0 && activeSubMenu->isVisible()) activeSubMenu->selectNextItem (1); } else if (componentAttachedTo != 0) { componentAttachedTo->keyPressed (key); } } else if (key.isKeyCode (KeyPress::returnKey)) { triggerCurrentlyHighlightedItem(); } else if (key.isKeyCode (KeyPress::escapeKey)) { dismissMenu (0); } else { return false; } return true; } void inputAttemptWhenModal() { Component::SafePointer deletionChecker (this); timerCallback(); if (deletionChecker != 0 && ! isOverAnyMenu()) { if (componentAttachedTo != 0) { // we want to dismiss the menu, but if we do it synchronously, then // the mouse-click will be allowed to pass through. That's good, except // when the user clicks on the button that orginally popped the menu up, // as they'll expect the menu to go away, and in fact it'll just // come back. So only dismiss synchronously if they're not on the original // comp that we're attached to. const Point mousePos (componentAttachedTo->getMouseXYRelative()); if (componentAttachedTo->reallyContains (mousePos.getX(), mousePos.getY(), true)) { postCommandMessage (PopupMenuSettings::dismissCommandId); // dismiss asynchrounously return; } } dismissMenu (0); } } void handleCommandMessage (int commandId) { Component::handleCommandMessage (commandId); if (commandId == PopupMenuSettings::dismissCommandId) dismissMenu (0); } void timerCallback() { if (! isVisible()) return; if (componentAttachedTo != componentAttachedToOriginal) { dismissMenu (0); return; } Window* currentlyModalWindow = dynamic_cast (Component::getCurrentlyModalComponent()); if (currentlyModalWindow != 0 && ! treeContains (currentlyModalWindow)) return; startTimer (PopupMenuSettings::timerInterval); // do this in case it was called from a mouse // move rather than a real timer callback const Point globalMousePos (Desktop::getMousePosition()); const Point localMousePos (globalPositionToRelative (globalMousePos)); const uint32 now = Time::getMillisecondCounter(); if (now > timeEnteredCurrentChildComp + 100 && reallyContains (localMousePos.getX(), localMousePos.getY(), true) && currentChild->isValidComponent() && (! disableMouseMoves) && ! (activeSubMenu != 0 && activeSubMenu->isVisible())) { showSubMenuFor (currentChild); } if (globalMousePos != lastMouse || now > lastMouseMoveTime + 350) { highlightItemUnderMouse (globalMousePos, localMousePos); } bool overScrollArea = false; if (isScrolling() && (isOver || (isDown && ((unsigned int) localMousePos.getX()) < (unsigned int) getWidth())) && ((isScrollZoneActive (false) && localMousePos.getY() < PopupMenuSettings::scrollZone) || (isScrollZoneActive (true) && localMousePos.getY() > getHeight() - PopupMenuSettings::scrollZone))) { if (now > lastScroll + 20) { scrollAcceleration = jmin (4.0, scrollAcceleration * 1.04); int amount = 0; for (int i = 0; i < getNumChildComponents() && amount == 0; ++i) amount = ((int) scrollAcceleration) * getChildComponent (i)->getHeight(); alterChildYPos (localMousePos.getY() < PopupMenuSettings::scrollZone ? -amount : amount); lastScroll = now; } overScrollArea = true; lastMouse = Point (-1, -1); // trigger a mouse-move } else { scrollAcceleration = 1.0; } const bool wasDown = isDown; bool isOverAny = isOverAnyMenu(); if (hideOnExit && hasBeenOver && (! isOverAny) && activeSubMenu != 0) { activeSubMenu->updateMouseOverStatus (globalMousePos); isOverAny = isOverAnyMenu(); } if (hideOnExit && hasBeenOver && ! isOverAny) { hide (0); } else { isDown = hasBeenOver && (ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown() || ModifierKeys::getCurrentModifiersRealtime().isAnyMouseButtonDown()); bool anyFocused = Process::isForegroundProcess(); if (anyFocused && Component::getCurrentlyFocusedComponent() == 0) { // because no component at all may have focus, our test here will // only be triggered when something has focus and then loses it. anyFocused = ! hasAnyJuceCompHadFocus; for (int i = ComponentPeer::getNumPeers(); --i >= 0;) { if (ComponentPeer::getPeer (i)->isFocused()) { anyFocused = true; hasAnyJuceCompHadFocus = true; break; } } } if (! anyFocused) { if (now > lastFocused + 10) { wasHiddenBecauseOfAppChange() = true; dismissMenu (0); return; // may have been deleted by the previous call.. } } else if (wasDown && now > menuCreationTime + 250 && ! (isDown || overScrollArea)) { isOver = reallyContains (localMousePos.getX(), localMousePos.getY(), true); if (isOver) { triggerCurrentlyHighlightedItem(); } else if ((hasBeenOver || ! dismissOnMouseUp) && ! isOverAny) { dismissMenu (0); } return; // may have been deleted by the previous calls.. } else { lastFocused = now; } } } static Array& getActiveWindows() { static Array activeMenuWindows; return activeMenuWindows; } static bool& wasHiddenBecauseOfAppChange() throw() { static bool b = false; return b; } juce_UseDebuggingNewOperator private: Window* owner; PopupMenu::ItemComponent* currentChild; ScopedPointer activeSubMenu; ApplicationCommandManager** managerOfChosenCommand; Component::SafePointer componentAttachedTo; Component* componentAttachedToOriginal; Rectangle windowPos; Point lastMouse; int minimumWidth, maximumNumColumns, standardItemHeight; bool isOver, hasBeenOver, isDown, needsToScroll; bool dismissOnMouseUp, hideOnExit, disableMouseMoves, hasAnyJuceCompHadFocus; int numColumns, contentHeight, childYOffset; Array columnWidths; uint32 menuCreationTime, lastFocused, lastScroll, lastMouseMoveTime, timeEnteredCurrentChildComp; double scrollAcceleration; bool overlaps (const Rectangle& r) const { return r.intersects (getBounds()) || (owner != 0 && owner->overlaps (r)); } bool isOverAnyMenu() const { return (owner != 0) ? owner->isOverAnyMenu() : isOverChildren(); } bool isOverChildren() const { jassert (activeSubMenu == 0 || activeSubMenu->isValidComponent()); return isVisible() && (isOver || (activeSubMenu != 0 && activeSubMenu->isOverChildren())); } void updateMouseOverStatus (const Point& globalMousePos) { const Point relPos (globalPositionToRelative (globalMousePos)); isOver = reallyContains (relPos.getX(), relPos.getY(), true); if (activeSubMenu != 0) activeSubMenu->updateMouseOverStatus (globalMousePos); } bool treeContains (const Window* const window) const throw() { const Window* mw = this; while (mw->owner != 0) mw = mw->owner; while (mw != 0) { if (mw == window) return true; mw = mw->activeSubMenu; } return false; } void calculateWindowPos (const Rectangle& target, const bool alignToRectangle) { const Rectangle mon (Desktop::getInstance() .getMonitorAreaContaining (target.getCentre(), #if JUCE_MAC true)); #else false)); // on windows, don't stop the menu overlapping the taskbar #endif int x, y, widthToUse, heightToUse; layoutMenuItems (mon.getWidth() - 24, widthToUse, heightToUse); if (alignToRectangle) { x = target.getX(); const int spaceUnder = mon.getHeight() - (target.getBottom() - mon.getY()); const int spaceOver = target.getY() - mon.getY(); if (heightToUse < spaceUnder - 30 || spaceUnder >= spaceOver) y = target.getBottom(); else y = target.getY() - heightToUse; } else { bool tendTowardsRight = target.getCentreX() < mon.getCentreX(); if (owner != 0) { if (owner->owner != 0) { const bool ownerGoingRight = (owner->getX() + owner->getWidth() / 2 > owner->owner->getX() + owner->owner->getWidth() / 2); if (ownerGoingRight && target.getRight() + widthToUse < mon.getRight() - 4) tendTowardsRight = true; else if ((! ownerGoingRight) && target.getX() > widthToUse + 4) tendTowardsRight = false; } else if (target.getRight() + widthToUse < mon.getRight() - 32) { tendTowardsRight = true; } } const int biggestSpace = jmax (mon.getRight() - target.getRight(), target.getX() - mon.getX()) - 32; if (biggestSpace < widthToUse) { layoutMenuItems (biggestSpace + target.getWidth() / 3, widthToUse, heightToUse); if (numColumns > 1) layoutMenuItems (biggestSpace - 4, widthToUse, heightToUse); tendTowardsRight = (mon.getRight() - target.getRight()) >= (target.getX() - mon.getX()); } if (tendTowardsRight) x = jmin (mon.getRight() - widthToUse - 4, target.getRight()); else x = jmax (mon.getX() + 4, target.getX() - widthToUse); y = target.getY(); if (target.getCentreY() > mon.getCentreY()) y = jmax (mon.getY(), target.getBottom() - heightToUse); } x = jmax (mon.getX() + 1, jmin (mon.getRight() - (widthToUse + 6), x)); y = jmax (mon.getY() + 1, jmin (mon.getBottom() - (heightToUse + 6), y)); windowPos.setBounds (x, y, widthToUse, heightToUse); // sets this flag if it's big enough to obscure any of its parent menus hideOnExit = (owner != 0) && owner->windowPos.intersects (windowPos.expanded (-4, -4)); } void layoutMenuItems (const int maxMenuW, int& width, int& height) { numColumns = 0; contentHeight = 0; const int maxMenuH = getParentHeight() - 24; int totalW; do { ++numColumns; totalW = workOutBestSize (maxMenuW); if (totalW > maxMenuW) { numColumns = jmax (1, numColumns - 1); totalW = workOutBestSize (maxMenuW); // to update col widths break; } else if (totalW > maxMenuW / 2 || contentHeight < maxMenuH) { break; } } while (numColumns < maximumNumColumns); const int actualH = jmin (contentHeight, maxMenuH); needsToScroll = contentHeight > actualH; width = updateYPositions(); height = actualH + PopupMenuSettings::borderSize * 2; } int workOutBestSize (const int maxMenuW) { int totalW = 0; contentHeight = 0; int childNum = 0; for (int col = 0; col < numColumns; ++col) { int i, colW = 50, colH = 0; const int numChildren = jmin (getNumChildComponents() - childNum, (getNumChildComponents() + numColumns - 1) / numColumns); for (i = numChildren; --i >= 0;) { colW = jmax (colW, getChildComponent (childNum + i)->getWidth()); colH += getChildComponent (childNum + i)->getHeight(); } colW = jmin (maxMenuW / jmax (1, numColumns - 2), colW + PopupMenuSettings::borderSize * 2); columnWidths.set (col, colW); totalW += colW; contentHeight = jmax (contentHeight, colH); childNum += numChildren; } if (totalW < minimumWidth) { totalW = minimumWidth; for (int col = 0; col < numColumns; ++col) columnWidths.set (0, totalW / numColumns); } return totalW; } void ensureItemIsVisible (const int itemId, int wantedY) { jassert (itemId != 0) for (int i = getNumChildComponents(); --i >= 0;) { PopupMenu::ItemComponent* const m = static_cast (getChildComponent (i)); if (m != 0 && m->itemInfo.itemId == itemId && windowPos.getHeight() > PopupMenuSettings::scrollZone * 4) { const int currentY = m->getY(); if (wantedY > 0 || currentY < 0 || m->getBottom() > windowPos.getHeight()) { if (wantedY < 0) wantedY = jlimit (PopupMenuSettings::scrollZone, jmax (PopupMenuSettings::scrollZone, windowPos.getHeight() - (PopupMenuSettings::scrollZone + m->getHeight())), currentY); const Rectangle mon (Desktop::getInstance().getMonitorAreaContaining (windowPos.getPosition(), true)); int deltaY = wantedY - currentY; windowPos.setSize (jmin (windowPos.getWidth(), mon.getWidth()), jmin (windowPos.getHeight(), mon.getHeight())); const int newY = jlimit (mon.getY(), mon.getBottom() - windowPos.getHeight(), windowPos.getY() + deltaY); deltaY -= newY - windowPos.getY(); childYOffset -= deltaY; windowPos.setPosition (windowPos.getX(), newY); updateYPositions(); } break; } } } void resizeToBestWindowPos() { Rectangle r (windowPos); if (childYOffset < 0) { r.setBounds (r.getX(), r.getY() - childYOffset, r.getWidth(), r.getHeight() + childYOffset); } else if (childYOffset > 0) { const int spaceAtBottom = r.getHeight() - (contentHeight - childYOffset); if (spaceAtBottom > 0) r.setSize (r.getWidth(), r.getHeight() - spaceAtBottom); } setBounds (r); updateYPositions(); } void alterChildYPos (const int delta) { if (isScrolling()) { childYOffset += delta; if (delta < 0) { childYOffset = jmax (childYOffset, 0); } else if (delta > 0) { childYOffset = jmin (childYOffset, contentHeight - windowPos.getHeight() + PopupMenuSettings::borderSize); } updateYPositions(); } else { childYOffset = 0; } resizeToBestWindowPos(); repaint(); } int updateYPositions() { int x = 0; int childNum = 0; for (int col = 0; col < numColumns; ++col) { const int numChildren = jmin (getNumChildComponents() - childNum, (getNumChildComponents() + numColumns - 1) / numColumns); const int colW = columnWidths [col]; int y = PopupMenuSettings::borderSize - (childYOffset + (getY() - windowPos.getY())); for (int i = 0; i < numChildren; ++i) { Component* const c = getChildComponent (childNum + i); c->setBounds (x, y, colW, c->getHeight()); y += c->getHeight(); } x += colW; childNum += numChildren; } return x; } bool isScrolling() const throw() { return childYOffset != 0 || needsToScroll; } void setCurrentlyHighlightedChild (PopupMenu::ItemComponent* const child) { if (currentChild->isValidComponent()) currentChild->setHighlighted (false); currentChild = child; if (currentChild != 0) { currentChild->setHighlighted (true); timeEnteredCurrentChildComp = Time::getApproximateMillisecondCounter(); } } bool showSubMenuFor (PopupMenu::ItemComponent* const childComp) { jassert (activeSubMenu == 0 || activeSubMenu->isValidComponent()); activeSubMenu = 0; if (childComp->isValidComponent() && childComp->itemInfo.hasActiveSubMenu()) { activeSubMenu = Window::create (*(childComp->itemInfo.subMenu), dismissOnMouseUp, this, childComp->getScreenBounds(), 0, maximumNumColumns, standardItemHeight, false, 0, managerOfChosenCommand, componentAttachedTo); if (activeSubMenu != 0) { activeSubMenu->setVisible (true); activeSubMenu->enterModalState (false); activeSubMenu->toFront (false); return true; } } return false; } void highlightItemUnderMouse (const Point& globalMousePos, const Point& localMousePos) { isOver = reallyContains (localMousePos.getX(), localMousePos.getY(), true); if (isOver) hasBeenOver = true; if (lastMouse.getDistanceFrom (globalMousePos) > 2) { lastMouseMoveTime = Time::getApproximateMillisecondCounter(); if (disableMouseMoves && isOver) disableMouseMoves = false; } if (disableMouseMoves || (activeSubMenu != 0 && activeSubMenu->isOverChildren())) return; bool isMovingTowardsMenu = false; jassert (activeSubMenu == 0 || activeSubMenu->isValidComponent()) if (isOver && (activeSubMenu != 0) && globalMousePos != lastMouse) { // try to intelligently guess whether the user is moving the mouse towards a currently-open // submenu. To do this, look at whether the mouse stays inside a triangular region that // extends from the last mouse pos to the submenu's rectangle.. float subX = (float) activeSubMenu->getScreenX(); if (activeSubMenu->getX() > getX()) { lastMouse -= Point (2, 0); // to enlarge the triangle a bit, in case the mouse only moves a couple of pixels } else { lastMouse += Point (2, 0); subX += activeSubMenu->getWidth(); } Path areaTowardsSubMenu; areaTowardsSubMenu.addTriangle ((float) lastMouse.getX(), (float) lastMouse.getY(), subX, (float) activeSubMenu->getScreenY(), subX, (float) (activeSubMenu->getScreenY() + activeSubMenu->getHeight())); isMovingTowardsMenu = areaTowardsSubMenu.contains ((float) globalMousePos.getX(), (float) globalMousePos.getY()); } lastMouse = globalMousePos; if (! isMovingTowardsMenu) { Component* c = getComponentAt (localMousePos.getX(), localMousePos.getY()); if (c == this) c = 0; PopupMenu::ItemComponent* mic = dynamic_cast (c); if (mic == 0 && c != 0) mic = c->findParentComponentOfClass ((PopupMenu::ItemComponent*) 0); if (mic != currentChild && (isOver || (activeSubMenu == 0) || ! activeSubMenu->isVisible())) { if (isOver && (c != 0) && (activeSubMenu != 0)) { activeSubMenu->hide (0); } if (! isOver) mic = 0; setCurrentlyHighlightedChild (mic); } } } void triggerCurrentlyHighlightedItem() { if (currentChild->isValidComponent() && currentChild->itemInfo.canBeTriggered() && (currentChild->itemInfo.customComp == 0 || currentChild->itemInfo.customComp->isTriggeredAutomatically)) { dismissMenu (¤tChild->itemInfo); } } void selectNextItem (const int delta) { disableTimerUntilMouseMoves(); PopupMenu::ItemComponent* mic = 0; bool wasLastOne = (currentChild == 0); const int numItems = getNumChildComponents(); for (int i = 0; i < numItems + 1; ++i) { int index = (delta > 0) ? i : (numItems - 1 - i); index = (index + numItems) % numItems; mic = dynamic_cast (getChildComponent (index)); if (mic != 0 && (mic->itemInfo.canBeTriggered() || mic->itemInfo.hasActiveSubMenu()) && wasLastOne) break; if (mic == currentChild) wasLastOne = true; } setCurrentlyHighlightedChild (mic); } void disableTimerUntilMouseMoves() { disableMouseMoves = true; if (owner != 0) owner->disableTimerUntilMouseMoves(); } Window (const Window&); Window& operator= (const Window&); }; PopupMenu::PopupMenu() : lookAndFeel (0), separatorPending (false) { } PopupMenu::PopupMenu (const PopupMenu& other) : lookAndFeel (other.lookAndFeel), separatorPending (false) { items.addCopiesOf (other.items); } PopupMenu& PopupMenu::operator= (const PopupMenu& other) { if (this != &other) { lookAndFeel = other.lookAndFeel; clear(); items.addCopiesOf (other.items); } return *this; } PopupMenu::~PopupMenu() { clear(); } void PopupMenu::clear() { items.clear(); separatorPending = false; } void PopupMenu::addSeparatorIfPending() { if (separatorPending) { separatorPending = false; if (items.size() > 0) items.add (new Item()); } } void PopupMenu::addItem (const int itemResultId, const String& itemText, const bool isActive, const bool isTicked, const Image& iconToUse) { jassert (itemResultId != 0); // 0 is used as a return value to indicate that the user // didn't pick anything, so you shouldn't use it as the id // for an item.. addSeparatorIfPending(); items.add (new Item (itemResultId, itemText, isActive, isTicked, iconToUse, Colours::black, false, 0, 0, 0)); } void PopupMenu::addCommandItem (ApplicationCommandManager* commandManager, const int commandID, const String& displayName) { jassert (commandManager != 0 && commandID != 0); const ApplicationCommandInfo* const registeredInfo = commandManager->getCommandForID (commandID); if (registeredInfo != 0) { ApplicationCommandInfo info (*registeredInfo); ApplicationCommandTarget* const target = commandManager->getTargetForCommand (commandID, info); addSeparatorIfPending(); items.add (new Item (commandID, displayName.isNotEmpty() ? displayName : info.shortName, target != 0 && (info.flags & ApplicationCommandInfo::isDisabled) == 0, (info.flags & ApplicationCommandInfo::isTicked) != 0, Image::null, Colours::black, false, 0, 0, commandManager)); } } void PopupMenu::addColouredItem (const int itemResultId, const String& itemText, const Colour& itemTextColour, const bool isActive, const bool isTicked, const Image& iconToUse) { jassert (itemResultId != 0); // 0 is used as a return value to indicate that the user // didn't pick anything, so you shouldn't use it as the id // for an item.. addSeparatorIfPending(); items.add (new Item (itemResultId, itemText, isActive, isTicked, iconToUse, itemTextColour, true, 0, 0, 0)); } void PopupMenu::addCustomItem (const int itemResultId, PopupMenuCustomComponent* const customComponent) { jassert (itemResultId != 0); // 0 is used as a return value to indicate that the user // didn't pick anything, so you shouldn't use it as the id // for an item.. addSeparatorIfPending(); items.add (new Item (itemResultId, String::empty, true, false, Image::null, Colours::black, false, customComponent, 0, 0)); } class NormalComponentWrapper : public PopupMenuCustomComponent { public: NormalComponentWrapper (Component* const comp, const int w, const int h, const bool triggerMenuItemAutomaticallyWhenClicked) : PopupMenuCustomComponent (triggerMenuItemAutomaticallyWhenClicked), width (w), height (h) { addAndMakeVisible (comp); } ~NormalComponentWrapper() {} void getIdealSize (int& idealWidth, int& idealHeight) { idealWidth = width; idealHeight = height; } void resized() { if (getChildComponent(0) != 0) getChildComponent(0)->setBounds (getLocalBounds()); } juce_UseDebuggingNewOperator private: const int width, height; NormalComponentWrapper (const NormalComponentWrapper&); NormalComponentWrapper& operator= (const NormalComponentWrapper&); }; void PopupMenu::addCustomItem (const int itemResultId, Component* customComponent, int idealWidth, int idealHeight, const bool triggerMenuItemAutomaticallyWhenClicked) { addCustomItem (itemResultId, new NormalComponentWrapper (customComponent, idealWidth, idealHeight, triggerMenuItemAutomaticallyWhenClicked)); } void PopupMenu::addSubMenu (const String& subMenuName, const PopupMenu& subMenu, const bool isActive, const Image& iconToUse, const bool isTicked) { addSeparatorIfPending(); items.add (new Item (0, subMenuName, isActive && (subMenu.getNumItems() > 0), isTicked, iconToUse, Colours::black, false, 0, &subMenu, 0)); } void PopupMenu::addSeparator() { separatorPending = true; } class HeaderItemComponent : public PopupMenuCustomComponent { public: HeaderItemComponent (const String& name) : PopupMenuCustomComponent (false) { setName (name); } ~HeaderItemComponent() { } void paint (Graphics& g) { Font f (getLookAndFeel().getPopupMenuFont()); f.setBold (true); g.setFont (f); g.setColour (findColour (PopupMenu::headerTextColourId)); g.drawFittedText (getName(), 12, 0, getWidth() - 16, proportionOfHeight (0.8f), Justification::bottomLeft, 1); } void getIdealSize (int& idealWidth, int& idealHeight) { getLookAndFeel().getIdealPopupMenuItemSize (getName(), false, -1, idealWidth, idealHeight); idealHeight += idealHeight / 2; idealWidth += idealWidth / 4; } juce_UseDebuggingNewOperator }; void PopupMenu::addSectionHeader (const String& title) { addCustomItem (0X4734a34f, new HeaderItemComponent (title)); } // This invokes any command manager commands and deletes the menu window when it is dismissed class PopupMenuCompletionCallback : public ModalComponentManager::Callback { public: PopupMenuCompletionCallback() : managerOfChosenCommand (0) { } ~PopupMenuCompletionCallback() {} void modalStateFinished (int result) { if (managerOfChosenCommand != 0 && result != 0) { ApplicationCommandTarget::InvocationInfo info (result); info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu; managerOfChosenCommand->invoke (info, true); } } ApplicationCommandManager* managerOfChosenCommand; ScopedPointer component; private: PopupMenuCompletionCallback (const PopupMenuCompletionCallback&); PopupMenuCompletionCallback& operator= (const PopupMenuCompletionCallback&); }; int PopupMenu::showMenu (const Rectangle& target, const int itemIdThatMustBeVisible, const int minimumWidth, const int maximumNumColumns, const int standardItemHeight, const bool alignToRectangle, Component* const componentAttachedTo, ModalComponentManager::Callback* userCallback) { ScopedPointer userCallbackDeleter (userCallback); Component::SafePointer prevFocused (Component::getCurrentlyFocusedComponent()); Component::SafePointer prevTopLevel ((prevFocused != 0) ? prevFocused->getTopLevelComponent() : 0); Window::wasHiddenBecauseOfAppChange() = false; PopupMenuCompletionCallback* callback = new PopupMenuCompletionCallback(); ScopedPointer callbackDeleter (callback); callback->component = Window::create (*this, ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown(), 0, target, minimumWidth, maximumNumColumns > 0 ? maximumNumColumns : 7, standardItemHeight, alignToRectangle, itemIdThatMustBeVisible, &callback->managerOfChosenCommand, componentAttachedTo); if (callback->component == 0) return 0; callbackDeleter.release(); callback->component->enterModalState (false, userCallbackDeleter.release()); callback->component->toFront (false); // need to do this after making it modal, or it could // be stuck behind other comps that are already modal.. ModalComponentManager::getInstance()->attachCallback (callback->component, callback); if (userCallback != 0) return 0; const int result = callback->component->runModalLoop(); if (! Window::wasHiddenBecauseOfAppChange()) { if (prevTopLevel != 0) prevTopLevel->toFront (true); if (prevFocused != 0) prevFocused->grabKeyboardFocus(); } return result; } int PopupMenu::show (const int itemIdThatMustBeVisible, const int minimumWidth, const int maximumNumColumns, const int standardItemHeight, ModalComponentManager::Callback* callback) { const Point mousePos (Desktop::getMousePosition()); return showAt (mousePos.getX(), mousePos.getY(), itemIdThatMustBeVisible, minimumWidth, maximumNumColumns, standardItemHeight, callback); } int PopupMenu::showAt (const int screenX, const int screenY, const int itemIdThatMustBeVisible, const int minimumWidth, const int maximumNumColumns, const int standardItemHeight, ModalComponentManager::Callback* callback) { return showMenu (Rectangle (screenX, screenY, 1, 1), itemIdThatMustBeVisible, minimumWidth, maximumNumColumns, standardItemHeight, false, 0, callback); } int PopupMenu::showAt (Component* componentToAttachTo, const int itemIdThatMustBeVisible, const int minimumWidth, const int maximumNumColumns, const int standardItemHeight, ModalComponentManager::Callback* callback) { if (componentToAttachTo != 0) { return showMenu (componentToAttachTo->getScreenBounds(), itemIdThatMustBeVisible, minimumWidth, maximumNumColumns, standardItemHeight, true, componentToAttachTo, callback); } else { return show (itemIdThatMustBeVisible, minimumWidth, maximumNumColumns, standardItemHeight, callback); } } void JUCE_CALLTYPE PopupMenu::dismissAllActiveMenus() { for (int i = Window::getActiveWindows().size(); --i >= 0;) { Window* const pmw = Window::getActiveWindows()[i]; if (pmw != 0) pmw->dismissMenu (0); } } int PopupMenu::getNumItems() const throw() { int num = 0; for (int i = items.size(); --i >= 0;) if (! (items.getUnchecked(i))->isSeparator) ++num; return num; } bool PopupMenu::containsCommandItem (const int commandID) const { for (int i = items.size(); --i >= 0;) { const Item* mi = items.getUnchecked (i); if ((mi->itemId == commandID && mi->commandManager != 0) || (mi->subMenu != 0 && mi->subMenu->containsCommandItem (commandID))) { return true; } } return false; } bool PopupMenu::containsAnyActiveItems() const throw() { for (int i = items.size(); --i >= 0;) { const Item* const mi = items.getUnchecked (i); if (mi->subMenu != 0) { if (mi->subMenu->containsAnyActiveItems()) return true; } else if (mi->active) { return true; } } return false; } void PopupMenu::setLookAndFeel (LookAndFeel* const newLookAndFeel) { lookAndFeel = newLookAndFeel; } PopupMenuCustomComponent::PopupMenuCustomComponent (const bool isTriggeredAutomatically_) : isHighlighted (false), isTriggeredAutomatically (isTriggeredAutomatically_) { } PopupMenuCustomComponent::~PopupMenuCustomComponent() { } void PopupMenuCustomComponent::triggerMenuItem() { PopupMenu::ItemComponent* const mic = dynamic_cast (getParentComponent()); if (mic != 0) { PopupMenu::Window* const pmw = dynamic_cast (mic->getParentComponent()); if (pmw != 0) { pmw->dismissMenu (&mic->itemInfo); } else { // something must have gone wrong with the component hierarchy if this happens.. jassertfalse; } } else { // why isn't this component inside a menu? Not much point triggering the item if // there's no menu. jassertfalse; } } PopupMenu::MenuItemIterator::MenuItemIterator (const PopupMenu& menu_) : subMenu (0), itemId (0), isSeparator (false), isTicked (false), isEnabled (false), isCustomComponent (false), isSectionHeader (false), customColour (0), customImage (0), menu (menu_), index (0) { } PopupMenu::MenuItemIterator::~MenuItemIterator() { } bool PopupMenu::MenuItemIterator::next() { if (index >= menu.items.size()) return false; const Item* const item = menu.items.getUnchecked (index); ++index; itemName = item->customComp != 0 ? item->customComp->getName() : item->text; subMenu = item->subMenu; itemId = item->itemId; isSeparator = item->isSeparator; isTicked = item->isTicked; isEnabled = item->active; isSectionHeader = dynamic_cast ((PopupMenuCustomComponent*) item->customComp) != 0; isCustomComponent = (! isSectionHeader) && item->customComp != 0; customColour = item->usesColour ? &(item->textColour) : 0; customImage = item->image; commandManager = item->commandManager; return true; } END_JUCE_NAMESPACE /*** End of inlined file: juce_PopupMenu.cpp ***/ /*** Start of inlined file: juce_ComponentDragger.cpp ***/ BEGIN_JUCE_NAMESPACE ComponentDragger::ComponentDragger() : constrainer (0) { } ComponentDragger::~ComponentDragger() { } void ComponentDragger::startDraggingComponent (Component* const componentToDrag, ComponentBoundsConstrainer* const constrainer_) { jassert (componentToDrag->isValidComponent()); if (componentToDrag != 0) { constrainer = constrainer_; originalPos = componentToDrag->relativePositionToGlobal (Point()); } } void ComponentDragger::dragComponent (Component* const componentToDrag, const MouseEvent& e) { jassert (componentToDrag->isValidComponent()); jassert (e.mods.isAnyMouseButtonDown()); // (the event has to be a drag event..) if (componentToDrag != 0) { Rectangle bounds (componentToDrag->getBounds().withPosition (originalPos)); const Component* const parentComp = componentToDrag->getParentComponent(); if (parentComp != 0) bounds.setPosition (parentComp->globalPositionToRelative (originalPos)); bounds.setPosition (bounds.getPosition() + e.getOffsetFromDragStart()); if (constrainer != 0) constrainer->setBoundsForComponent (componentToDrag, bounds, false, false, false, false); else componentToDrag->setBounds (bounds); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ComponentDragger.cpp ***/ /*** Start of inlined file: juce_DragAndDropContainer.cpp ***/ BEGIN_JUCE_NAMESPACE bool juce_performDragDropFiles (const StringArray& files, const bool copyFiles, bool& shouldStop); bool juce_performDragDropText (const String& text, bool& shouldStop); class DragImageComponent : public Component, public Timer { public: DragImageComponent (const Image& im, const String& desc, Component* const sourceComponent, Component* const mouseDragSource_, DragAndDropContainer* const o, const Point& imageOffset_) : image (im), source (sourceComponent), mouseDragSource (mouseDragSource_), owner (o), dragDesc (desc), imageOffset (imageOffset_), hasCheckedForExternalDrag (false), drawImage (true) { setSize (im.getWidth(), im.getHeight()); if (mouseDragSource == 0) mouseDragSource = source; mouseDragSource->addMouseListener (this, false); startTimer (200); setInterceptsMouseClicks (false, false); setAlwaysOnTop (true); } ~DragImageComponent() { if (owner->dragImageComponent == this) owner->dragImageComponent.release(); if (mouseDragSource != 0) { mouseDragSource->removeMouseListener (this); if (getCurrentlyOver() != 0 && getCurrentlyOver()->isInterestedInDragSource (dragDesc, source)) getCurrentlyOver()->itemDragExit (dragDesc, source); } } void paint (Graphics& g) { if (isOpaque()) g.fillAll (Colours::white); if (drawImage) { g.setOpacity (1.0f); g.drawImageAt (image, 0, 0); } } DragAndDropTarget* findTarget (const Point& screenPos, Point& relativePos) { Component* hit = getParentComponent(); if (hit == 0) { hit = Desktop::getInstance().findComponentAt (screenPos); } else { const Point relPos (hit->globalPositionToRelative (screenPos)); hit = hit->getComponentAt (relPos.getX(), relPos.getY()); } // (note: use a local copy of the dragDesc member in case the callback runs // a modal loop and deletes this object before the method completes) const String dragDescLocal (dragDesc); while (hit != 0) { DragAndDropTarget* const ddt = dynamic_cast (hit); if (ddt != 0 && ddt->isInterestedInDragSource (dragDescLocal, source)) { relativePos = hit->globalPositionToRelative (screenPos); return ddt; } hit = hit->getParentComponent(); } return 0; } void mouseUp (const MouseEvent& e) { if (e.originalComponent != this) { if (mouseDragSource != 0) mouseDragSource->removeMouseListener (this); bool dropAccepted = false; DragAndDropTarget* ddt = 0; Point relPos; if (isVisible()) { setVisible (false); ddt = findTarget (e.getScreenPosition(), relPos); // fade this component and remove it - it'll be deleted later by the timer callback dropAccepted = ddt != 0; setVisible (true); if (dropAccepted || source == 0) { fadeOutComponent (120); } else { const Point target (source->relativePositionToGlobal (Point (source->getWidth() / 2, source->getHeight() / 2))); const Point ourCentre (relativePositionToGlobal (Point (getWidth() / 2, getHeight() / 2))); fadeOutComponent (120, target.getX() - ourCentre.getX(), target.getY() - ourCentre.getY()); } } if (getParentComponent() != 0) getParentComponent()->removeChildComponent (this); if (dropAccepted && ddt != 0) { // (note: use a local copy of the dragDesc member in case the callback runs // a modal loop and deletes this object before the method completes) const String dragDescLocal (dragDesc); currentlyOverComp = 0; ddt->itemDropped (dragDescLocal, source, relPos.getX(), relPos.getY()); } // careful - this object could now be deleted.. } } void updateLocation (const bool canDoExternalDrag, const Point& screenPos) { // (note: use a local copy of the dragDesc member in case the callback runs // a modal loop and deletes this object before it returns) const String dragDescLocal (dragDesc); Point newPos (screenPos + imageOffset); if (getParentComponent() != 0) newPos = getParentComponent()->globalPositionToRelative (newPos); //if (newX != getX() || newY != getY()) { setTopLeftPosition (newPos.getX(), newPos.getY()); Point relPos; DragAndDropTarget* const ddt = findTarget (screenPos, relPos); Component* ddtComp = dynamic_cast (ddt); drawImage = (ddt == 0) || ddt->shouldDrawDragImageWhenOver(); if (ddtComp != currentlyOverComp) { if (currentlyOverComp != 0 && source != 0 && getCurrentlyOver()->isInterestedInDragSource (dragDescLocal, source)) { getCurrentlyOver()->itemDragExit (dragDescLocal, source); } currentlyOverComp = ddtComp; if (ddt != 0 && ddt->isInterestedInDragSource (dragDescLocal, source)) ddt->itemDragEnter (dragDescLocal, source, relPos.getX(), relPos.getY()); } DragAndDropTarget* target = getCurrentlyOver(); if (target != 0 && target->isInterestedInDragSource (dragDescLocal, source)) target->itemDragMove (dragDescLocal, source, relPos.getX(), relPos.getY()); if (getCurrentlyOver() == 0 && canDoExternalDrag && ! hasCheckedForExternalDrag) { if (Desktop::getInstance().findComponentAt (screenPos) == 0) { hasCheckedForExternalDrag = true; StringArray files; bool canMoveFiles = false; if (owner->shouldDropFilesWhenDraggedExternally (dragDescLocal, source, files, canMoveFiles) && files.size() > 0) { Component::SafePointer cdw (this); setVisible (false); if (ModifierKeys::getCurrentModifiersRealtime().isAnyMouseButtonDown()) DragAndDropContainer::performExternalDragDropOfFiles (files, canMoveFiles); if (cdw != 0) delete this; return; } } } } } void mouseDrag (const MouseEvent& e) { if (e.originalComponent != this) updateLocation (true, e.getScreenPosition()); } void timerCallback() { if (source == 0) { delete this; } else if (! isMouseButtonDownAnywhere()) { if (mouseDragSource != 0) mouseDragSource->removeMouseListener (this); delete this; } } private: Image image; Component::SafePointer source; Component::SafePointer mouseDragSource; DragAndDropContainer* const owner; Component::SafePointer currentlyOverComp; DragAndDropTarget* getCurrentlyOver() { return dynamic_cast (static_cast (currentlyOverComp)); } String dragDesc; const Point imageOffset; bool hasCheckedForExternalDrag, drawImage; DragImageComponent (const DragImageComponent&); DragImageComponent& operator= (const DragImageComponent&); }; DragAndDropContainer::DragAndDropContainer() { } DragAndDropContainer::~DragAndDropContainer() { dragImageComponent = 0; } void DragAndDropContainer::startDragging (const String& sourceDescription, Component* sourceComponent, const Image& dragImage_, const bool allowDraggingToExternalWindows, const Point* imageOffsetFromMouse) { Image dragImage (dragImage_); if (dragImageComponent == 0) { Component* const thisComp = dynamic_cast (this); if (thisComp == 0) { jassertfalse; // Your DragAndDropContainer needs to be a Component! return; } MouseInputSource* draggingSource = Desktop::getInstance().getDraggingMouseSource (0); if (draggingSource == 0 || ! draggingSource->isDragging()) { jassertfalse; // You must call startDragging() from within a mouseDown or mouseDrag callback! return; } const Point lastMouseDown (Desktop::getLastMouseDownPosition()); Point imageOffset; if (dragImage.isNull()) { dragImage = sourceComponent->createComponentSnapshot (sourceComponent->getLocalBounds()) .convertedToFormat (Image::ARGB); dragImage.multiplyAllAlphas (0.6f); const int lo = 150; const int hi = 400; Point relPos (sourceComponent->globalPositionToRelative (lastMouseDown)); Point clipped (dragImage.getBounds().getConstrainedPoint (relPos)); for (int y = dragImage.getHeight(); --y >= 0;) { const double dy = (y - clipped.getY()) * (y - clipped.getY()); for (int x = dragImage.getWidth(); --x >= 0;) { const int dx = x - clipped.getX(); const int distance = roundToInt (std::sqrt (dx * dx + dy)); if (distance > lo) { const float alpha = (distance > hi) ? 0 : (hi - distance) / (float) (hi - lo) + Random::getSystemRandom().nextFloat() * 0.008f; dragImage.multiplyAlphaAt (x, y, alpha); } } } imageOffset = -clipped; } else { if (imageOffsetFromMouse == 0) imageOffset = -dragImage.getBounds().getCentre(); else imageOffset = -(dragImage.getBounds().getConstrainedPoint (-*imageOffsetFromMouse)); } dragImageComponent = new DragImageComponent (dragImage, sourceDescription, sourceComponent, draggingSource->getComponentUnderMouse(), this, imageOffset); currentDragDesc = sourceDescription; if (allowDraggingToExternalWindows) { if (! Desktop::canUseSemiTransparentWindows()) dragImageComponent->setOpaque (true); dragImageComponent->addToDesktop (ComponentPeer::windowIgnoresMouseClicks | ComponentPeer::windowIsTemporary | ComponentPeer::windowIgnoresKeyPresses); } else thisComp->addChildComponent (dragImageComponent); static_cast (static_cast (dragImageComponent))->updateLocation (false, lastMouseDown); dragImageComponent->setVisible (true); } } bool DragAndDropContainer::isDragAndDropActive() const { return dragImageComponent != 0; } const String DragAndDropContainer::getCurrentDragDescription() const { return (dragImageComponent != 0) ? currentDragDesc : String::empty; } DragAndDropContainer* DragAndDropContainer::findParentDragContainerFor (Component* c) { return c == 0 ? 0 : c->findParentComponentOfClass ((DragAndDropContainer*) 0); } bool DragAndDropContainer::shouldDropFilesWhenDraggedExternally (const String&, Component*, StringArray&, bool&) { return false; } void DragAndDropTarget::itemDragEnter (const String&, Component*, int, int) { } void DragAndDropTarget::itemDragMove (const String&, Component*, int, int) { } void DragAndDropTarget::itemDragExit (const String&, Component*) { } bool DragAndDropTarget::shouldDrawDragImageWhenOver() { return true; } void FileDragAndDropTarget::fileDragEnter (const StringArray&, int, int) { } void FileDragAndDropTarget::fileDragMove (const StringArray&, int, int) { } void FileDragAndDropTarget::fileDragExit (const StringArray&) { } END_JUCE_NAMESPACE /*** End of inlined file: juce_DragAndDropContainer.cpp ***/ /*** Start of inlined file: juce_MouseCursor.cpp ***/ BEGIN_JUCE_NAMESPACE class MouseCursor::SharedCursorHandle { public: explicit SharedCursorHandle (const MouseCursor::StandardCursorType type) : handle (createStandardMouseCursor (type)), refCount (1), standardType (type), isStandard (true) { } SharedCursorHandle (const Image& image, const int hotSpotX, const int hotSpotY) : handle (createMouseCursorFromImage (image, hotSpotX, hotSpotY)), refCount (1), standardType (MouseCursor::NormalCursor), isStandard (false) { } static SharedCursorHandle* createStandard (const MouseCursor::StandardCursorType type) { const ScopedLock sl (getLock()); for (int i = 0; i < getCursors().size(); ++i) { SharedCursorHandle* const sc = getCursors().getUnchecked(i); if (sc->standardType == type) return sc->retain(); } SharedCursorHandle* const sc = new SharedCursorHandle (type); getCursors().add (sc); return sc; } SharedCursorHandle* retain() throw() { ++refCount; return this; } void release() { if (--refCount == 0) { if (isStandard) { const ScopedLock sl (getLock()); getCursors().removeValue (this); } delete this; } } void* getHandle() const throw() { return handle; } juce_UseDebuggingNewOperator private: void* const handle; Atomic refCount; const MouseCursor::StandardCursorType standardType; const bool isStandard; static CriticalSection& getLock() { static CriticalSection lock; return lock; } static Array & getCursors() { static Array cursors; return cursors; } ~SharedCursorHandle() { deleteMouseCursor (handle, isStandard); } SharedCursorHandle& operator= (const SharedCursorHandle&); }; MouseCursor::MouseCursor() : cursorHandle (SharedCursorHandle::createStandard (NormalCursor)) { jassert (cursorHandle != 0); } MouseCursor::MouseCursor (const StandardCursorType type) : cursorHandle (SharedCursorHandle::createStandard (type)) { jassert (cursorHandle != 0); } MouseCursor::MouseCursor (const Image& image, const int hotSpotX, const int hotSpotY) : cursorHandle (new SharedCursorHandle (image, hotSpotX, hotSpotY)) { } MouseCursor::MouseCursor (const MouseCursor& other) : cursorHandle (other.cursorHandle->retain()) { } MouseCursor::~MouseCursor() { cursorHandle->release(); } MouseCursor& MouseCursor::operator= (const MouseCursor& other) { other.cursorHandle->retain(); cursorHandle->release(); cursorHandle = other.cursorHandle; return *this; } bool MouseCursor::operator== (const MouseCursor& other) const throw() { return getHandle() == other.getHandle(); } bool MouseCursor::operator!= (const MouseCursor& other) const throw() { return getHandle() != other.getHandle(); } void* MouseCursor::getHandle() const throw() { return cursorHandle->getHandle(); } void MouseCursor::showWaitCursor() { Desktop::getInstance().getMainMouseSource().showMouseCursor (MouseCursor::WaitCursor); } void MouseCursor::hideWaitCursor() { Desktop::getInstance().getMainMouseSource().revealCursor(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_MouseCursor.cpp ***/ /*** Start of inlined file: juce_MouseEvent.cpp ***/ BEGIN_JUCE_NAMESPACE MouseEvent::MouseEvent (MouseInputSource& source_, const Point& position, const ModifierKeys& mods_, Component* const eventComponent_, Component* const originator, const Time& eventTime_, const Point mouseDownPos_, const Time& mouseDownTime_, const int numberOfClicks_, const bool mouseWasDragged) throw() : x (position.getX()), y (position.getY()), mods (mods_), eventComponent (eventComponent_), originalComponent (originator), eventTime (eventTime_), source (source_), mouseDownPos (mouseDownPos_), mouseDownTime (mouseDownTime_), numberOfClicks (numberOfClicks_), wasMovedSinceMouseDown (mouseWasDragged) { } MouseEvent::~MouseEvent() throw() { } const MouseEvent MouseEvent::getEventRelativeTo (Component* const otherComponent) const throw() { if (otherComponent == 0) { jassertfalse; return *this; } return MouseEvent (source, eventComponent->relativePositionToOtherComponent (otherComponent, getPosition()), mods, otherComponent, originalComponent, eventTime, eventComponent->relativePositionToOtherComponent (otherComponent, mouseDownPos), mouseDownTime, numberOfClicks, wasMovedSinceMouseDown); } const MouseEvent MouseEvent::withNewPosition (const Point& newPosition) const throw() { return MouseEvent (source, newPosition, mods, eventComponent, originalComponent, eventTime, mouseDownPos, mouseDownTime, numberOfClicks, wasMovedSinceMouseDown); } bool MouseEvent::mouseWasClicked() const throw() { return ! wasMovedSinceMouseDown; } int MouseEvent::getMouseDownX() const throw() { return mouseDownPos.getX(); } int MouseEvent::getMouseDownY() const throw() { return mouseDownPos.getY(); } const Point MouseEvent::getMouseDownPosition() const throw() { return mouseDownPos; } int MouseEvent::getDistanceFromDragStartX() const throw() { return x - mouseDownPos.getX(); } int MouseEvent::getDistanceFromDragStartY() const throw() { return y - mouseDownPos.getY(); } int MouseEvent::getDistanceFromDragStart() const throw() { return mouseDownPos.getDistanceFrom (getPosition()); } const Point MouseEvent::getOffsetFromDragStart() const throw() { return getPosition() - mouseDownPos; } int MouseEvent::getLengthOfMousePress() const throw() { if (mouseDownTime.toMilliseconds() > 0) return jmax (0, (int) (eventTime - mouseDownTime).inMilliseconds()); return 0; } const Point MouseEvent::getPosition() const throw() { return Point (x, y); } int MouseEvent::getScreenX() const { return getScreenPosition().getX(); } int MouseEvent::getScreenY() const { return getScreenPosition().getY(); } const Point MouseEvent::getScreenPosition() const { return eventComponent->relativePositionToGlobal (Point (x, y)); } int MouseEvent::getMouseDownScreenX() const { return getMouseDownScreenPosition().getX(); } int MouseEvent::getMouseDownScreenY() const { return getMouseDownScreenPosition().getY(); } const Point MouseEvent::getMouseDownScreenPosition() const { return eventComponent->relativePositionToGlobal (mouseDownPos); } int MouseEvent::doubleClickTimeOutMs = 400; void MouseEvent::setDoubleClickTimeout (const int newTime) throw() { doubleClickTimeOutMs = newTime; } int MouseEvent::getDoubleClickTimeout() throw() { return doubleClickTimeOutMs; } END_JUCE_NAMESPACE /*** End of inlined file: juce_MouseEvent.cpp ***/ /*** Start of inlined file: juce_MouseInputSource.cpp ***/ BEGIN_JUCE_NAMESPACE class MouseInputSourceInternal : public AsyncUpdater { public: MouseInputSourceInternal (MouseInputSource& source_, const int index_, const bool isMouseDevice_) : index (index_), isMouseDevice (isMouseDevice_), source (source_), lastPeer (0), lastTime (0), isUnboundedMouseModeOn (false), isCursorVisibleUntilOffscreen (false), currentCursorHandle (0), mouseEventCounter (0) { zerostruct (mouseDowns); } ~MouseInputSourceInternal() { } bool isDragging() const throw() { return buttonState.isAnyMouseButtonDown(); } Component* getComponentUnderMouse() const { return static_cast (componentUnderMouse); } const ModifierKeys getCurrentModifiers() const { return ModifierKeys::getCurrentModifiers().withoutMouseButtons().withFlags (buttonState.getRawFlags()); } ComponentPeer* getPeer() { if (! ComponentPeer::isValidPeer (lastPeer)) lastPeer = 0; return lastPeer; } Component* findComponentAt (const Point& screenPos) { ComponentPeer* const peer = getPeer(); if (peer != 0) { Component* const comp = peer->getComponent(); const Point relativePos (comp->globalPositionToRelative (screenPos)); // (the contains() call is needed to test for overlapping desktop windows) if (comp->contains (relativePos.getX(), relativePos.getY())) return comp->getComponentAt (relativePos); } return 0; } const Point getScreenPosition() const throw() { return lastScreenPos + unboundedMouseOffset; } void sendMouseEnter (Component* const comp, const Point& screenPos, const int64 time) { //DBG ("Mouse " + String (source.getIndex()) + " enter: " + comp->globalPositionToRelative (screenPos).toString() + " - Comp: " + String::toHexString ((int) comp)); comp->internalMouseEnter (source, comp->globalPositionToRelative (screenPos), time); } void sendMouseExit (Component* const comp, const Point& screenPos, const int64 time) { //DBG ("Mouse " + String (source.getIndex()) + " exit: " + comp->globalPositionToRelative (screenPos).toString() + " - Comp: " + String::toHexString ((int) comp)); comp->internalMouseExit (source, comp->globalPositionToRelative (screenPos), time); } void sendMouseMove (Component* const comp, const Point& screenPos, const int64 time) { //DBG ("Mouse " + String (source.getIndex()) + " move: " + comp->globalPositionToRelative (screenPos).toString() + " - Comp: " + String::toHexString ((int) comp)); comp->internalMouseMove (source, comp->globalPositionToRelative (screenPos), time); } void sendMouseDown (Component* const comp, const Point& screenPos, const int64 time) { //DBG ("Mouse " + String (source.getIndex()) + " down: " + comp->globalPositionToRelative (screenPos).toString() + " - Comp: " + String::toHexString ((int) comp)); comp->internalMouseDown (source, comp->globalPositionToRelative (screenPos), time); } void sendMouseDrag (Component* const comp, const Point& screenPos, const int64 time) { //DBG ("Mouse " + String (source.getIndex()) + " drag: " + comp->globalPositionToRelative (screenPos).toString() + " - Comp: " + String::toHexString ((int) comp)); comp->internalMouseDrag (source, comp->globalPositionToRelative (screenPos), time); } void sendMouseUp (Component* const comp, const Point& screenPos, const int64 time) { //DBG ("Mouse " + String (source.getIndex()) + " up: " + comp->globalPositionToRelative (screenPos).toString() + " - Comp: " + String::toHexString ((int) comp)); comp->internalMouseUp (source, comp->globalPositionToRelative (screenPos), time, getCurrentModifiers()); } void sendMouseWheel (Component* const comp, const Point& screenPos, const int64 time, float x, float y) { //DBG ("Mouse " + String (source.getIndex()) + " wheel: " + comp->globalPositionToRelative (screenPos).toString() + " - Comp: " + String::toHexString ((int) comp)); comp->internalMouseWheel (source, comp->globalPositionToRelative (screenPos), time, x, y); } // (returns true if the button change caused a modal event loop) bool setButtons (const Point& screenPos, const int64 time, const ModifierKeys& newButtonState) { if (buttonState == newButtonState) return false; setScreenPos (screenPos, time, false); // (ignore secondary clicks when there's already a button down) if (buttonState.isAnyMouseButtonDown() == newButtonState.isAnyMouseButtonDown()) { buttonState = newButtonState; return false; } const int lastCounter = mouseEventCounter; if (buttonState.isAnyMouseButtonDown()) { Component* const current = getComponentUnderMouse(); if (current != 0) sendMouseUp (current, screenPos + unboundedMouseOffset, time); enableUnboundedMouseMovement (false, false); } buttonState = newButtonState; if (buttonState.isAnyMouseButtonDown()) { Desktop::getInstance().incrementMouseClickCounter(); Component* const current = getComponentUnderMouse(); if (current != 0) { registerMouseDown (screenPos, time, current); sendMouseDown (current, screenPos, time); } } return lastCounter != mouseEventCounter; } void setComponentUnderMouse (Component* const newComponent, const Point& screenPos, const int64 time) { Component* current = getComponentUnderMouse(); if (newComponent != current) { Component::SafePointer safeNewComp (newComponent); const ModifierKeys originalButtonState (buttonState); if (current != 0) { setButtons (screenPos, time, ModifierKeys()); sendMouseExit (current, screenPos, time); buttonState = originalButtonState; } componentUnderMouse = safeNewComp; current = getComponentUnderMouse(); if (current != 0) sendMouseEnter (current, screenPos, time); revealCursor (false); setButtons (screenPos, time, originalButtonState); } } void setPeer (ComponentPeer* const newPeer, const Point& screenPos, const int64 time) { ModifierKeys::updateCurrentModifiers(); if (newPeer != lastPeer) { setComponentUnderMouse (0, screenPos, time); lastPeer = newPeer; setComponentUnderMouse (findComponentAt (screenPos), screenPos, time); } } void setScreenPos (const Point& newScreenPos, const int64 time, const bool forceUpdate) { if (! isDragging()) setComponentUnderMouse (findComponentAt (newScreenPos), newScreenPos, time); if (newScreenPos != lastScreenPos || forceUpdate) { cancelPendingUpdate(); lastScreenPos = newScreenPos; Component* const current = getComponentUnderMouse(); if (current != 0) { if (isDragging()) { registerMouseDrag (newScreenPos); sendMouseDrag (current, newScreenPos + unboundedMouseOffset, time); if (isUnboundedMouseModeOn) handleUnboundedDrag (current); } else { sendMouseMove (current, newScreenPos, time); } } revealCursor (false); } } void handleEvent (ComponentPeer* const newPeer, const Point& positionWithinPeer, const int64 time, const ModifierKeys& newMods) { jassert (newPeer != 0); lastTime = time; ++mouseEventCounter; const Point screenPos (newPeer->relativePositionToGlobal (positionWithinPeer)); if (isDragging() && newMods.isAnyMouseButtonDown()) { setScreenPos (screenPos, time, false); } else { setPeer (newPeer, screenPos, time); ComponentPeer* peer = getPeer(); if (peer != 0) { if (setButtons (screenPos, time, newMods)) return; // some modal events have been dispatched, so the current event is now out-of-date peer = getPeer(); if (peer != 0) setScreenPos (screenPos, time, false); } } } void handleWheel (ComponentPeer* const peer, const Point& positionWithinPeer, int64 time, float x, float y) { jassert (peer != 0); lastTime = time; ++mouseEventCounter; const Point screenPos (peer->relativePositionToGlobal (positionWithinPeer)); setPeer (peer, screenPos, time); setScreenPos (screenPos, time, false); triggerFakeMove(); if (! isDragging()) { Component* current = getComponentUnderMouse(); if (current != 0) sendMouseWheel (current, screenPos, time, x, y); } } const Time getLastMouseDownTime() const throw() { return Time (mouseDowns[0].time); } const Point getLastMouseDownPosition() const throw() { return mouseDowns[0].position; } int getNumberOfMultipleClicks() const throw() { int numClicks = 0; if (mouseDowns[0].time != 0) { if (! mouseMovedSignificantlySincePressed) ++numClicks; for (int i = 1; i < numElementsInArray (mouseDowns); ++i) { if (mouseDowns[0].time - mouseDowns[i].time < (int) (MouseEvent::getDoubleClickTimeout() * (1.0 + 0.25 * (i - 1))) && abs (mouseDowns[0].position.getX() - mouseDowns[i].position.getX()) < 8 && abs (mouseDowns[0].position.getY() - mouseDowns[i].position.getY()) < 8) { ++numClicks; } else { break; } } } return numClicks; } bool hasMouseMovedSignificantlySincePressed() const throw() { return mouseMovedSignificantlySincePressed || lastTime > mouseDowns[0].time + 300; } void triggerFakeMove() { triggerAsyncUpdate(); } void handleAsyncUpdate() { setScreenPos (lastScreenPos, jmax (lastTime, Time::currentTimeMillis()), true); } void enableUnboundedMouseMovement (bool enable, bool keepCursorVisibleUntilOffscreen) { enable = enable && isDragging(); isCursorVisibleUntilOffscreen = keepCursorVisibleUntilOffscreen; if (enable != isUnboundedMouseModeOn) { if ((! enable) && ((! isCursorVisibleUntilOffscreen) || ! unboundedMouseOffset.isOrigin())) { // when released, return the mouse to within the component's bounds Component* current = getComponentUnderMouse(); if (current != 0) Desktop::setMousePosition (current->getScreenBounds() .getConstrainedPoint (lastScreenPos)); } isUnboundedMouseModeOn = enable; unboundedMouseOffset = Point(); revealCursor (true); } } void handleUnboundedDrag (Component* current) { const Rectangle screenArea (current->getParentMonitorArea().expanded (-2, -2)); if (! screenArea.contains (lastScreenPos)) { const Point componentCentre (current->getScreenBounds().getCentre()); unboundedMouseOffset += (lastScreenPos - componentCentre); Desktop::setMousePosition (componentCentre); } else if (isCursorVisibleUntilOffscreen && (! unboundedMouseOffset.isOrigin()) && screenArea.contains (lastScreenPos + unboundedMouseOffset)) { Desktop::setMousePosition (lastScreenPos + unboundedMouseOffset); unboundedMouseOffset = Point(); } } void showMouseCursor (MouseCursor cursor, bool forcedUpdate) { if (isUnboundedMouseModeOn && ((! unboundedMouseOffset.isOrigin()) || ! isCursorVisibleUntilOffscreen)) { cursor = MouseCursor::NoCursor; forcedUpdate = true; } if (forcedUpdate || cursor.getHandle() != currentCursorHandle) { currentCursorHandle = cursor.getHandle(); cursor.showInWindow (getPeer()); } } void hideCursor() { showMouseCursor (MouseCursor::NoCursor, true); } void revealCursor (bool forcedUpdate) { MouseCursor mc (MouseCursor::NormalCursor); Component* current = getComponentUnderMouse(); if (current != 0) mc = current->getLookAndFeel().getMouseCursorFor (*current); showMouseCursor (mc, forcedUpdate); } int index; bool isMouseDevice; Point lastScreenPos; ModifierKeys buttonState; private: MouseInputSource& source; Component::SafePointer componentUnderMouse; ComponentPeer* lastPeer; Point unboundedMouseOffset; bool isUnboundedMouseModeOn, isCursorVisibleUntilOffscreen; void* currentCursorHandle; int mouseEventCounter; struct RecentMouseDown { Point position; int64 time; Component* component; }; RecentMouseDown mouseDowns[4]; bool mouseMovedSignificantlySincePressed; int64 lastTime; void registerMouseDown (const Point& screenPos, const int64 time, Component* const component) throw() { for (int i = numElementsInArray (mouseDowns); --i > 0;) mouseDowns[i] = mouseDowns[i - 1]; mouseDowns[0].position = screenPos; mouseDowns[0].time = time; mouseDowns[0].component = component; mouseMovedSignificantlySincePressed = false; } void registerMouseDrag (const Point& screenPos) throw() { mouseMovedSignificantlySincePressed = mouseMovedSignificantlySincePressed || mouseDowns[0].position.getDistanceFrom (screenPos) >= 4; } MouseInputSourceInternal (const MouseInputSourceInternal&); MouseInputSourceInternal& operator= (const MouseInputSourceInternal&); }; MouseInputSource::MouseInputSource (const int index, const bool isMouseDevice) { pimpl = new MouseInputSourceInternal (*this, index, isMouseDevice); } MouseInputSource::~MouseInputSource() { } bool MouseInputSource::isMouse() const { return pimpl->isMouseDevice; } bool MouseInputSource::isTouch() const { return ! isMouse(); } bool MouseInputSource::canHover() const { return isMouse(); } bool MouseInputSource::hasMouseWheel() const { return isMouse(); } int MouseInputSource::getIndex() const { return pimpl->index; } bool MouseInputSource::isDragging() const { return pimpl->isDragging(); } const Point MouseInputSource::getScreenPosition() const { return pimpl->getScreenPosition(); } const ModifierKeys MouseInputSource::getCurrentModifiers() const { return pimpl->getCurrentModifiers(); } Component* MouseInputSource::getComponentUnderMouse() const { return pimpl->getComponentUnderMouse(); } void MouseInputSource::triggerFakeMove() const { pimpl->triggerFakeMove(); } int MouseInputSource::getNumberOfMultipleClicks() const throw() { return pimpl->getNumberOfMultipleClicks(); } const Time MouseInputSource::getLastMouseDownTime() const throw() { return pimpl->getLastMouseDownTime(); } const Point MouseInputSource::getLastMouseDownPosition() const throw() { return pimpl->getLastMouseDownPosition(); } bool MouseInputSource::hasMouseMovedSignificantlySincePressed() const throw() { return pimpl->hasMouseMovedSignificantlySincePressed(); } bool MouseInputSource::canDoUnboundedMovement() const throw() { return isMouse(); } void MouseInputSource::enableUnboundedMouseMovement (bool isEnabled, bool keepCursorVisibleUntilOffscreen) { pimpl->enableUnboundedMouseMovement (isEnabled, keepCursorVisibleUntilOffscreen); } bool MouseInputSource::hasMouseCursor() const throw() { return isMouse(); } void MouseInputSource::showMouseCursor (const MouseCursor& cursor) { pimpl->showMouseCursor (cursor, false); } void MouseInputSource::hideCursor() { pimpl->hideCursor(); } void MouseInputSource::revealCursor() { pimpl->revealCursor (false); } void MouseInputSource::forceMouseCursorUpdate() { pimpl->revealCursor (true); } void MouseInputSource::handleEvent (ComponentPeer* peer, const Point& positionWithinPeer, const int64 time, const ModifierKeys& mods) { pimpl->handleEvent (peer, positionWithinPeer, time, mods.withOnlyMouseButtons()); } void MouseInputSource::handleWheel (ComponentPeer* const peer, const Point& positionWithinPeer, const int64 time, const float x, const float y) { pimpl->handleWheel (peer, positionWithinPeer, time, x, y); } END_JUCE_NAMESPACE /*** End of inlined file: juce_MouseInputSource.cpp ***/ /*** Start of inlined file: juce_MouseHoverDetector.cpp ***/ BEGIN_JUCE_NAMESPACE MouseHoverDetector::MouseHoverDetector (const int hoverTimeMillisecs_) : source (0), hoverTimeMillisecs (hoverTimeMillisecs_), hasJustHovered (false) { internalTimer.owner = this; } MouseHoverDetector::~MouseHoverDetector() { setHoverComponent (0); } void MouseHoverDetector::setHoverTimeMillisecs (const int newTimeInMillisecs) { hoverTimeMillisecs = newTimeInMillisecs; } void MouseHoverDetector::setHoverComponent (Component* const newSourceComponent) { if (source != newSourceComponent) { internalTimer.stopTimer(); hasJustHovered = false; if (source != 0) { // ! you need to delete the hover detector before deleting its component jassert (source->isValidComponent()); source->removeMouseListener (&internalTimer); } source = newSourceComponent; if (newSourceComponent != 0) newSourceComponent->addMouseListener (&internalTimer, false); } } void MouseHoverDetector::hoverTimerCallback() { internalTimer.stopTimer(); if (source != 0) { const Point pos (source->getMouseXYRelative()); if (source->reallyContains (pos.getX(), pos.getY(), false)) { hasJustHovered = true; mouseHovered (pos.getX(), pos.getY()); } } } void MouseHoverDetector::checkJustHoveredCallback() { if (hasJustHovered) { hasJustHovered = false; mouseMovedAfterHover(); } } void MouseHoverDetector::HoverDetectorInternal::timerCallback() { owner->hoverTimerCallback(); } void MouseHoverDetector::HoverDetectorInternal::mouseEnter (const MouseEvent&) { stopTimer(); owner->checkJustHoveredCallback(); } void MouseHoverDetector::HoverDetectorInternal::mouseExit (const MouseEvent&) { stopTimer(); owner->checkJustHoveredCallback(); } void MouseHoverDetector::HoverDetectorInternal::mouseDown (const MouseEvent&) { stopTimer(); owner->checkJustHoveredCallback(); } void MouseHoverDetector::HoverDetectorInternal::mouseUp (const MouseEvent&) { stopTimer(); owner->checkJustHoveredCallback(); } void MouseHoverDetector::HoverDetectorInternal::mouseMove (const MouseEvent& e) { if (lastX != e.x || lastY != e.y) // to avoid fake mouse-moves setting it off { lastX = e.x; lastY = e.y; if (owner->source != 0) startTimer (owner->hoverTimeMillisecs); owner->checkJustHoveredCallback(); } } void MouseHoverDetector::HoverDetectorInternal::mouseWheelMove (const MouseEvent&, float, float) { stopTimer(); owner->checkJustHoveredCallback(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_MouseHoverDetector.cpp ***/ /*** Start of inlined file: juce_MouseListener.cpp ***/ BEGIN_JUCE_NAMESPACE void MouseListener::mouseEnter (const MouseEvent&) { } void MouseListener::mouseExit (const MouseEvent&) { } void MouseListener::mouseDown (const MouseEvent&) { } void MouseListener::mouseUp (const MouseEvent&) { } void MouseListener::mouseDrag (const MouseEvent&) { } void MouseListener::mouseMove (const MouseEvent&) { } void MouseListener::mouseDoubleClick (const MouseEvent&) { } void MouseListener::mouseWheelMove (const MouseEvent&, float, float) { } END_JUCE_NAMESPACE /*** End of inlined file: juce_MouseListener.cpp ***/ /*** Start of inlined file: juce_BooleanPropertyComponent.cpp ***/ BEGIN_JUCE_NAMESPACE BooleanPropertyComponent::BooleanPropertyComponent (const String& name, const String& buttonTextWhenTrue, const String& buttonTextWhenFalse) : PropertyComponent (name), onText (buttonTextWhenTrue), offText (buttonTextWhenFalse) { addAndMakeVisible (&button); button.setClickingTogglesState (false); button.addButtonListener (this); } BooleanPropertyComponent::BooleanPropertyComponent (const Value& valueToControl, const String& name, const String& buttonText) : PropertyComponent (name), onText (buttonText), offText (buttonText) { addAndMakeVisible (&button); button.setClickingTogglesState (false); button.setButtonText (buttonText); button.getToggleStateValue().referTo (valueToControl); button.setClickingTogglesState (true); } BooleanPropertyComponent::~BooleanPropertyComponent() { } void BooleanPropertyComponent::setState (const bool newState) { button.setToggleState (newState, true); } bool BooleanPropertyComponent::getState() const { return button.getToggleState(); } void BooleanPropertyComponent::paint (Graphics& g) { PropertyComponent::paint (g); g.setColour (Colours::white); g.fillRect (button.getBounds()); g.setColour (findColour (ComboBox::outlineColourId)); g.drawRect (button.getBounds()); } void BooleanPropertyComponent::refresh() { button.setToggleState (getState(), false); button.setButtonText (button.getToggleState() ? onText : offText); } void BooleanPropertyComponent::buttonClicked (Button*) { setState (! getState()); } END_JUCE_NAMESPACE /*** End of inlined file: juce_BooleanPropertyComponent.cpp ***/ /*** Start of inlined file: juce_ButtonPropertyComponent.cpp ***/ BEGIN_JUCE_NAMESPACE ButtonPropertyComponent::ButtonPropertyComponent (const String& name, const bool triggerOnMouseDown) : PropertyComponent (name) { addAndMakeVisible (&button); button.setTriggeredOnMouseDown (triggerOnMouseDown); button.addButtonListener (this); } ButtonPropertyComponent::~ButtonPropertyComponent() { } void ButtonPropertyComponent::refresh() { button.setButtonText (getButtonText()); } void ButtonPropertyComponent::buttonClicked (Button*) { buttonClicked(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ButtonPropertyComponent.cpp ***/ /*** Start of inlined file: juce_ChoicePropertyComponent.cpp ***/ BEGIN_JUCE_NAMESPACE class ChoicePropertyComponent::RemapperValueSource : public Value::ValueSource, public Value::Listener { public: RemapperValueSource (const Value& sourceValue_, const Array& mappings_) : sourceValue (sourceValue_), mappings (mappings_) { sourceValue.addListener (this); } ~RemapperValueSource() {} const var getValue() const { return mappings.indexOf (sourceValue.getValue()) + 1; } void setValue (const var& newValue) { const var remappedVal (mappings [(int) newValue - 1]); if (remappedVal != sourceValue) sourceValue = remappedVal; } void valueChanged (Value&) { sendChangeMessage (true); } juce_UseDebuggingNewOperator protected: Value sourceValue; Array mappings; RemapperValueSource (const RemapperValueSource&); const RemapperValueSource& operator= (const RemapperValueSource&); }; ChoicePropertyComponent::ChoicePropertyComponent (const String& name) : PropertyComponent (name), isCustomClass (true) { } ChoicePropertyComponent::ChoicePropertyComponent (const Value& valueToControl, const String& name, const StringArray& choices_, const Array & correspondingValues) : PropertyComponent (name), choices (choices_), isCustomClass (false) { // The array of corresponding values must contain one value for each of the items in // the choices array! jassert (correspondingValues.size() == choices.size()); createComboBox(); comboBox.getSelectedIdAsValue().referTo (Value (new RemapperValueSource (valueToControl, correspondingValues))); } ChoicePropertyComponent::~ChoicePropertyComponent() { } void ChoicePropertyComponent::createComboBox() { addAndMakeVisible (&comboBox); for (int i = 0; i < choices.size(); ++i) { if (choices[i].isNotEmpty()) comboBox.addItem (choices[i], i + 1); else comboBox.addSeparator(); } comboBox.setEditableText (false); } void ChoicePropertyComponent::setIndex (const int /*newIndex*/) { jassertfalse; // you need to override this method in your subclass! } int ChoicePropertyComponent::getIndex() const { jassertfalse; // you need to override this method in your subclass! return -1; } const StringArray& ChoicePropertyComponent::getChoices() const { return choices; } void ChoicePropertyComponent::refresh() { if (isCustomClass) { if (! comboBox.isVisible()) { createComboBox(); comboBox.addListener (this); } comboBox.setSelectedId (getIndex() + 1, true); } } void ChoicePropertyComponent::comboBoxChanged (ComboBox*) { if (isCustomClass) { const int newIndex = comboBox.getSelectedId() - 1; if (newIndex != getIndex()) setIndex (newIndex); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ChoicePropertyComponent.cpp ***/ /*** Start of inlined file: juce_PropertyComponent.cpp ***/ BEGIN_JUCE_NAMESPACE PropertyComponent::PropertyComponent (const String& name, const int preferredHeight_) : Component (name), preferredHeight (preferredHeight_) { jassert (name.isNotEmpty()); } PropertyComponent::~PropertyComponent() { } void PropertyComponent::paint (Graphics& g) { getLookAndFeel().drawPropertyComponentBackground (g, getWidth(), getHeight(), *this); getLookAndFeel().drawPropertyComponentLabel (g, getWidth(), getHeight(), *this); } void PropertyComponent::resized() { if (getNumChildComponents() > 0) getChildComponent (0)->setBounds (getLookAndFeel().getPropertyComponentContentPosition (*this)); } void PropertyComponent::enablementChanged() { repaint(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_PropertyComponent.cpp ***/ /*** Start of inlined file: juce_PropertyPanel.cpp ***/ BEGIN_JUCE_NAMESPACE class PropertyPanel::PropertyHolderComponent : public Component { public: PropertyHolderComponent() { } ~PropertyHolderComponent() { deleteAllChildren(); } void paint (Graphics&) { } void updateLayout (int width); void refreshAll() const; private: PropertyHolderComponent (const PropertyHolderComponent&); PropertyHolderComponent& operator= (const PropertyHolderComponent&); }; class PropertySectionComponent : public Component { public: PropertySectionComponent (const String& sectionTitle, const Array & newProperties, const bool open) : Component (sectionTitle), titleHeight (sectionTitle.isNotEmpty() ? 22 : 0), isOpen_ (open) { for (int i = newProperties.size(); --i >= 0;) { addAndMakeVisible (newProperties.getUnchecked(i)); newProperties.getUnchecked(i)->refresh(); } } ~PropertySectionComponent() { deleteAllChildren(); } void paint (Graphics& g) { if (titleHeight > 0) getLookAndFeel().drawPropertyPanelSectionHeader (g, getName(), isOpen(), getWidth(), titleHeight); } void resized() { int y = titleHeight; for (int i = getNumChildComponents(); --i >= 0;) { PropertyComponent* const pec = dynamic_cast (getChildComponent (i)); if (pec != 0) { const int prefH = pec->getPreferredHeight(); pec->setBounds (1, y, getWidth() - 2, prefH); y += prefH; } } } int getPreferredHeight() const { int y = titleHeight; if (isOpen()) { for (int i = 0; i < getNumChildComponents(); ++i) { PropertyComponent* pec = dynamic_cast (getChildComponent (i)); if (pec != 0) y += pec->getPreferredHeight(); } } return y; } void setOpen (const bool open) { if (isOpen_ != open) { isOpen_ = open; for (int i = 0; i < getNumChildComponents(); ++i) { PropertyComponent* pec = dynamic_cast (getChildComponent (i)); if (pec != 0) pec->setVisible (open); } // (unable to use the syntax findParentComponentOfClass () because of a VC6 compiler bug) PropertyPanel* const pp = findParentComponentOfClass ((PropertyPanel*) 0); if (pp != 0) pp->resized(); } } bool isOpen() const { return isOpen_; } void refreshAll() const { for (int i = 0; i < getNumChildComponents(); ++i) { PropertyComponent* pec = dynamic_cast (getChildComponent (i)); if (pec != 0) pec->refresh(); } } void mouseDown (const MouseEvent&) { } void mouseUp (const MouseEvent& e) { if (e.getMouseDownX() < titleHeight && e.x < titleHeight && e.y < titleHeight && e.getNumberOfClicks() != 2) { setOpen (! isOpen()); } } void mouseDoubleClick (const MouseEvent& e) { if (e.y < titleHeight) setOpen (! isOpen()); } private: int titleHeight; bool isOpen_; PropertySectionComponent (const PropertySectionComponent&); PropertySectionComponent& operator= (const PropertySectionComponent&); }; void PropertyPanel::PropertyHolderComponent::updateLayout (const int width) { int y = 0; for (int i = getNumChildComponents(); --i >= 0;) { PropertySectionComponent* const section = dynamic_cast (getChildComponent (i)); if (section != 0) { const int prefH = section->getPreferredHeight(); section->setBounds (0, y, width, prefH); y += prefH; } } setSize (width, y); repaint(); } void PropertyPanel::PropertyHolderComponent::refreshAll() const { for (int i = getNumChildComponents(); --i >= 0;) { PropertySectionComponent* const section = dynamic_cast (getChildComponent (i)); if (section != 0) section->refreshAll(); } } PropertyPanel::PropertyPanel() { messageWhenEmpty = TRANS("(nothing selected)"); addAndMakeVisible (&viewport); viewport.setViewedComponent (propertyHolderComponent = new PropertyHolderComponent()); viewport.setFocusContainer (true); } PropertyPanel::~PropertyPanel() { clear(); } void PropertyPanel::paint (Graphics& g) { if (propertyHolderComponent->getNumChildComponents() == 0) { g.setColour (Colours::black.withAlpha (0.5f)); g.setFont (14.0f); g.drawText (messageWhenEmpty, 0, 0, getWidth(), 30, Justification::centred, true); } } void PropertyPanel::resized() { viewport.setBounds (getLocalBounds()); updatePropHolderLayout(); } void PropertyPanel::clear() { if (propertyHolderComponent->getNumChildComponents() > 0) { propertyHolderComponent->deleteAllChildren(); repaint(); } } void PropertyPanel::addProperties (const Array & newProperties) { if (propertyHolderComponent->getNumChildComponents() == 0) repaint(); propertyHolderComponent->addAndMakeVisible (new PropertySectionComponent (String::empty, newProperties, true), 0); updatePropHolderLayout(); } void PropertyPanel::addSection (const String& sectionTitle, const Array & newProperties, const bool shouldBeOpen) { jassert (sectionTitle.isNotEmpty()); if (propertyHolderComponent->getNumChildComponents() == 0) repaint(); propertyHolderComponent->addAndMakeVisible (new PropertySectionComponent (sectionTitle, newProperties, shouldBeOpen), 0); updatePropHolderLayout(); } void PropertyPanel::updatePropHolderLayout() const { const int maxWidth = viewport.getMaximumVisibleWidth(); propertyHolderComponent->updateLayout (maxWidth); const int newMaxWidth = viewport.getMaximumVisibleWidth(); if (maxWidth != newMaxWidth) { // need to do this twice because of scrollbars changing the size, etc. propertyHolderComponent->updateLayout (newMaxWidth); } } void PropertyPanel::refreshAll() const { propertyHolderComponent->refreshAll(); } const StringArray PropertyPanel::getSectionNames() const { StringArray s; for (int i = 0; i < propertyHolderComponent->getNumChildComponents(); ++i) { PropertySectionComponent* const section = dynamic_cast (propertyHolderComponent->getChildComponent (i)); if (section != 0 && section->getName().isNotEmpty()) s.add (section->getName()); } return s; } bool PropertyPanel::isSectionOpen (const int sectionIndex) const { int index = 0; for (int i = 0; i < propertyHolderComponent->getNumChildComponents(); ++i) { PropertySectionComponent* const section = dynamic_cast (propertyHolderComponent->getChildComponent (i)); if (section != 0 && section->getName().isNotEmpty()) { if (index == sectionIndex) return section->isOpen(); ++index; } } return false; } void PropertyPanel::setSectionOpen (const int sectionIndex, const bool shouldBeOpen) { int index = 0; for (int i = 0; i < propertyHolderComponent->getNumChildComponents(); ++i) { PropertySectionComponent* const section = dynamic_cast (propertyHolderComponent->getChildComponent (i)); if (section != 0 && section->getName().isNotEmpty()) { if (index == sectionIndex) { section->setOpen (shouldBeOpen); break; } ++index; } } } void PropertyPanel::setSectionEnabled (const int sectionIndex, const bool shouldBeEnabled) { int index = 0; for (int i = 0; i < propertyHolderComponent->getNumChildComponents(); ++i) { PropertySectionComponent* const section = dynamic_cast (propertyHolderComponent->getChildComponent (i)); if (section != 0 && section->getName().isNotEmpty()) { if (index == sectionIndex) { section->setEnabled (shouldBeEnabled); break; } ++index; } } } XmlElement* PropertyPanel::getOpennessState() const { XmlElement* const xml = new XmlElement ("PROPERTYPANELSTATE"); xml->setAttribute ("scrollPos", viewport.getViewPositionY()); const StringArray sections (getSectionNames()); for (int i = 0; i < sections.size(); ++i) { if (sections[i].isNotEmpty()) { XmlElement* const e = xml->createNewChildElement ("SECTION"); e->setAttribute ("name", sections[i]); e->setAttribute ("open", isSectionOpen (i) ? 1 : 0); } } return xml; } void PropertyPanel::restoreOpennessState (const XmlElement& xml) { if (xml.hasTagName ("PROPERTYPANELSTATE")) { const StringArray sections (getSectionNames()); forEachXmlChildElementWithTagName (xml, e, "SECTION") { setSectionOpen (sections.indexOf (e->getStringAttribute ("name")), e->getBoolAttribute ("open")); } viewport.setViewPosition (viewport.getViewPositionX(), xml.getIntAttribute ("scrollPos", viewport.getViewPositionY())); } } void PropertyPanel::setMessageWhenEmpty (const String& newMessage) { if (messageWhenEmpty != newMessage) { messageWhenEmpty = newMessage; repaint(); } } const String& PropertyPanel::getMessageWhenEmpty() const { return messageWhenEmpty; } END_JUCE_NAMESPACE /*** End of inlined file: juce_PropertyPanel.cpp ***/ /*** Start of inlined file: juce_SliderPropertyComponent.cpp ***/ BEGIN_JUCE_NAMESPACE SliderPropertyComponent::SliderPropertyComponent (const String& name, const double rangeMin, const double rangeMax, const double interval, const double skewFactor) : PropertyComponent (name) { addAndMakeVisible (&slider); slider.setRange (rangeMin, rangeMax, interval); slider.setSkewFactor (skewFactor); slider.setSliderStyle (Slider::LinearBar); slider.addListener (this); } SliderPropertyComponent::SliderPropertyComponent (const Value& valueToControl, const String& name, const double rangeMin, const double rangeMax, const double interval, const double skewFactor) : PropertyComponent (name) { addAndMakeVisible (&slider); slider.setRange (rangeMin, rangeMax, interval); slider.setSkewFactor (skewFactor); slider.setSliderStyle (Slider::LinearBar); slider.getValueObject().referTo (valueToControl); } SliderPropertyComponent::~SliderPropertyComponent() { } void SliderPropertyComponent::setValue (const double /*newValue*/) { } double SliderPropertyComponent::getValue() const { return slider.getValue(); } void SliderPropertyComponent::refresh() { slider.setValue (getValue(), false); } void SliderPropertyComponent::sliderValueChanged (Slider*) { if (getValue() != slider.getValue()) setValue (slider.getValue()); } END_JUCE_NAMESPACE /*** End of inlined file: juce_SliderPropertyComponent.cpp ***/ /*** Start of inlined file: juce_TextPropertyComponent.cpp ***/ BEGIN_JUCE_NAMESPACE class TextPropLabel : public Label { TextPropertyComponent& owner; int maxChars; bool isMultiline; public: TextPropLabel (TextPropertyComponent& owner_, const int maxChars_, const bool isMultiline_) : Label (String::empty, String::empty), owner (owner_), maxChars (maxChars_), isMultiline (isMultiline_) { setEditable (true, true, false); setColour (backgroundColourId, Colours::white); setColour (outlineColourId, findColour (ComboBox::outlineColourId)); } ~TextPropLabel() { } TextEditor* createEditorComponent() { TextEditor* const textEditor = Label::createEditorComponent(); textEditor->setInputRestrictions (maxChars); if (isMultiline) { textEditor->setMultiLine (true, true); textEditor->setReturnKeyStartsNewLine (true); } return textEditor; } void textWasEdited() { owner.textWasEdited(); } }; TextPropertyComponent::TextPropertyComponent (const String& name, const int maxNumChars, const bool isMultiLine) : PropertyComponent (name) { createEditor (maxNumChars, isMultiLine); } TextPropertyComponent::TextPropertyComponent (const Value& valueToControl, const String& name, const int maxNumChars, const bool isMultiLine) : PropertyComponent (name) { createEditor (maxNumChars, isMultiLine); textEditor->getTextValue().referTo (valueToControl); } TextPropertyComponent::~TextPropertyComponent() { deleteAllChildren(); } void TextPropertyComponent::setText (const String& newText) { textEditor->setText (newText, true); } const String TextPropertyComponent::getText() const { return textEditor->getText(); } void TextPropertyComponent::createEditor (const int maxNumChars, const bool isMultiLine) { addAndMakeVisible (textEditor = new TextPropLabel (*this, maxNumChars, isMultiLine)); if (isMultiLine) { textEditor->setJustificationType (Justification::topLeft); preferredHeight = 120; } } void TextPropertyComponent::refresh() { textEditor->setText (getText(), false); } void TextPropertyComponent::textWasEdited() { const String newText (textEditor->getText()); if (getText() != newText) setText (newText); } END_JUCE_NAMESPACE /*** End of inlined file: juce_TextPropertyComponent.cpp ***/ /*** Start of inlined file: juce_AudioDeviceSelectorComponent.cpp ***/ BEGIN_JUCE_NAMESPACE class SimpleDeviceManagerInputLevelMeter : public Component, public Timer { public: SimpleDeviceManagerInputLevelMeter (AudioDeviceManager* const manager_) : manager (manager_), level (0) { startTimer (50); manager->enableInputLevelMeasurement (true); } ~SimpleDeviceManagerInputLevelMeter() { manager->enableInputLevelMeasurement (false); } void timerCallback() { const float newLevel = (float) manager->getCurrentInputLevel(); if (std::abs (level - newLevel) > 0.005f) { level = newLevel; repaint(); } } void paint (Graphics& g) { getLookAndFeel().drawLevelMeter (g, getWidth(), getHeight(), (float) exp (log (level) / 3.0)); // (add a bit of a skew to make the level more obvious) } private: AudioDeviceManager* const manager; float level; SimpleDeviceManagerInputLevelMeter (const SimpleDeviceManagerInputLevelMeter&); SimpleDeviceManagerInputLevelMeter& operator= (const SimpleDeviceManagerInputLevelMeter&); }; class AudioDeviceSelectorComponent::MidiInputSelectorComponentListBox : public ListBox, public ListBoxModel { public: MidiInputSelectorComponentListBox (AudioDeviceManager& deviceManager_, const String& noItemsMessage_, const int minNumber_, const int maxNumber_) : ListBox (String::empty, 0), deviceManager (deviceManager_), noItemsMessage (noItemsMessage_), minNumber (minNumber_), maxNumber (maxNumber_) { items = MidiInput::getDevices(); setModel (this); setOutlineThickness (1); } ~MidiInputSelectorComponentListBox() { } int getNumRows() { return items.size(); } void paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) { if (((unsigned int) row) < (unsigned int) items.size()) { if (rowIsSelected) g.fillAll (findColour (TextEditor::highlightColourId) .withMultipliedAlpha (0.3f)); const String item (items [row]); bool enabled = deviceManager.isMidiInputEnabled (item); const int x = getTickX(); const float tickW = height * 0.75f; getLookAndFeel().drawTickBox (g, *this, x - tickW, (height - tickW) / 2, tickW, tickW, enabled, true, true, false); g.setFont (height * 0.6f); g.setColour (findColour (ListBox::textColourId, true).withMultipliedAlpha (enabled ? 1.0f : 0.6f)); g.drawText (item, x, 0, width - x - 2, height, Justification::centredLeft, true); } } void listBoxItemClicked (int row, const MouseEvent& e) { selectRow (row); if (e.x < getTickX()) flipEnablement (row); } void listBoxItemDoubleClicked (int row, const MouseEvent&) { flipEnablement (row); } void returnKeyPressed (int row) { flipEnablement (row); } void paint (Graphics& g) { ListBox::paint (g); if (items.size() == 0) { g.setColour (Colours::grey); g.setFont (13.0f); g.drawText (noItemsMessage, 0, 0, getWidth(), getHeight() / 2, Justification::centred, true); } } int getBestHeight (const int preferredHeight) { const int extra = getOutlineThickness() * 2; return jmax (getRowHeight() * 2 + extra, jmin (getRowHeight() * getNumRows() + extra, preferredHeight)); } juce_UseDebuggingNewOperator private: AudioDeviceManager& deviceManager; const String noItemsMessage; StringArray items; int minNumber, maxNumber; void flipEnablement (const int row) { if (((unsigned int) row) < (unsigned int) items.size()) { const String item (items [row]); deviceManager.setMidiInputEnabled (item, ! deviceManager.isMidiInputEnabled (item)); } } int getTickX() const { return getRowHeight() + 5; } MidiInputSelectorComponentListBox (const MidiInputSelectorComponentListBox&); MidiInputSelectorComponentListBox& operator= (const MidiInputSelectorComponentListBox&); }; class AudioDeviceSettingsPanel : public Component, public ChangeListener, public ComboBoxListener, // (can't use ComboBox::Listener due to idiotic VC2005 bug) public ButtonListener { public: AudioDeviceSettingsPanel (AudioIODeviceType* type_, AudioIODeviceType::DeviceSetupDetails& setup_, const bool hideAdvancedOptionsWithButton) : type (type_), setup (setup_) { if (hideAdvancedOptionsWithButton) { addAndMakeVisible (showAdvancedSettingsButton = new TextButton (TRANS("Show advanced settings..."))); showAdvancedSettingsButton->addButtonListener (this); } type->scanForDevices(); setup.manager->addChangeListener (this); changeListenerCallback (0); } ~AudioDeviceSettingsPanel() { setup.manager->removeChangeListener (this); } void resized() { const int lx = proportionOfWidth (0.35f); const int w = proportionOfWidth (0.4f); const int h = 24; const int space = 6; const int dh = h + space; int y = 0; if (outputDeviceDropDown != 0) { outputDeviceDropDown->setBounds (lx, y, w, h); if (testButton != 0) testButton->setBounds (proportionOfWidth (0.77f), outputDeviceDropDown->getY(), proportionOfWidth (0.18f), h); y += dh; } if (inputDeviceDropDown != 0) { inputDeviceDropDown->setBounds (lx, y, w, h); inputLevelMeter->setBounds (proportionOfWidth (0.77f), inputDeviceDropDown->getY(), proportionOfWidth (0.18f), h); y += dh; } const int maxBoxHeight = 100;//(getHeight() - y - dh * 2) / numBoxes; if (outputChanList != 0) { const int bh = outputChanList->getBestHeight (maxBoxHeight); outputChanList->setBounds (lx, y, proportionOfWidth (0.55f), bh); y += bh + space; } if (inputChanList != 0) { const int bh = inputChanList->getBestHeight (maxBoxHeight); inputChanList->setBounds (lx, y, proportionOfWidth (0.55f), bh); y += bh + space; } y += space * 2; if (showAdvancedSettingsButton != 0) { showAdvancedSettingsButton->changeWidthToFitText (h); showAdvancedSettingsButton->setTopLeftPosition (lx, y); } if (sampleRateDropDown != 0) { sampleRateDropDown->setVisible (showAdvancedSettingsButton == 0 || ! showAdvancedSettingsButton->isVisible()); sampleRateDropDown->setBounds (lx, y, w, h); y += dh; } if (bufferSizeDropDown != 0) { bufferSizeDropDown->setVisible (showAdvancedSettingsButton == 0 || ! showAdvancedSettingsButton->isVisible()); bufferSizeDropDown->setBounds (lx, y, w, h); y += dh; } if (showUIButton != 0) { showUIButton->setVisible (showAdvancedSettingsButton == 0 || ! showAdvancedSettingsButton->isVisible()); showUIButton->changeWidthToFitText (h); showUIButton->setTopLeftPosition (lx, y); } } void comboBoxChanged (ComboBox* comboBoxThatHasChanged) { if (comboBoxThatHasChanged == 0) return; AudioDeviceManager::AudioDeviceSetup config; setup.manager->getAudioDeviceSetup (config); String error; if (comboBoxThatHasChanged == outputDeviceDropDown || comboBoxThatHasChanged == inputDeviceDropDown) { if (outputDeviceDropDown != 0) config.outputDeviceName = outputDeviceDropDown->getSelectedId() < 0 ? String::empty : outputDeviceDropDown->getText(); if (inputDeviceDropDown != 0) config.inputDeviceName = inputDeviceDropDown->getSelectedId() < 0 ? String::empty : inputDeviceDropDown->getText(); if (! type->hasSeparateInputsAndOutputs()) config.inputDeviceName = config.outputDeviceName; if (comboBoxThatHasChanged == inputDeviceDropDown) config.useDefaultInputChannels = true; else config.useDefaultOutputChannels = true; error = setup.manager->setAudioDeviceSetup (config, true); showCorrectDeviceName (inputDeviceDropDown, true); showCorrectDeviceName (outputDeviceDropDown, false); updateControlPanelButton(); resized(); } else if (comboBoxThatHasChanged == sampleRateDropDown) { if (sampleRateDropDown->getSelectedId() > 0) { config.sampleRate = sampleRateDropDown->getSelectedId(); error = setup.manager->setAudioDeviceSetup (config, true); } } else if (comboBoxThatHasChanged == bufferSizeDropDown) { if (bufferSizeDropDown->getSelectedId() > 0) { config.bufferSize = bufferSizeDropDown->getSelectedId(); error = setup.manager->setAudioDeviceSetup (config, true); } } if (error.isNotEmpty()) { AlertWindow::showMessageBox (AlertWindow::WarningIcon, "Error when trying to open audio device!", error); } } void buttonClicked (Button* button) { if (button == showAdvancedSettingsButton) { showAdvancedSettingsButton->setVisible (false); resized(); } else if (button == showUIButton) { AudioIODevice* const device = setup.manager->getCurrentAudioDevice(); if (device != 0 && device->showControlPanel()) { setup.manager->closeAudioDevice(); setup.manager->restartLastAudioDevice(); getTopLevelComponent()->toFront (true); } } else if (button == testButton && testButton != 0) { setup.manager->playTestSound(); } } void updateControlPanelButton() { AudioIODevice* const currentDevice = setup.manager->getCurrentAudioDevice(); showUIButton = 0; if (currentDevice != 0 && currentDevice->hasControlPanel()) { addAndMakeVisible (showUIButton = new TextButton (TRANS ("show this device's control panel"), TRANS ("opens the device's own control panel"))); showUIButton->addButtonListener (this); } resized(); } void changeListenerCallback (void*) { AudioIODevice* const currentDevice = setup.manager->getCurrentAudioDevice(); if (setup.maxNumOutputChannels > 0 || ! type->hasSeparateInputsAndOutputs()) { if (outputDeviceDropDown == 0) { outputDeviceDropDown = new ComboBox (String::empty); outputDeviceDropDown->addListener (this); addAndMakeVisible (outputDeviceDropDown); outputDeviceLabel = new Label (String::empty, type->hasSeparateInputsAndOutputs() ? TRANS ("output:") : TRANS ("device:")); outputDeviceLabel->attachToComponent (outputDeviceDropDown, true); if (setup.maxNumOutputChannels > 0) { addAndMakeVisible (testButton = new TextButton (TRANS ("Test"))); testButton->addButtonListener (this); } } addNamesToDeviceBox (*outputDeviceDropDown, false); } if (setup.maxNumInputChannels > 0 && type->hasSeparateInputsAndOutputs()) { if (inputDeviceDropDown == 0) { inputDeviceDropDown = new ComboBox (String::empty); inputDeviceDropDown->addListener (this); addAndMakeVisible (inputDeviceDropDown); inputDeviceLabel = new Label (String::empty, TRANS ("input:")); inputDeviceLabel->attachToComponent (inputDeviceDropDown, true); addAndMakeVisible (inputLevelMeter = new SimpleDeviceManagerInputLevelMeter (setup.manager)); } addNamesToDeviceBox (*inputDeviceDropDown, true); } updateControlPanelButton(); showCorrectDeviceName (inputDeviceDropDown, true); showCorrectDeviceName (outputDeviceDropDown, false); if (currentDevice != 0) { if (setup.maxNumOutputChannels > 0 && setup.minNumOutputChannels < setup.manager->getCurrentAudioDevice()->getOutputChannelNames().size()) { if (outputChanList == 0) { addAndMakeVisible (outputChanList = new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioOutputType, TRANS ("(no audio output channels found)"))); outputChanLabel = new Label (String::empty, TRANS ("active output channels:")); outputChanLabel->attachToComponent (outputChanList, true); } outputChanList->refresh(); } else { outputChanLabel = 0; outputChanList = 0; } if (setup.maxNumInputChannels > 0 && setup.minNumInputChannels < setup.manager->getCurrentAudioDevice()->getInputChannelNames().size()) { if (inputChanList == 0) { addAndMakeVisible (inputChanList = new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioInputType, TRANS ("(no audio input channels found)"))); inputChanLabel = new Label (String::empty, TRANS ("active input channels:")); inputChanLabel->attachToComponent (inputChanList, true); } inputChanList->refresh(); } else { inputChanLabel = 0; inputChanList = 0; } // sample rate.. { if (sampleRateDropDown == 0) { addAndMakeVisible (sampleRateDropDown = new ComboBox (String::empty)); sampleRateLabel = new Label (String::empty, TRANS ("sample rate:")); sampleRateLabel->attachToComponent (sampleRateDropDown, true); } else { sampleRateDropDown->clear(); sampleRateDropDown->removeListener (this); } const int numRates = currentDevice->getNumSampleRates(); for (int i = 0; i < numRates; ++i) { const int rate = roundToInt (currentDevice->getSampleRate (i)); sampleRateDropDown->addItem (String (rate) + " Hz", rate); } sampleRateDropDown->setSelectedId (roundToInt (currentDevice->getCurrentSampleRate()), true); sampleRateDropDown->addListener (this); } // buffer size { if (bufferSizeDropDown == 0) { addAndMakeVisible (bufferSizeDropDown = new ComboBox (String::empty)); bufferSizeLabel = new Label (String::empty, TRANS ("audio buffer size:")); bufferSizeLabel->attachToComponent (bufferSizeDropDown, true); } else { bufferSizeDropDown->clear(); bufferSizeDropDown->removeListener (this); } const int numBufferSizes = currentDevice->getNumBufferSizesAvailable(); double currentRate = currentDevice->getCurrentSampleRate(); if (currentRate == 0) currentRate = 48000.0; for (int i = 0; i < numBufferSizes; ++i) { const int bs = currentDevice->getBufferSizeSamples (i); bufferSizeDropDown->addItem (String (bs) + " samples (" + String (bs * 1000.0 / currentRate, 1) + " ms)", bs); } bufferSizeDropDown->setSelectedId (currentDevice->getCurrentBufferSizeSamples(), true); bufferSizeDropDown->addListener (this); } } else { jassert (setup.manager->getCurrentAudioDevice() == 0); // not the correct device type! sampleRateLabel = 0; bufferSizeLabel = 0; sampleRateDropDown = 0; bufferSizeDropDown = 0; if (outputDeviceDropDown != 0) outputDeviceDropDown->setSelectedId (-1, true); if (inputDeviceDropDown != 0) inputDeviceDropDown->setSelectedId (-1, true); } resized(); setSize (getWidth(), getLowestY() + 4); } private: AudioIODeviceType* const type; const AudioIODeviceType::DeviceSetupDetails setup; ScopedPointer outputDeviceDropDown, inputDeviceDropDown, sampleRateDropDown, bufferSizeDropDown; ScopedPointer