diff --git a/build/macosx/Juce.xcodeproj/project.pbxproj b/build/macosx/Juce.xcodeproj/project.pbxproj index 080c33d847..6799936e24 100644 --- a/build/macosx/Juce.xcodeproj/project.pbxproj +++ b/build/macosx/Juce.xcodeproj/project.pbxproj @@ -1089,7 +1089,7 @@ 84E024E80E94028C003E41AF /* juce_mac_Network.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = juce_mac_Network.mm; sourceTree = ""; }; 84E024E90E94028C003E41AF /* juce_mac_NSViewComponent.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = juce_mac_NSViewComponent.mm; sourceTree = ""; }; 84E024EA0E94028C003E41AF /* juce_mac_NSViewComponentPeer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 2; path = juce_mac_NSViewComponentPeer.mm; sourceTree = ""; }; - 84E024EB0E94028C003E41AF /* juce_mac_OpenGLComponent.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = juce_mac_OpenGLComponent.mm; sourceTree = ""; }; + 84E024EB0E94028C003E41AF /* juce_mac_OpenGLComponent.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 2; path = juce_mac_OpenGLComponent.mm; sourceTree = ""; }; 84E024EC0E94028C003E41AF /* juce_mac_QuickTimeMovieComponent.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = juce_mac_QuickTimeMovieComponent.mm; sourceTree = ""; }; 84E024ED0E94028C003E41AF /* juce_mac_SystemStats.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = juce_mac_SystemStats.mm; sourceTree = ""; }; 84E024EE0E94028C003E41AF /* juce_mac_Threads.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = juce_mac_Threads.mm; sourceTree = ""; }; @@ -1177,7 +1177,7 @@ 84FED3C80CAA96DA00003997 /* juce_FileDragAndDropTarget.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = juce_FileDragAndDropTarget.h; sourceTree = ""; }; 84FFAF290C6C8F2B009F6E72 /* juce_FileSearchPathListComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = juce_FileSearchPathListComponent.cpp; path = filebrowser/juce_FileSearchPathListComponent.cpp; sourceTree = ""; }; 84FFAF2A0C6C8F2B009F6E72 /* juce_FileSearchPathListComponent.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = juce_FileSearchPathListComponent.h; path = filebrowser/juce_FileSearchPathListComponent.h; sourceTree = ""; }; - D2AAC046055464E500DB518D /* libjuce.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libjuce.a; sourceTree = BUILT_PRODUCTS_DIR; }; + D2AAC046055464E500DB518D /* libjucedebug.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libjucedebug.a; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1216,7 +1216,7 @@ 1AB674ADFE9D54B511CA2CBB /* Products */ = { isa = PBXGroup; children = ( - D2AAC046055464E500DB518D /* libjuce.a */, + D2AAC046055464E500DB518D /* libjucedebug.a */, ); name = Products; sourceTree = ""; @@ -2566,7 +2566,7 @@ ); name = Juce; productName = Juce; - productReference = D2AAC046055464E500DB518D /* libjuce.a */; + productReference = D2AAC046055464E500DB518D /* libjucedebug.a */; productType = "com.apple.product-type.library.static"; }; /* End PBXNativeTarget section */ diff --git a/build/macosx/platform_specific_code/juce_mac_Files.mm b/build/macosx/platform_specific_code/juce_mac_Files.mm index 3b12105a07..2bd683d16c 100644 --- a/build/macosx/platform_specific_code/juce_mac_Files.mm +++ b/build/macosx/platform_specific_code/juce_mac_Files.mm @@ -1,614 +1,614 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - JUCE is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -// (This file gets included by juce_mac_NativeCode.mm, rather than being -// compiled on its own). -#ifdef JUCE_INCLUDED_FILE - -/* - Note that a lot of methods that you'd expect to find in this file actually - live in juce_posix_SharedCode.h! -*/ - -//============================================================================== -static File executableFile; - -const unsigned int macTimeToUnixTimeDiff = 0x7c25be90; - -static uint64 utcDateTimeToUnixTime (const UTCDateTime& d) throw() -{ - if (d.highSeconds == 0 && d.lowSeconds == 0 && d.fraction == 0) - return 0; - - return (((((uint64) d.highSeconds) << 32) | (uint64) d.lowSeconds) * 1000) - + ((d.fraction * 1000) >> 16) - - 2082844800000ll; -} - -static void unixTimeToUtcDateTime (uint64 t, UTCDateTime& d) throw() -{ - if (t != 0) - t += 2082844800000ll; - - d.highSeconds = (t / 1000) >> 32; - d.lowSeconds = (t / 1000) & (uint64) 0xffffffff; - d.fraction = ((t % 1000) << 16) / 1000; -} - -void juce_getFileTimes (const String& fileName, - int64& modificationTime, - int64& accessTime, - int64& creationTime) throw() -{ - modificationTime = 0; - accessTime = 0; - creationTime = 0; - - FSRef fileRef; - if (PlatformUtilities::makeFSRefFromPath (&fileRef, fileName)) - { - FSRefParam info; - zerostruct (info); - - info.ref = &fileRef; - info.whichInfo = kFSCatInfoAllDates; - - FSCatalogInfo catInfo; - info.catInfo = &catInfo; - - if (PBGetCatalogInfoSync (&info) == noErr) - { - creationTime = utcDateTimeToUnixTime (catInfo.createDate); - accessTime = utcDateTimeToUnixTime (catInfo.accessDate); - modificationTime = utcDateTimeToUnixTime (catInfo.contentModDate); - } - } -} - -bool juce_setFileTimes (const String& fileName, - int64 modificationTime, - int64 accessTime, - int64 creationTime) throw() -{ - FSRef fileRef; - if (PlatformUtilities::makeFSRefFromPath (&fileRef, fileName)) - { - FSRefParam info; - zerostruct (info); - - info.ref = &fileRef; - info.whichInfo = kFSCatInfoAllDates; - - FSCatalogInfo catInfo; - info.catInfo = &catInfo; - - if (PBGetCatalogInfoSync (&info) == noErr) - { - if (creationTime != 0) - unixTimeToUtcDateTime (creationTime, catInfo.createDate); - - if (modificationTime != 0) - unixTimeToUtcDateTime (modificationTime, catInfo.contentModDate); - - if (accessTime != 0) - unixTimeToUtcDateTime (accessTime, catInfo.accessDate); - - return PBSetCatalogInfoSync (&info) == noErr; - } - } - - return false; -} - -bool juce_setFileReadOnly (const String& fileName, bool isReadOnly) throw() -{ - const char* const fileNameUTF8 = fileName.toUTF8(); - - struct stat info; - const int res = stat (fileNameUTF8, &info); - - bool ok = false; - - if (res == 0) - { - info.st_mode &= 0777; // Just permissions - - if (isReadOnly) - info.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); - else - // Give everybody write permission? - info.st_mode |= S_IWUSR | S_IWGRP | S_IWOTH; - - ok = chmod (fileNameUTF8, info.st_mode) == 0; - } - - return ok; -} - -bool juce_copyFile (const String& src, const String& dst) throw() -{ - const ScopedAutoReleasePool pool; - NSFileManager* fm = [NSFileManager defaultManager]; - - return [fm fileExistsAtPath: juceStringToNS (src)] - && [fm copyPath: juceStringToNS (src) - toPath: juceStringToNS (dst) - handler: nil]; -} - -const StringArray juce_getFileSystemRoots() throw() -{ - StringArray s; - s.add (T("/")); - return s; -} - -//============================================================================== -static bool isFileOnDriveType (const File* const f, const char** types) throw() -{ - struct statfs buf; - - if (doStatFS (f, buf)) - { - const String type (buf.f_fstypename); - - while (*types != 0) - if (type.equalsIgnoreCase (*types++)) - return true; - } - - return false; -} - -bool File::isOnCDRomDrive() const throw() -{ - static const char* const cdTypes[] = { "cd9660", "cdfs", "cddafs", "udf", 0 }; - - return isFileOnDriveType (this, (const char**) cdTypes); -} - -bool File::isOnHardDisk() const throw() -{ - static const char* const nonHDTypes[] = { "nfs", "smbfs", "ramfs", 0 }; - - return ! (isOnCDRomDrive() || isFileOnDriveType (this, (const char**) nonHDTypes)); -} - -bool File::isOnRemovableDrive() const throw() -{ - const ScopedAutoReleasePool pool; - BOOL removable = false; - - [[NSWorkspace sharedWorkspace] - getFileSystemInfoForPath: juceStringToNS (getFullPathName()) - isRemovable: &removable - isWritable: nil - isUnmountable: nil - description: nil - type: nil]; - - return removable; -} - -static bool juce_isHiddenFile (const String& path) throw() -{ - FSRef ref; - if (! PlatformUtilities::makeFSRefFromPath (&ref, path)) - return false; - - FSCatalogInfo info; - FSGetCatalogInfo (&ref, kFSCatInfoNodeFlags | kFSCatInfoFinderInfo, &info, 0, 0, 0); - - if ((info.nodeFlags & kFSNodeIsDirectoryBit) != 0) - return (((FolderInfo*) &info.finderInfo)->finderFlags & kIsInvisible) != 0; - - return (((FileInfo*) &info.finderInfo)->finderFlags & kIsInvisible) != 0; -} - -bool File::isHidden() const throw() -{ - return juce_isHiddenFile (getFullPathName()); -} - -//============================================================================== -const File File::getSpecialLocation (const SpecialLocationType type) -{ - const ScopedAutoReleasePool pool; - - String resultPath; - - switch (type) - { - case userHomeDirectory: - resultPath = nsStringToJuce (NSHomeDirectory()); - break; - - case userDocumentsDirectory: - resultPath = "~/Documents"; - break; - - case userDesktopDirectory: - resultPath = "~/Desktop"; - break; - - case userApplicationDataDirectory: - resultPath = "~/Library"; - break; - - case commonApplicationDataDirectory: - resultPath = "/Library"; - break; - - case globalApplicationsDirectory: - resultPath = "/Applications"; - break; - - case userMusicDirectory: - resultPath = "~/Music"; - break; - - case userMoviesDirectory: - resultPath = "~/Movies"; - break; - - case tempDirectory: - { - File tmp (T("~/Library/Caches/") + executableFile.getFileNameWithoutExtension()); - - tmp.createDirectory(); - return tmp.getFullPathName(); - } - - case currentExecutableFile: - return executableFile; - - case currentApplicationFile: - { - const File parent (executableFile.getParentDirectory()); - - return parent.getFullPathName().endsWithIgnoreCase (T("Contents/MacOS")) - ? parent.getParentDirectory().getParentDirectory() - : executableFile; - } - - default: - jassertfalse // unknown type? - break; - } - - if (resultPath != 0) - return File (PlatformUtilities::convertToPrecomposedUnicode (resultPath)); - - return File::nonexistent; -} - -void juce_setCurrentExecutableFileName (const String& filename) throw() -{ - executableFile = File::getCurrentWorkingDirectory() - .getChildFile (PlatformUtilities::convertToPrecomposedUnicode (filename)); -} - -void juce_setCurrentExecutableFileNameFromBundleId (const String& bundleId) throw() -{ - const ScopedAutoReleasePool pool; - - NSBundle* b = [NSBundle bundleWithIdentifier: juceStringToNS (bundleId)]; - - if (b != nil) - executableFile = nsStringToJuce ([b executablePath]); -} - -//============================================================================== -const File File::getCurrentWorkingDirectory() throw() -{ - char buf [2048]; - getcwd (buf, sizeof(buf)); - - return File (PlatformUtilities::convertToPrecomposedUnicode (buf)); -} - -bool File::setAsCurrentWorkingDirectory() const throw() -{ - return chdir (getFullPathName().toUTF8()) == 0; -} - -//============================================================================== -const String File::getVersion() const throw() -{ - const ScopedAutoReleasePool pool; - String result; - - NSBundle* bundle = [NSBundle bundleWithPath: juceStringToNS (getFullPathName())]; - - if (bundle != 0) - { - NSDictionary* info = [bundle infoDictionary]; - - if (info != 0) - { - NSString* name = [info valueForKey: @"CFBundleShortVersionString"]; - - if (name != nil) - result = nsStringToJuce (name); - } - } - - return result; -} - -//============================================================================== -const File File::getLinkedTarget() const throw() -{ - FSRef ref; - Boolean targetIsAFolder, wasAliased; - - if (PlatformUtilities::makeFSRefFromPath (&ref, getFullPathName()) - && (FSResolveAliasFileWithMountFlags (&ref, true, &targetIsAFolder, &wasAliased, 0) == noErr) - && wasAliased) - { - return File (PlatformUtilities::makePathFromFSRef (&ref)); - } - - return *this; -} - -//============================================================================== -bool File::moveToTrash() const throw() -{ - if (! exists()) - return true; - - const ScopedAutoReleasePool pool; - - NSString* p = juceStringToNS (getFullPathName()); - - return [[NSWorkspace sharedWorkspace] - performFileOperation: NSWorkspaceRecycleOperation - source: [p stringByDeletingLastPathComponent] - destination: @"" - files: [NSArray arrayWithObject: [p lastPathComponent]] - tag: nil ]; -} - -//============================================================================== -struct FindFileStruct -{ - String parentDir, wildCard; - DIR* dir; - - bool getNextMatch (String& result, bool* const isDir, bool* const isHidden, int64* const fileSize, - Time* const modTime, Time* const creationTime, bool* const isReadOnly) throw() - { - const char* const wildCardUTF8 = wildCard.toUTF8(); - - for (;;) - { - struct dirent* const de = readdir (dir); - - if (de == 0) - break; - - if (fnmatch (wildCardUTF8, de->d_name, 0) == 0) - { - result = String::fromUTF8 ((const uint8*) de->d_name); - - const String path (parentDir + result); - - if (isDir != 0 || fileSize != 0) - { - struct stat info; - const bool statOk = juce_stat (path, info); - - if (isDir != 0) - *isDir = path.isEmpty() || (statOk && ((info.st_mode & S_IFDIR) != 0)); - - if (isHidden != 0) - *isHidden = (de->d_name[0] == '.') - || juce_isHiddenFile (path); - - if (fileSize != 0) - *fileSize = statOk ? info.st_size : 0; - } - - if (modTime != 0 || creationTime != 0) - { - int64 m, a, c; - juce_getFileTimes (path, m, a, c); - - if (modTime != 0) - *modTime = m; - - if (creationTime != 0) - *creationTime = c; - } - - if (isReadOnly != 0) - *isReadOnly = ! juce_canWriteToFile (path); - - return true; - } - } - - return false; - } -}; - -// returns 0 on failure -void* juce_findFileStart (const String& directory, const String& wildCard, String& firstResultFile, - bool* isDir, bool* isHidden, int64* fileSize, Time* modTime, - Time* creationTime, bool* isReadOnly) throw() -{ - DIR* const d = opendir (directory.toUTF8()); - - if (d != 0) - { - FindFileStruct* const ff = new FindFileStruct(); - ff->parentDir = directory; - - if (!ff->parentDir.endsWithChar (File::separator)) - ff->parentDir += File::separator; - - ff->wildCard = wildCard; - ff->dir = d; - - if (ff->getNextMatch (firstResultFile, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly)) - { - return ff; - } - else - { - firstResultFile = String::empty; - isDir = false; - closedir (d); - delete ff; - } - } - - return 0; -} - -bool juce_findFileNext (void* handle, String& resultFile, - bool* isDir, bool* isHidden, int64* fileSize, Time* modTime, Time* creationTime, bool* isReadOnly) throw() -{ - FindFileStruct* const ff = (FindFileStruct*) handle; - - if (ff != 0) - return ff->getNextMatch (resultFile, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly); - - return false; -} - -void juce_findFileClose (void* handle) throw() -{ - FindFileStruct* const ff = (FindFileStruct*)handle; - - if (ff != 0) - { - closedir (ff->dir); - delete ff; - } -} - -//============================================================================== -bool juce_launchExecutable (const String& pathAndArguments) throw() -{ - char* const argv[4] = { "/bin/sh", "-c", (char*) (const char*) pathAndArguments, 0 }; - - const int cpid = fork(); - - if (cpid == 0) - { - // Child process - if (execve (argv[0], argv, 0) < 0) - exit (0); - } - else - { - if (cpid < 0) - return false; - } - - return true; -} - -bool juce_launchFile (const String& fileName, - const String& parameters) throw() -{ - const ScopedAutoReleasePool pool; - - if (parameters.isEmpty()) - { - return [[NSWorkspace sharedWorkspace] openFile: juceStringToNS (fileName)] - || [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: juceStringToNS (fileName)]]; - } - - bool ok = false; - - FSRef ref; - if (PlatformUtilities::makeFSRefFromPath (&ref, fileName)) - { - if (PlatformUtilities::isBundle (fileName)) - { - NSMutableArray* urls = [NSMutableArray array]; - - StringArray docs; - docs.addTokens (parameters, true); - for (int i = 0; i < docs.size(); ++i) - [urls addObject: juceStringToNS (docs[i])]; - - ok = [[NSWorkspace sharedWorkspace] openURLs: urls - withAppBundleIdentifier: [[NSBundle bundleWithPath: juceStringToNS (fileName)] bundleIdentifier] - options: nil - additionalEventParamDescriptor: nil - launchIdentifiers: nil]; - } - else - { - ok = juce_launchExecutable (T("\"") + fileName + T("\" ") + parameters); - } - } - - return ok; -} - -//============================================================================== -bool PlatformUtilities::makeFSRefFromPath (FSRef* destFSRef, const String& path) -{ - return FSPathMakeRef ((const UInt8*) path.toUTF8(), destFSRef, 0) == noErr; -} - -const String PlatformUtilities::makePathFromFSRef (FSRef* file) -{ - uint8 path [2048]; - zeromem (path, sizeof (path)); - - String result; - - if (FSRefMakePath (file, (UInt8*) path, sizeof (path) - 1) == noErr) - result = String::fromUTF8 (path); - - return PlatformUtilities::convertToPrecomposedUnicode (result); -} - -//============================================================================== -OSType PlatformUtilities::getTypeOfFile (const String& filename) -{ - const ScopedAutoReleasePool pool; - return NSHFSTypeCodeFromFileType (NSHFSTypeOfFile (juceStringToNS (filename))); -} - -bool PlatformUtilities::isBundle (const String& filename) -{ - const ScopedAutoReleasePool pool; - return [[NSWorkspace sharedWorkspace] isFilePackageAtPath: juceStringToNS (filename)]; -} - -#endif +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + JUCE is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#ifdef JUCE_INCLUDED_FILE + +/* + Note that a lot of methods that you'd expect to find in this file actually + live in juce_posix_SharedCode.h! +*/ + +//============================================================================== +static File executableFile; + +const unsigned int macTimeToUnixTimeDiff = 0x7c25be90; + +static uint64 utcDateTimeToUnixTime (const UTCDateTime& d) throw() +{ + if (d.highSeconds == 0 && d.lowSeconds == 0 && d.fraction == 0) + return 0; + + return (((((uint64) d.highSeconds) << 32) | (uint64) d.lowSeconds) * 1000) + + ((d.fraction * 1000) >> 16) + - 2082844800000ll; +} + +static void unixTimeToUtcDateTime (uint64 t, UTCDateTime& d) throw() +{ + if (t != 0) + t += 2082844800000ll; + + d.highSeconds = (t / 1000) >> 32; + d.lowSeconds = (t / 1000) & (uint64) 0xffffffff; + d.fraction = ((t % 1000) << 16) / 1000; +} + +void juce_getFileTimes (const String& fileName, + int64& modificationTime, + int64& accessTime, + int64& creationTime) throw() +{ + modificationTime = 0; + accessTime = 0; + creationTime = 0; + + FSRef fileRef; + if (PlatformUtilities::makeFSRefFromPath (&fileRef, fileName)) + { + FSRefParam info; + zerostruct (info); + + info.ref = &fileRef; + info.whichInfo = kFSCatInfoAllDates; + + FSCatalogInfo catInfo; + info.catInfo = &catInfo; + + if (PBGetCatalogInfoSync (&info) == noErr) + { + creationTime = utcDateTimeToUnixTime (catInfo.createDate); + accessTime = utcDateTimeToUnixTime (catInfo.accessDate); + modificationTime = utcDateTimeToUnixTime (catInfo.contentModDate); + } + } +} + +bool juce_setFileTimes (const String& fileName, + int64 modificationTime, + int64 accessTime, + int64 creationTime) throw() +{ + FSRef fileRef; + if (PlatformUtilities::makeFSRefFromPath (&fileRef, fileName)) + { + FSRefParam info; + zerostruct (info); + + info.ref = &fileRef; + info.whichInfo = kFSCatInfoAllDates; + + FSCatalogInfo catInfo; + info.catInfo = &catInfo; + + if (PBGetCatalogInfoSync (&info) == noErr) + { + if (creationTime != 0) + unixTimeToUtcDateTime (creationTime, catInfo.createDate); + + if (modificationTime != 0) + unixTimeToUtcDateTime (modificationTime, catInfo.contentModDate); + + if (accessTime != 0) + unixTimeToUtcDateTime (accessTime, catInfo.accessDate); + + return PBSetCatalogInfoSync (&info) == noErr; + } + } + + return false; +} + +bool juce_setFileReadOnly (const String& fileName, bool isReadOnly) throw() +{ + const char* const fileNameUTF8 = fileName.toUTF8(); + + struct stat info; + const int res = stat (fileNameUTF8, &info); + + bool ok = false; + + if (res == 0) + { + info.st_mode &= 0777; // Just permissions + + if (isReadOnly) + info.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); + else + // Give everybody write permission? + info.st_mode |= S_IWUSR | S_IWGRP | S_IWOTH; + + ok = chmod (fileNameUTF8, info.st_mode) == 0; + } + + return ok; +} + +bool juce_copyFile (const String& src, const String& dst) throw() +{ + const ScopedAutoReleasePool pool; + NSFileManager* fm = [NSFileManager defaultManager]; + + return [fm fileExistsAtPath: juceStringToNS (src)] + && [fm copyPath: juceStringToNS (src) + toPath: juceStringToNS (dst) + handler: nil]; +} + +const StringArray juce_getFileSystemRoots() throw() +{ + StringArray s; + s.add (T("/")); + return s; +} + +//============================================================================== +static bool isFileOnDriveType (const File* const f, const char** types) throw() +{ + struct statfs buf; + + if (doStatFS (f, buf)) + { + const String type (buf.f_fstypename); + + while (*types != 0) + if (type.equalsIgnoreCase (*types++)) + return true; + } + + return false; +} + +bool File::isOnCDRomDrive() const throw() +{ + static const char* const cdTypes[] = { "cd9660", "cdfs", "cddafs", "udf", 0 }; + + return isFileOnDriveType (this, (const char**) cdTypes); +} + +bool File::isOnHardDisk() const throw() +{ + static const char* const nonHDTypes[] = { "nfs", "smbfs", "ramfs", 0 }; + + return ! (isOnCDRomDrive() || isFileOnDriveType (this, (const char**) nonHDTypes)); +} + +bool File::isOnRemovableDrive() const throw() +{ + const ScopedAutoReleasePool pool; + BOOL removable = false; + + [[NSWorkspace sharedWorkspace] + getFileSystemInfoForPath: juceStringToNS (getFullPathName()) + isRemovable: &removable + isWritable: nil + isUnmountable: nil + description: nil + type: nil]; + + return removable; +} + +static bool juce_isHiddenFile (const String& path) throw() +{ + FSRef ref; + if (! PlatformUtilities::makeFSRefFromPath (&ref, path)) + return false; + + FSCatalogInfo info; + FSGetCatalogInfo (&ref, kFSCatInfoNodeFlags | kFSCatInfoFinderInfo, &info, 0, 0, 0); + + if ((info.nodeFlags & kFSNodeIsDirectoryBit) != 0) + return (((FolderInfo*) &info.finderInfo)->finderFlags & kIsInvisible) != 0; + + return (((FileInfo*) &info.finderInfo)->finderFlags & kIsInvisible) != 0; +} + +bool File::isHidden() const throw() +{ + return juce_isHiddenFile (getFullPathName()); +} + +//============================================================================== +const File File::getSpecialLocation (const SpecialLocationType type) +{ + const ScopedAutoReleasePool pool; + + String resultPath; + + switch (type) + { + case userHomeDirectory: + resultPath = nsStringToJuce (NSHomeDirectory()); + break; + + case userDocumentsDirectory: + resultPath = "~/Documents"; + break; + + case userDesktopDirectory: + resultPath = "~/Desktop"; + break; + + case userApplicationDataDirectory: + resultPath = "~/Library"; + break; + + case commonApplicationDataDirectory: + resultPath = "/Library"; + break; + + case globalApplicationsDirectory: + resultPath = "/Applications"; + break; + + case userMusicDirectory: + resultPath = "~/Music"; + break; + + case userMoviesDirectory: + resultPath = "~/Movies"; + break; + + case tempDirectory: + { + File tmp (T("~/Library/Caches/") + executableFile.getFileNameWithoutExtension()); + + tmp.createDirectory(); + return tmp.getFullPathName(); + } + + case currentExecutableFile: + return executableFile; + + case currentApplicationFile: + { + const File parent (executableFile.getParentDirectory()); + + return parent.getFullPathName().endsWithIgnoreCase (T("Contents/MacOS")) + ? parent.getParentDirectory().getParentDirectory() + : executableFile; + } + + default: + jassertfalse // unknown type? + break; + } + + if (resultPath != 0) + return File (PlatformUtilities::convertToPrecomposedUnicode (resultPath)); + + return File::nonexistent; +} + +void juce_setCurrentExecutableFileName (const String& filename) throw() +{ + executableFile = File::getCurrentWorkingDirectory() + .getChildFile (PlatformUtilities::convertToPrecomposedUnicode (filename)); +} + +void juce_setCurrentExecutableFileNameFromBundleId (const String& bundleId) throw() +{ + const ScopedAutoReleasePool pool; + + NSBundle* b = [NSBundle bundleWithIdentifier: juceStringToNS (bundleId)]; + + if (b != nil) + executableFile = nsStringToJuce ([b executablePath]); +} + +//============================================================================== +const File File::getCurrentWorkingDirectory() throw() +{ + char buf [2048]; + getcwd (buf, sizeof(buf)); + + return File (PlatformUtilities::convertToPrecomposedUnicode (buf)); +} + +bool File::setAsCurrentWorkingDirectory() const throw() +{ + return chdir (getFullPathName().toUTF8()) == 0; +} + +//============================================================================== +const String File::getVersion() const throw() +{ + const ScopedAutoReleasePool pool; + String result; + + NSBundle* bundle = [NSBundle bundleWithPath: juceStringToNS (getFullPathName())]; + + if (bundle != 0) + { + NSDictionary* info = [bundle infoDictionary]; + + if (info != 0) + { + NSString* name = [info valueForKey: @"CFBundleShortVersionString"]; + + if (name != nil) + result = nsStringToJuce (name); + } + } + + return result; +} + +//============================================================================== +const File File::getLinkedTarget() const throw() +{ + FSRef ref; + Boolean targetIsAFolder, wasAliased; + + if (PlatformUtilities::makeFSRefFromPath (&ref, getFullPathName()) + && (FSResolveAliasFileWithMountFlags (&ref, true, &targetIsAFolder, &wasAliased, 0) == noErr) + && wasAliased) + { + return File (PlatformUtilities::makePathFromFSRef (&ref)); + } + + return *this; +} + +//============================================================================== +bool File::moveToTrash() const throw() +{ + if (! exists()) + return true; + + const ScopedAutoReleasePool pool; + + NSString* p = juceStringToNS (getFullPathName()); + + return [[NSWorkspace sharedWorkspace] + performFileOperation: NSWorkspaceRecycleOperation + source: [p stringByDeletingLastPathComponent] + destination: @"" + files: [NSArray arrayWithObject: [p lastPathComponent]] + tag: nil ]; +} + +//============================================================================== +struct FindFileStruct +{ + String parentDir, wildCard; + DIR* dir; + + bool getNextMatch (String& result, bool* const isDir, bool* const isHidden, int64* const fileSize, + Time* const modTime, Time* const creationTime, bool* const isReadOnly) throw() + { + const char* const wildCardUTF8 = wildCard.toUTF8(); + + for (;;) + { + struct dirent* const de = readdir (dir); + + if (de == 0) + break; + + if (fnmatch (wildCardUTF8, de->d_name, 0) == 0) + { + result = String::fromUTF8 ((const uint8*) de->d_name); + + const String path (parentDir + result); + + if (isDir != 0 || fileSize != 0) + { + struct stat info; + const bool statOk = juce_stat (path, info); + + if (isDir != 0) + *isDir = path.isEmpty() || (statOk && ((info.st_mode & S_IFDIR) != 0)); + + if (isHidden != 0) + *isHidden = (de->d_name[0] == '.') + || juce_isHiddenFile (path); + + if (fileSize != 0) + *fileSize = statOk ? info.st_size : 0; + } + + if (modTime != 0 || creationTime != 0) + { + int64 m, a, c; + juce_getFileTimes (path, m, a, c); + + if (modTime != 0) + *modTime = m; + + if (creationTime != 0) + *creationTime = c; + } + + if (isReadOnly != 0) + *isReadOnly = ! juce_canWriteToFile (path); + + return true; + } + } + + return false; + } +}; + +// returns 0 on failure +void* juce_findFileStart (const String& directory, const String& wildCard, String& firstResultFile, + bool* isDir, bool* isHidden, int64* fileSize, Time* modTime, + Time* creationTime, bool* isReadOnly) throw() +{ + DIR* const d = opendir (directory.toUTF8()); + + if (d != 0) + { + FindFileStruct* const ff = new FindFileStruct(); + ff->parentDir = directory; + + if (!ff->parentDir.endsWithChar (File::separator)) + ff->parentDir += File::separator; + + ff->wildCard = wildCard; + ff->dir = d; + + if (ff->getNextMatch (firstResultFile, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly)) + { + return ff; + } + else + { + firstResultFile = String::empty; + isDir = false; + closedir (d); + delete ff; + } + } + + return 0; +} + +bool juce_findFileNext (void* handle, String& resultFile, + bool* isDir, bool* isHidden, int64* fileSize, Time* modTime, Time* creationTime, bool* isReadOnly) throw() +{ + FindFileStruct* const ff = (FindFileStruct*) handle; + + if (ff != 0) + return ff->getNextMatch (resultFile, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly); + + return false; +} + +void juce_findFileClose (void* handle) throw() +{ + FindFileStruct* const ff = (FindFileStruct*)handle; + + if (ff != 0) + { + closedir (ff->dir); + delete ff; + } +} + +//============================================================================== +bool juce_launchExecutable (const String& pathAndArguments) throw() +{ + char* const argv[4] = { "/bin/sh", "-c", (char*) (const char*) pathAndArguments, 0 }; + + const int cpid = fork(); + + if (cpid == 0) + { + // Child process + if (execve (argv[0], argv, 0) < 0) + exit (0); + } + else + { + if (cpid < 0) + return false; + } + + return true; +} + +bool juce_launchFile (const String& fileName, + const String& parameters) throw() +{ + const ScopedAutoReleasePool pool; + + if (parameters.isEmpty()) + { + return [[NSWorkspace sharedWorkspace] openFile: juceStringToNS (fileName)] + || [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: juceStringToNS (fileName)]]; + } + + bool ok = false; + + FSRef ref; + if (PlatformUtilities::makeFSRefFromPath (&ref, fileName)) + { + if (PlatformUtilities::isBundle (fileName)) + { + NSMutableArray* urls = [NSMutableArray array]; + + StringArray docs; + docs.addTokens (parameters, true); + for (int i = 0; i < docs.size(); ++i) + [urls addObject: juceStringToNS (docs[i])]; + + ok = [[NSWorkspace sharedWorkspace] openURLs: urls + withAppBundleIdentifier: [[NSBundle bundleWithPath: juceStringToNS (fileName)] bundleIdentifier] + options: nil + additionalEventParamDescriptor: nil + launchIdentifiers: nil]; + } + else + { + ok = juce_launchExecutable (T("\"") + fileName + T("\" ") + parameters); + } + } + + return ok; +} + +//============================================================================== +bool PlatformUtilities::makeFSRefFromPath (FSRef* destFSRef, const String& path) +{ + return FSPathMakeRef ((const UInt8*) path.toUTF8(), destFSRef, 0) == noErr; +} + +const String PlatformUtilities::makePathFromFSRef (FSRef* file) +{ + uint8 path [2048]; + zeromem (path, sizeof (path)); + + String result; + + if (FSRefMakePath (file, (UInt8*) path, sizeof (path) - 1) == noErr) + result = String::fromUTF8 (path); + + return PlatformUtilities::convertToPrecomposedUnicode (result); +} + +//============================================================================== +OSType PlatformUtilities::getTypeOfFile (const String& filename) +{ + const ScopedAutoReleasePool pool; + return NSHFSTypeCodeFromFileType (NSHFSTypeOfFile (juceStringToNS (filename))); +} + +bool PlatformUtilities::isBundle (const String& filename) +{ + const ScopedAutoReleasePool pool; + return [[NSWorkspace sharedWorkspace] isFilePackageAtPath: juceStringToNS (filename)]; +} + +#endif diff --git a/build/macosx/platform_specific_code/juce_mac_Fonts.mm b/build/macosx/platform_specific_code/juce_mac_Fonts.mm index d8d9899a8d..e8cab4d50a 100644 --- a/build/macosx/platform_specific_code/juce_mac_Fonts.mm +++ b/build/macosx/platform_specific_code/juce_mac_Fonts.mm @@ -1,341 +1,341 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - JUCE is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -// (This file gets included by juce_mac_NativeCode.mm, rather than being -// compiled on its own). -#ifdef JUCE_INCLUDED_FILE - - -//============================================================================== -class FontHelper -{ - NSFont* font; - -public: - String name; - bool isBold, isItalic; - float fontSize, totalSize, ascent; - int refCount; - - FontHelper (const String& name_, - const bool bold_, - const bool italic_, - const float size_) - : font (0), - name (name_), - isBold (bold_), - isItalic (italic_), - fontSize (size_), - refCount (1) - { - font = [NSFont fontWithName: juceStringToNS (name_) size: size_]; - - if (italic_) - font = [[NSFontManager sharedFontManager] convertFont: font toHaveTrait: NSItalicFontMask]; - - if (bold_) - font = [[NSFontManager sharedFontManager] convertFont: font toHaveTrait: NSBoldFontMask]; - - [font retain]; - - ascent = fabsf ([font ascender]); - totalSize = ascent + fabsf ([font descender]); - } - - ~FontHelper() - { - [font release]; - } - - bool getPathAndKerning (const juce_wchar char1, - const juce_wchar char2, - Path* path, - float& kerning, - float* ascent, - float* descent) - { - const ScopedAutoReleasePool pool; - - if (font == 0 - || ! [[font coveredCharacterSet] longCharacterIsMember: (UTF32Char) char1]) - return false; - - String chars; - chars << ' ' << char1 << char2; - NSTextStorage* textStorage = [[[NSTextStorage alloc] - initWithString: juceStringToNS (chars) - attributes: [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: 0] - forKey: NSLigatureAttributeName]] autorelease]; - NSLayoutManager* layoutManager = [[[NSLayoutManager alloc] init] autorelease]; - NSTextContainer* textContainer = [[[NSTextContainer alloc] init] autorelease]; - [layoutManager addTextContainer: textContainer]; - [textStorage addLayoutManager: layoutManager]; - [textStorage setFont: font]; - - unsigned int glyphIndex = [layoutManager glyphRangeForCharacterRange: NSMakeRange (1, 1) - actualCharacterRange: 0].location; - NSPoint p1 = [layoutManager locationForGlyphAtIndex: glyphIndex]; - NSPoint p2 = [layoutManager locationForGlyphAtIndex: glyphIndex + 1]; - kerning = p2.x - p1.x; - - if (ascent != 0) - *ascent = this->ascent; - - if (descent != 0) - *descent = fabsf ([font descender]); - - if (path != 0) - { - NSBezierPath* bez = [NSBezierPath bezierPath]; - [bez moveToPoint: NSMakePoint (0, 0)]; - [bez appendBezierPathWithGlyph: [layoutManager glyphAtIndex: glyphIndex] - inFont: font]; - - for (int i = 0; i < [bez elementCount]; ++i) - { - NSPoint p[3]; - switch ([bez elementAtIndex: i associatedPoints: p]) - { - case NSMoveToBezierPathElement: - path->startNewSubPath (p[0].x, -p[0].y); - break; - case NSLineToBezierPathElement: - path->lineTo (p[0].x, -p[0].y); - break; - case NSCurveToBezierPathElement: - path->cubicTo (p[0].x, -p[0].y, p[1].x, -p[1].y, p[2].x, -p[2].y); - break; - case NSClosePathBezierPathElement: - path->closeSubPath(); - break; - default: - jassertfalse - break; - } - } - } - - return kerning != 0; - } - - juce_wchar getDefaultChar() - { - return 0; - } -}; - -//============================================================================== -class FontHelperCache : public Timer, - public DeletedAtShutdown -{ - VoidArray cache; - -public: - FontHelperCache() - { - } - - ~FontHelperCache() - { - for (int i = cache.size(); --i >= 0;) - { - FontHelper* const f = (FontHelper*) cache.getUnchecked(i); - delete f; - } - - clearSingletonInstance(); - } - - FontHelper* getFont (const String& name, - const bool bold, - const bool italic, - const float size = 1024) - { - for (int i = cache.size(); --i >= 0;) - { - FontHelper* const f = (FontHelper*) cache.getUnchecked(i); - - if (f->name == name - && f->isBold == bold - && f->isItalic == italic - && f->fontSize == size) - { - f->refCount++; - return f; - } - } - - FontHelper* const f = new FontHelper (name, bold, italic, size); - cache.add (f); - return f; - } - - void releaseFont (FontHelper* f) - { - for (int i = cache.size(); --i >= 0;) - { - FontHelper* const f2 = (FontHelper*) cache.getUnchecked(i); - - if (f == f2) - { - f->refCount--; - - if (f->refCount == 0) - startTimer (5000); - - break; - } - } - } - - void timerCallback() - { - stopTimer(); - - for (int i = cache.size(); --i >= 0;) - { - FontHelper* const f = (FontHelper*) cache.getUnchecked(i); - - if (f->refCount == 0) - { - cache.remove (i); - delete f; - } - } - - if (cache.size() == 0) - delete this; - } - - juce_DeclareSingleton_SingleThreaded_Minimal (FontHelperCache) -}; - -juce_ImplementSingleton_SingleThreaded (FontHelperCache) - -//============================================================================== -void Typeface::initialiseTypefaceCharacteristics (const String& fontName, - bool bold, - bool italic, - bool addAllGlyphsToFont) throw() -{ - // This method is only safe to be called from the normal UI thread.. - jassert (MessageManager::getInstance()->isThisTheMessageThread()); - - FontHelper* const helper = FontHelperCache::getInstance() - ->getFont (fontName, bold, italic); - - clear(); - setAscent (helper->ascent / helper->totalSize); - setName (fontName); - setDefaultCharacter (helper->getDefaultChar()); - setBold (bold); - setItalic (italic); - - if (addAllGlyphsToFont) - { - //xxx - jassertfalse - } - - FontHelperCache::getInstance()->releaseFont (helper); -} - -bool Typeface::findAndAddSystemGlyph (juce_wchar character) throw() -{ - // This method is only safe to be called from the normal UI thread.. - jassert (MessageManager::getInstance()->isThisTheMessageThread()); - - FontHelper* const helper = FontHelperCache::getInstance() - ->getFont (getName(), isBold(), isItalic()); - - Path path; - float width; - bool foundOne = false; - - if (helper->getPathAndKerning (character, T('I'), &path, width, 0, 0)) - { - path.applyTransform (AffineTransform::scale (1.0f / helper->totalSize, - 1.0f / helper->totalSize)); - - addGlyph (character, path, width / helper->totalSize); - - for (int i = 0; i < glyphs.size(); ++i) - { - const TypefaceGlyphInfo* const g = (const TypefaceGlyphInfo*) glyphs.getUnchecked(i); - - float kerning; - if (helper->getPathAndKerning (character, g->getCharacter(), 0, kerning, 0, 0)) - { - kerning = (kerning - width) / helper->totalSize; - - if (kerning != 0) - addKerningPair (character, g->getCharacter(), kerning); - } - - if (helper->getPathAndKerning (g->getCharacter(), character, 0, kerning, 0, 0)) - { - kerning = kerning / helper->totalSize - g->width; - - if (kerning != 0) - addKerningPair (g->getCharacter(), character, kerning); - } - } - - foundOne = true; - } - - FontHelperCache::getInstance()->releaseFont (helper); - return foundOne; -} - -//============================================================================== -const StringArray Font::findAllTypefaceNames() throw() -{ - StringArray names; - - const ScopedAutoReleasePool pool; - NSArray* fonts = [[NSFontManager sharedFontManager] availableFontFamilies]; - - for (int i = 0; i < [fonts count]; ++i) - names.add (nsStringToJuce ((NSString*) [fonts objectAtIndex: i])); - - names.sort (true); - return names; -} - -void Typeface::getDefaultFontNames (String& defaultSans, String& defaultSerif, String& defaultFixed) throw() -{ - defaultSans = "Lucida Grande"; - defaultSerif = "Times New Roman"; - defaultFixed = "Monaco"; -} - -#endif +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + JUCE is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#ifdef JUCE_INCLUDED_FILE + + +//============================================================================== +class FontHelper +{ + NSFont* font; + +public: + String name; + bool isBold, isItalic; + float fontSize, totalSize, ascent; + int refCount; + + FontHelper (const String& name_, + const bool bold_, + const bool italic_, + const float size_) + : font (0), + name (name_), + isBold (bold_), + isItalic (italic_), + fontSize (size_), + refCount (1) + { + font = [NSFont fontWithName: juceStringToNS (name_) size: size_]; + + if (italic_) + font = [[NSFontManager sharedFontManager] convertFont: font toHaveTrait: NSItalicFontMask]; + + if (bold_) + font = [[NSFontManager sharedFontManager] convertFont: font toHaveTrait: NSBoldFontMask]; + + [font retain]; + + ascent = fabsf ([font ascender]); + totalSize = ascent + fabsf ([font descender]); + } + + ~FontHelper() + { + [font release]; + } + + bool getPathAndKerning (const juce_wchar char1, + const juce_wchar char2, + Path* path, + float& kerning, + float* ascent, + float* descent) + { + const ScopedAutoReleasePool pool; + + if (font == 0 + || ! [[font coveredCharacterSet] longCharacterIsMember: (UTF32Char) char1]) + return false; + + String chars; + chars << ' ' << char1 << char2; + NSTextStorage* textStorage = [[[NSTextStorage alloc] + initWithString: juceStringToNS (chars) + attributes: [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: 0] + forKey: NSLigatureAttributeName]] autorelease]; + NSLayoutManager* layoutManager = [[[NSLayoutManager alloc] init] autorelease]; + NSTextContainer* textContainer = [[[NSTextContainer alloc] init] autorelease]; + [layoutManager addTextContainer: textContainer]; + [textStorage addLayoutManager: layoutManager]; + [textStorage setFont: font]; + + unsigned int glyphIndex = [layoutManager glyphRangeForCharacterRange: NSMakeRange (1, 1) + actualCharacterRange: 0].location; + NSPoint p1 = [layoutManager locationForGlyphAtIndex: glyphIndex]; + NSPoint p2 = [layoutManager locationForGlyphAtIndex: glyphIndex + 1]; + kerning = p2.x - p1.x; + + if (ascent != 0) + *ascent = this->ascent; + + if (descent != 0) + *descent = fabsf ([font descender]); + + if (path != 0) + { + NSBezierPath* bez = [NSBezierPath bezierPath]; + [bez moveToPoint: NSMakePoint (0, 0)]; + [bez appendBezierPathWithGlyph: [layoutManager glyphAtIndex: glyphIndex] + inFont: font]; + + for (int i = 0; i < [bez elementCount]; ++i) + { + NSPoint p[3]; + switch ([bez elementAtIndex: i associatedPoints: p]) + { + case NSMoveToBezierPathElement: + path->startNewSubPath (p[0].x, -p[0].y); + break; + case NSLineToBezierPathElement: + path->lineTo (p[0].x, -p[0].y); + break; + case NSCurveToBezierPathElement: + path->cubicTo (p[0].x, -p[0].y, p[1].x, -p[1].y, p[2].x, -p[2].y); + break; + case NSClosePathBezierPathElement: + path->closeSubPath(); + break; + default: + jassertfalse + break; + } + } + } + + return kerning != 0; + } + + juce_wchar getDefaultChar() + { + return 0; + } +}; + +//============================================================================== +class FontHelperCache : public Timer, + public DeletedAtShutdown +{ + VoidArray cache; + +public: + FontHelperCache() + { + } + + ~FontHelperCache() + { + for (int i = cache.size(); --i >= 0;) + { + FontHelper* const f = (FontHelper*) cache.getUnchecked(i); + delete f; + } + + clearSingletonInstance(); + } + + FontHelper* getFont (const String& name, + const bool bold, + const bool italic, + const float size = 1024) + { + for (int i = cache.size(); --i >= 0;) + { + FontHelper* const f = (FontHelper*) cache.getUnchecked(i); + + if (f->name == name + && f->isBold == bold + && f->isItalic == italic + && f->fontSize == size) + { + f->refCount++; + return f; + } + } + + FontHelper* const f = new FontHelper (name, bold, italic, size); + cache.add (f); + return f; + } + + void releaseFont (FontHelper* f) + { + for (int i = cache.size(); --i >= 0;) + { + FontHelper* const f2 = (FontHelper*) cache.getUnchecked(i); + + if (f == f2) + { + f->refCount--; + + if (f->refCount == 0) + startTimer (5000); + + break; + } + } + } + + void timerCallback() + { + stopTimer(); + + for (int i = cache.size(); --i >= 0;) + { + FontHelper* const f = (FontHelper*) cache.getUnchecked(i); + + if (f->refCount == 0) + { + cache.remove (i); + delete f; + } + } + + if (cache.size() == 0) + delete this; + } + + juce_DeclareSingleton_SingleThreaded_Minimal (FontHelperCache) +}; + +juce_ImplementSingleton_SingleThreaded (FontHelperCache) + +//============================================================================== +void Typeface::initialiseTypefaceCharacteristics (const String& fontName, + bool bold, + bool italic, + bool addAllGlyphsToFont) throw() +{ + // This method is only safe to be called from the normal UI thread.. + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + + FontHelper* const helper = FontHelperCache::getInstance() + ->getFont (fontName, bold, italic); + + clear(); + setAscent (helper->ascent / helper->totalSize); + setName (fontName); + setDefaultCharacter (helper->getDefaultChar()); + setBold (bold); + setItalic (italic); + + if (addAllGlyphsToFont) + { + //xxx + jassertfalse + } + + FontHelperCache::getInstance()->releaseFont (helper); +} + +bool Typeface::findAndAddSystemGlyph (juce_wchar character) throw() +{ + // This method is only safe to be called from the normal UI thread.. + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + + FontHelper* const helper = FontHelperCache::getInstance() + ->getFont (getName(), isBold(), isItalic()); + + Path path; + float width; + bool foundOne = false; + + if (helper->getPathAndKerning (character, T('I'), &path, width, 0, 0)) + { + path.applyTransform (AffineTransform::scale (1.0f / helper->totalSize, + 1.0f / helper->totalSize)); + + addGlyph (character, path, width / helper->totalSize); + + for (int i = 0; i < glyphs.size(); ++i) + { + const TypefaceGlyphInfo* const g = (const TypefaceGlyphInfo*) glyphs.getUnchecked(i); + + float kerning; + if (helper->getPathAndKerning (character, g->getCharacter(), 0, kerning, 0, 0)) + { + kerning = (kerning - width) / helper->totalSize; + + if (kerning != 0) + addKerningPair (character, g->getCharacter(), kerning); + } + + if (helper->getPathAndKerning (g->getCharacter(), character, 0, kerning, 0, 0)) + { + kerning = kerning / helper->totalSize - g->width; + + if (kerning != 0) + addKerningPair (g->getCharacter(), character, kerning); + } + } + + foundOne = true; + } + + FontHelperCache::getInstance()->releaseFont (helper); + return foundOne; +} + +//============================================================================== +const StringArray Font::findAllTypefaceNames() throw() +{ + StringArray names; + + const ScopedAutoReleasePool pool; + NSArray* fonts = [[NSFontManager sharedFontManager] availableFontFamilies]; + + for (int i = 0; i < [fonts count]; ++i) + names.add (nsStringToJuce ((NSString*) [fonts objectAtIndex: i])); + + names.sort (true); + return names; +} + +void Typeface::getDefaultFontNames (String& defaultSans, String& defaultSerif, String& defaultFixed) throw() +{ + defaultSans = "Lucida Grande"; + defaultSerif = "Times New Roman"; + defaultFixed = "Monaco"; +} + +#endif diff --git a/build/macosx/platform_specific_code/juce_mac_MiscUtilities.mm b/build/macosx/platform_specific_code/juce_mac_MiscUtilities.mm index 5cb3d1a4d0..2471b21ffb 100644 --- a/build/macosx/platform_specific_code/juce_mac_MiscUtilities.mm +++ b/build/macosx/platform_specific_code/juce_mac_MiscUtilities.mm @@ -246,4 +246,3 @@ void juce_updateMultiMonitorInfo (Array & monitorCoords, const bool c #endif #endif - diff --git a/build/macosx/platform_specific_code/juce_mac_NativeIncludes.h b/build/macosx/platform_specific_code/juce_mac_NativeIncludes.h index 99097873de..6ef6cfdc80 100644 --- a/build/macosx/platform_specific_code/juce_mac_NativeIncludes.h +++ b/build/macosx/platform_specific_code/juce_mac_NativeIncludes.h @@ -1,74 +1,74 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - JUCE is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#ifndef __JUCE_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. -*/ - -#include "../../../src/juce_core/basics/juce_StandardHeader.h" - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#include -#include -#include -#include -#include -#include -#include - -#if MACOS_10_4_OR_EARLIER - #include - typedef int NSInteger; - typedef unsigned int NSUInteger; -#endif - -#endif // __JUCE_MAC_NATIVEINCLUDES_JUCEHEADER__ +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + JUCE is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#ifndef __JUCE_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. +*/ + +#include "../../../src/juce_core/basics/juce_StandardHeader.h" + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#include +#include +#include +#include +#include +#include +#include + +#if MACOS_10_4_OR_EARLIER + #include + typedef int NSInteger; + typedef unsigned int NSUInteger; +#endif + +#endif // __JUCE_MAC_NATIVEINCLUDES_JUCEHEADER__ diff --git a/build/macosx/platform_specific_code/juce_mac_OpenGLComponent.mm b/build/macosx/platform_specific_code/juce_mac_OpenGLComponent.mm index bee6d3cc23..83620f1985 100644 --- a/build/macosx/platform_specific_code/juce_mac_OpenGLComponent.mm +++ b/build/macosx/platform_specific_code/juce_mac_OpenGLComponent.mm @@ -38,7 +38,7 @@ END_JUCE_NAMESPACE //============================================================================== @interface ThreadSafeNSOpenGLView : NSOpenGLView { - CriticalSection* contextLock; + CriticalSection* contextLock; bool needsUpdate; } @@ -50,14 +50,14 @@ END_JUCE_NAMESPACE @implementation ThreadSafeNSOpenGLView -- (id) initWithFrame: (NSRect) frameRect +- (id) initWithFrame: (NSRect) frameRect pixelFormat: (NSOpenGLPixelFormat*) format { contextLock = new CriticalSection(); self = [super initWithFrame: frameRect pixelFormat: format]; - + if (self != nil) - [[NSNotificationCenter defaultCenter] addObserver: self + [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector (_surfaceNeedsUpdate:) name: NSViewGlobalFrameDidChangeNotification object: self]; @@ -74,7 +74,7 @@ END_JUCE_NAMESPACE - (bool) makeActive { const ScopedLock sl (*contextLock); - + if ([self openGLContext] == 0) return false; @@ -244,7 +244,7 @@ public: juce_UseDebuggingNewOperator NSOpenGLContext* renderContext; - ThreadSafeNSOpenGLView* view; + ThreadSafeNSOpenGLView* view; private: OpenGLPixelFormat pixelFormat; diff --git a/extras/juce demo/src/BinaryData.cpp b/extras/juce demo/src/BinaryData.cpp index 88ce28235e..c6640be808 100644 --- a/extras/juce demo/src/BinaryData.cpp +++ b/extras/juce demo/src/BinaryData.cpp @@ -7999,3 +7999,4 @@ static const unsigned char temp17[] = {47,42,13,10,32,32,61,61,61,61,61,61,61,61 111,109,109,97,110,100,77,97,110,97,103,101,114,41,13,10,123,13,10,32,32,32,32,114,101,116,117,114,110,32,110,101,119,32,87,105,100,103,101,116, 115,68,101,109,111,32,40,99,111,109,109,97,110,100,77,97,110,97,103,101,114,41,59,13,10,125,13,10,0,0}; const char* BinaryData::widgetsdemo_cpp = (const char*) temp17; + diff --git a/extras/the jucer/build/mac/Jucer.xcodeproj/project.pbxproj b/extras/the jucer/build/mac/Jucer.xcodeproj/project.pbxproj index bb925e5917..c629c6a7ac 100644 --- a/extras/the jucer/build/mac/Jucer.xcodeproj/project.pbxproj +++ b/extras/the jucer/build/mac/Jucer.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 844B0A9B0F52DC6000B2F1FD /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 844B0A9A0F52DC6000B2F1FD /* Carbon.framework */; }; 846929140A49DB9C00314975 /* juce.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 846929130A49DB9C00314975 /* juce.xcconfig */; }; 846C10D40DE33F4D00E8CCE8 /* juce_LibrarySource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 846C10D30DE33F4D00E8CCE8 /* juce_LibrarySource.mm */; }; 846C10EA0DE33FA100E8CCE8 /* ApplicationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 846C10DC0DE33FA100E8CCE8 /* ApplicationServices.framework */; }; @@ -60,6 +61,7 @@ /* Begin PBXFileReference section */ 0867D6ABFE840B52C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; + 844B0A9A0F52DC6000B2F1FD /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = ""; }; 846929130A49DB9C00314975 /* juce.xcconfig */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xcconfig; name = juce.xcconfig; path = ../../../../build/macosx/juce.xcconfig; sourceTree = SOURCE_ROOT; }; 846C10D20DE33F4D00E8CCE8 /* juce_AppConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = juce_AppConfig.h; path = ../../src/juce_AppConfig.h; sourceTree = SOURCE_ROOT; }; 846C10D30DE33F4D00E8CCE8 /* juce_LibrarySource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = juce_LibrarySource.mm; path = ../../src/juce_LibrarySource.mm; sourceTree = SOURCE_ROOT; }; @@ -200,6 +202,7 @@ 846C10F60DE33FA100E8CCE8 /* WebKit.framework in Frameworks */, 84E01DD90E910B7B003E41AF /* QTKit.framework in Frameworks */, 84F8B68E0EB5FB290020D98D /* QuickTime.framework in Frameworks */, + 844B0A9B0F52DC6000B2F1FD /* Carbon.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -258,6 +261,7 @@ 20286C32FDCF999611CA2CEA /* External Frameworks and Libraries */ = { isa = PBXGroup; children = ( + 844B0A9A0F52DC6000B2F1FD /* Carbon.framework */, 846C10DC0DE33FA100E8CCE8 /* ApplicationServices.framework */, 846C10DF0DE33FA100E8CCE8 /* Cocoa.framework */, 846C10E10DE33FA100E8CCE8 /* CoreFoundation.framework */, diff --git a/extras/the jucer/src/BinaryData.cpp b/extras/the jucer/src/BinaryData.cpp index 1279f531a8..c08fddd03e 100644 --- a/extras/the jucer/src/BinaryData.cpp +++ b/extras/the jucer/src/BinaryData.cpp @@ -911,3 +911,4 @@ static const unsigned char temp4[] = {137,80,78,71,13,10,26,10,0,0,0,13,73,72,68 0,98,28,9,155,95,0,2,104,68,236,11,1,8,160,17,225,73,128,0,3,0,120,52,172,151,198,78,252,63,0,0,0,0,73,69,78,68,174,66, 96,130,0,0}; const char* BinaryData::prefs_misc_png = (const char*) temp4; + diff --git a/src/juce_app_includes.h b/src/juce_app_includes.h index b7031d940d..dbd358998b 100644 --- a/src/juce_app_includes.h +++ b/src/juce_app_includes.h @@ -29,8 +29,8 @@ ============================================================================== */ -#ifndef __JUCE_JUCE_APP_INCLUDES_INCLUDEFILES__ -#define __JUCE_JUCE_APP_INCLUDES_INCLUDEFILES__ +#ifndef __JUCE_APP_INCLUDES_JUCEHEADER__ +#define __JUCE_APP_INCLUDES_JUCEHEADER__ #ifndef __JUCE_APPLICATION_JUCEHEADER__ #include "juce_appframework/application/juce_Application.h" @@ -744,4 +744,4 @@ #include "juce_appframework/documents/juce_UndoManager.h" #endif -#endif +#endif // __JUCE_APP_INCLUDES_JUCEHEADER__ diff --git a/src/juce_appframework/gui/components/buttons/juce_ImageButton.cpp b/src/juce_appframework/gui/components/buttons/juce_ImageButton.cpp index fc4e1256aa..925cef62a7 100644 --- a/src/juce_appframework/gui/components/buttons/juce_ImageButton.cpp +++ b/src/juce_appframework/gui/components/buttons/juce_ImageButton.cpp @@ -1,242 +1,242 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - JUCE is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../../../../juce_core/basics/juce_StandardHeader.h" - -BEGIN_JUCE_NAMESPACE - -#include "juce_ImageButton.h" -#include "../../graphics/imaging/juce_ImageCache.h" -#include "../lookandfeel/juce_LookAndFeel.h" - - -//============================================================================== -ImageButton::ImageButton (const String& text) - : Button (text), - scaleImageToFit (true), - preserveProportions (true), - alphaThreshold (0), - imageX (0), - imageY (0), - imageW (0), - imageH (0), - normalImage (0), - overImage (0), - downImage (0) -{ -} - -ImageButton::~ImageButton() -{ - deleteImages(); -} - -void ImageButton::deleteImages() -{ - if (normalImage != 0) - { - if (ImageCache::isImageInCache (normalImage)) - ImageCache::release (normalImage); - else - delete normalImage; - } - - if (overImage != 0) - { - if (ImageCache::isImageInCache (overImage)) - ImageCache::release (overImage); - else - delete overImage; - } - - if (downImage != 0) - { - if (ImageCache::isImageInCache (downImage)) - ImageCache::release (downImage); - else - delete downImage; - } -} - -void ImageButton::setImages (const bool resizeButtonNowToFitThisImage, - const bool rescaleImagesWhenButtonSizeChanges, - const bool preserveImageProportions, - Image* const normalImage_, - const float imageOpacityWhenNormal, - const Colour& overlayColourWhenNormal, - Image* const overImage_, - const float imageOpacityWhenOver, - const Colour& overlayColourWhenOver, - Image* const downImage_, - const float imageOpacityWhenDown, - const Colour& overlayColourWhenDown, - const float hitTestAlphaThreshold) -{ - deleteImages(); - - normalImage = normalImage_; - overImage = overImage_; - downImage = downImage_; - - if (resizeButtonNowToFitThisImage && normalImage != 0) - { - imageW = normalImage->getWidth(); - imageH = normalImage->getHeight(); - - setSize (imageW, imageH); - } - - scaleImageToFit = rescaleImagesWhenButtonSizeChanges; - preserveProportions = preserveImageProportions; - - normalOpacity = imageOpacityWhenNormal; - normalOverlay = overlayColourWhenNormal; - overOpacity = imageOpacityWhenOver; - overOverlay = overlayColourWhenOver; - downOpacity = imageOpacityWhenDown; - downOverlay = overlayColourWhenDown; - - alphaThreshold = (unsigned char) jlimit (0, 0xff, roundFloatToInt (255.0f * hitTestAlphaThreshold)); - - repaint(); -} - -Image* ImageButton::getCurrentImage() const -{ - if (isDown() || getToggleState()) - return getDownImage(); - - if (isOver()) - return getOverImage(); - - return getNormalImage(); -} - -Image* ImageButton::getNormalImage() const throw() -{ - return normalImage; -} - -Image* ImageButton::getOverImage() const throw() -{ - return (overImage != 0) ? overImage - : normalImage; -} - -Image* ImageButton::getDownImage() const throw() -{ - return (downImage != 0) ? downImage - : getOverImage(); -} - -void ImageButton::paintButton (Graphics& g, - bool isMouseOverButton, - bool isButtonDown) -{ - if (! isEnabled()) - { - isMouseOverButton = false; - isButtonDown = false; - } - - Image* const im = getCurrentImage(); - - if (im != 0) - { - const int iw = im->getWidth(); - const int ih = im->getHeight(); - imageW = getWidth(); - imageH = getHeight(); - imageX = (imageW - iw) >> 1; - imageY = (imageH - ih) >> 1; - - if (scaleImageToFit) - { - if (preserveProportions) - { - int newW, newH; - const float imRatio = ih / (float)iw; - const float destRatio = imageH / (float)imageW; - - if (imRatio > destRatio) - { - newW = roundFloatToInt (imageH / imRatio); - newH = imageH; - } - else - { - newW = imageW; - newH = roundFloatToInt (imageW * imRatio); - } - - imageX = (imageW - newW) / 2; - imageY = (imageH - newH) / 2; - imageW = newW; - imageH = newH; - } - else - { - imageX = 0; - imageY = 0; - } - } - - if (! scaleImageToFit) - { - imageW = iw; - imageH = ih; - } - - getLookAndFeel().drawImageButton (g, im, imageX, imageY, imageW, imageH, - isButtonDown ? downOverlay - : (isMouseOverButton ? overOverlay - : normalOverlay), - isButtonDown ? downOpacity - : (isMouseOverButton ? overOpacity - : normalOpacity), - *this); - } -} - -bool ImageButton::hitTest (int x, int y) -{ - if (alphaThreshold == 0) - return true; - - Image* const im = getCurrentImage(); - - return im == 0 - || (imageW > 0 && imageH > 0 - && alphaThreshold < im->getPixelAt (((x - imageX) * im->getWidth()) / imageW, - ((y - imageY) * im->getHeight()) / imageH).getAlpha()); -} - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + JUCE is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../../../../juce_core/basics/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + +#include "juce_ImageButton.h" +#include "../../graphics/imaging/juce_ImageCache.h" +#include "../lookandfeel/juce_LookAndFeel.h" + + +//============================================================================== +ImageButton::ImageButton (const String& text) + : Button (text), + scaleImageToFit (true), + preserveProportions (true), + alphaThreshold (0), + imageX (0), + imageY (0), + imageW (0), + imageH (0), + normalImage (0), + overImage (0), + downImage (0) +{ +} + +ImageButton::~ImageButton() +{ + deleteImages(); +} + +void ImageButton::deleteImages() +{ + if (normalImage != 0) + { + if (ImageCache::isImageInCache (normalImage)) + ImageCache::release (normalImage); + else + delete normalImage; + } + + if (overImage != 0) + { + if (ImageCache::isImageInCache (overImage)) + ImageCache::release (overImage); + else + delete overImage; + } + + if (downImage != 0) + { + if (ImageCache::isImageInCache (downImage)) + ImageCache::release (downImage); + else + delete downImage; + } +} + +void ImageButton::setImages (const bool resizeButtonNowToFitThisImage, + const bool rescaleImagesWhenButtonSizeChanges, + const bool preserveImageProportions, + Image* const normalImage_, + const float imageOpacityWhenNormal, + const Colour& overlayColourWhenNormal, + Image* const overImage_, + const float imageOpacityWhenOver, + const Colour& overlayColourWhenOver, + Image* const downImage_, + const float imageOpacityWhenDown, + const Colour& overlayColourWhenDown, + const float hitTestAlphaThreshold) +{ + deleteImages(); + + normalImage = normalImage_; + overImage = overImage_; + downImage = downImage_; + + if (resizeButtonNowToFitThisImage && normalImage != 0) + { + imageW = normalImage->getWidth(); + imageH = normalImage->getHeight(); + + setSize (imageW, imageH); + } + + scaleImageToFit = rescaleImagesWhenButtonSizeChanges; + preserveProportions = preserveImageProportions; + + normalOpacity = imageOpacityWhenNormal; + normalOverlay = overlayColourWhenNormal; + overOpacity = imageOpacityWhenOver; + overOverlay = overlayColourWhenOver; + downOpacity = imageOpacityWhenDown; + downOverlay = overlayColourWhenDown; + + alphaThreshold = (unsigned char) jlimit (0, 0xff, roundFloatToInt (255.0f * hitTestAlphaThreshold)); + + repaint(); +} + +Image* ImageButton::getCurrentImage() const +{ + if (isDown() || getToggleState()) + return getDownImage(); + + if (isOver()) + return getOverImage(); + + return getNormalImage(); +} + +Image* ImageButton::getNormalImage() const throw() +{ + return normalImage; +} + +Image* ImageButton::getOverImage() const throw() +{ + return (overImage != 0) ? overImage + : normalImage; +} + +Image* ImageButton::getDownImage() const throw() +{ + return (downImage != 0) ? downImage + : getOverImage(); +} + +void ImageButton::paintButton (Graphics& g, + bool isMouseOverButton, + bool isButtonDown) +{ + if (! isEnabled()) + { + isMouseOverButton = false; + isButtonDown = false; + } + + Image* const im = getCurrentImage(); + + if (im != 0) + { + const int iw = im->getWidth(); + const int ih = im->getHeight(); + imageW = getWidth(); + imageH = getHeight(); + imageX = (imageW - iw) >> 1; + imageY = (imageH - ih) >> 1; + + if (scaleImageToFit) + { + if (preserveProportions) + { + int newW, newH; + const float imRatio = ih / (float)iw; + const float destRatio = imageH / (float)imageW; + + if (imRatio > destRatio) + { + newW = roundFloatToInt (imageH / imRatio); + newH = imageH; + } + else + { + newW = imageW; + newH = roundFloatToInt (imageW * imRatio); + } + + imageX = (imageW - newW) / 2; + imageY = (imageH - newH) / 2; + imageW = newW; + imageH = newH; + } + else + { + imageX = 0; + imageY = 0; + } + } + + if (! scaleImageToFit) + { + imageW = iw; + imageH = ih; + } + + getLookAndFeel().drawImageButton (g, im, imageX, imageY, imageW, imageH, + isButtonDown ? downOverlay + : (isMouseOverButton ? overOverlay + : normalOverlay), + isButtonDown ? downOpacity + : (isMouseOverButton ? overOpacity + : normalOpacity), + *this); + } +} + +bool ImageButton::hitTest (int x, int y) +{ + if (alphaThreshold == 0) + return true; + + Image* const im = getCurrentImage(); + + return im == 0 + || (imageW > 0 && imageH > 0 + && alphaThreshold < im->getPixelAt (((x - imageX) * im->getWidth()) / imageW, + ((y - imageY) * im->getHeight()) / imageH).getAlpha()); +} + +END_JUCE_NAMESPACE diff --git a/src/juce_appframework/gui/components/controls/juce_Slider.cpp b/src/juce_appframework/gui/components/controls/juce_Slider.cpp index 8d2b466131..19f0eabc16 100644 --- a/src/juce_appframework/gui/components/controls/juce_Slider.cpp +++ b/src/juce_appframework/gui/components/controls/juce_Slider.cpp @@ -1,1416 +1,1416 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - JUCE is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../../../../juce_core/basics/juce_StandardHeader.h" - -BEGIN_JUCE_NAMESPACE - -#include "juce_Slider.h" -#include "../lookandfeel/juce_LookAndFeel.h" -#include "../menus/juce_PopupMenu.h" -#include "../juce_Desktop.h" -#include "../special/juce_BubbleComponent.h" -#include "../../../../juce_core/text/juce_LocalisedStrings.h" - - -//============================================================================== -class SliderPopupDisplayComponent : public BubbleComponent -{ -public: - //============================================================================== - SliderPopupDisplayComponent (Slider* const owner_) - : owner (owner_), - font (15.0f, Font::bold) - { - setAlwaysOnTop (true); - } - - ~SliderPopupDisplayComponent() - { - } - - void paintContent (Graphics& g, int w, int h) - { - g.setFont (font); - g.setColour (Colours::black); - - g.drawFittedText (text, 0, 0, w, h, Justification::centred, 1); - } - - void getContentSize (int& w, int& h) - { - w = font.getStringWidth (text) + 18; - h = (int) (font.getHeight() * 1.6f); - } - - void updatePosition (const String& newText) - { - if (text != newText) - { - text = newText; - repaint(); - } - - BubbleComponent::setPosition (owner); - } - - //============================================================================== - juce_UseDebuggingNewOperator - -private: - Slider* owner; - Font font; - String text; - - SliderPopupDisplayComponent (const SliderPopupDisplayComponent&); - const SliderPopupDisplayComponent& operator= (const SliderPopupDisplayComponent&); -}; - -//============================================================================== -Slider::Slider (const String& name) - : Component (name), - listeners (2), - currentValue (0.0), - valueMin (0.0), - valueMax (0.0), - minimum (0), - maximum (10), - interval (0), - skewFactor (1.0), - velocityModeSensitivity (1.0), - velocityModeOffset (0.0), - velocityModeThreshold (1), - rotaryStart (float_Pi * 1.2f), - rotaryEnd (float_Pi * 2.8f), - numDecimalPlaces (7), - sliderRegionStart (0), - sliderRegionSize (1), - sliderBeingDragged (-1), - pixelsForFullDragExtent (250), - style (LinearHorizontal), - textBoxPos (TextBoxLeft), - textBoxWidth (80), - textBoxHeight (20), - incDecButtonMode (incDecButtonsNotDraggable), - editableText (true), - doubleClickToValue (false), - isVelocityBased (false), - userKeyOverridesVelocity (true), - rotaryStop (true), - incDecButtonsSideBySide (false), - sendChangeOnlyOnRelease (false), - popupDisplayEnabled (false), - menuEnabled (false), - menuShown (false), - scrollWheelEnabled (true), - snapsToMousePos (true), - valueBox (0), - incButton (0), - decButton (0), - popupDisplay (0), - parentForPopupDisplay (0) -{ - setWantsKeyboardFocus (false); - setRepaintsOnMouseActivity (true); - - lookAndFeelChanged(); - updateText(); -} - -Slider::~Slider() -{ - deleteAndZero (popupDisplay); - deleteAllChildren(); -} - - -//============================================================================== -void Slider::handleAsyncUpdate() -{ - cancelPendingUpdate(); - - for (int i = listeners.size(); --i >= 0;) - { - ((SliderListener*) listeners.getUnchecked (i))->sliderValueChanged (this); - i = jmin (i, listeners.size()); - } -} - -void Slider::sendDragStart() -{ - startedDragging(); - - for (int i = listeners.size(); --i >= 0;) - { - ((SliderListener*) listeners.getUnchecked (i))->sliderDragStarted (this); - i = jmin (i, listeners.size()); - } -} - -void Slider::sendDragEnd() -{ - stoppedDragging(); - - sliderBeingDragged = -1; - - for (int i = listeners.size(); --i >= 0;) - { - ((SliderListener*) listeners.getUnchecked (i))->sliderDragEnded (this); - i = jmin (i, listeners.size()); - } -} - -void Slider::addListener (SliderListener* const listener) throw() -{ - jassert (listener != 0); - if (listener != 0) - listeners.add (listener); -} - -void Slider::removeListener (SliderListener* const listener) throw() -{ - listeners.removeValue (listener); -} - -//============================================================================== -void Slider::setSliderStyle (const SliderStyle newStyle) -{ - if (style != newStyle) - { - style = newStyle; - repaint(); - lookAndFeelChanged(); - } -} - -void Slider::setRotaryParameters (const float startAngleRadians, - const float endAngleRadians, - const bool stopAtEnd) -{ - // make sure the values are sensible.. - jassert (rotaryStart >= 0 && rotaryEnd >= 0); - jassert (rotaryStart < float_Pi * 4.0f && rotaryEnd < float_Pi * 4.0f); - jassert (rotaryStart < rotaryEnd); - - rotaryStart = startAngleRadians; - rotaryEnd = endAngleRadians; - rotaryStop = stopAtEnd; -} - -void Slider::setVelocityBasedMode (const bool velBased) throw() -{ - isVelocityBased = velBased; -} - -void Slider::setVelocityModeParameters (const double sensitivity, - const int threshold, - const double offset, - const bool userCanPressKeyToSwapMode) throw() -{ - jassert (threshold >= 0); - jassert (sensitivity > 0); - jassert (offset >= 0); - - velocityModeSensitivity = sensitivity; - velocityModeOffset = offset; - velocityModeThreshold = threshold; - userKeyOverridesVelocity = userCanPressKeyToSwapMode; -} - -void Slider::setSkewFactor (const double factor) throw() -{ - skewFactor = factor; -} - -void Slider::setSkewFactorFromMidPoint (const double sliderValueToShowAtMidPoint) throw() -{ - if (maximum > minimum) - skewFactor = log (0.5) / log ((sliderValueToShowAtMidPoint - minimum) - / (maximum - minimum)); -} - -void Slider::setMouseDragSensitivity (const int distanceForFullScaleDrag) -{ - jassert (distanceForFullScaleDrag > 0); - - pixelsForFullDragExtent = distanceForFullScaleDrag; -} - -void Slider::setIncDecButtonsMode (const IncDecButtonMode mode) -{ - if (incDecButtonMode != mode) - { - incDecButtonMode = mode; - lookAndFeelChanged(); - } -} - -void Slider::setTextBoxStyle (const TextEntryBoxPosition newPosition, - const bool isReadOnly, - const int textEntryBoxWidth, - const int textEntryBoxHeight) -{ - textBoxPos = newPosition; - editableText = ! isReadOnly; - textBoxWidth = textEntryBoxWidth; - textBoxHeight = textEntryBoxHeight; - - repaint(); - lookAndFeelChanged(); -} - -void Slider::setTextBoxIsEditable (const bool shouldBeEditable) throw() -{ - editableText = shouldBeEditable; - - if (valueBox != 0) - valueBox->setEditable (shouldBeEditable && isEnabled()); -} - -void Slider::showTextBox() -{ - jassert (editableText); // this should probably be avoided in read-only sliders. - - if (valueBox != 0) - valueBox->showEditor(); -} - -void Slider::hideTextBox (const bool discardCurrentEditorContents) -{ - if (valueBox != 0) - { - valueBox->hideEditor (discardCurrentEditorContents); - - if (discardCurrentEditorContents) - updateText(); - } -} - -void Slider::setChangeNotificationOnlyOnRelease (const bool onlyNotifyOnRelease) throw() -{ - sendChangeOnlyOnRelease = onlyNotifyOnRelease; -} - -void Slider::setSliderSnapsToMousePosition (const bool shouldSnapToMouse) throw() -{ - snapsToMousePos = shouldSnapToMouse; -} - -void Slider::setPopupDisplayEnabled (const bool enabled, - Component* const parentComponentToUse) throw() -{ - popupDisplayEnabled = enabled; - parentForPopupDisplay = parentComponentToUse; -} - -//============================================================================== -void Slider::colourChanged() -{ - lookAndFeelChanged(); -} - -void Slider::lookAndFeelChanged() -{ - const String previousTextBoxContent (valueBox != 0 ? valueBox->getText() - : getTextFromValue (currentValue)); - - deleteAllChildren(); - valueBox = 0; - - LookAndFeel& lf = getLookAndFeel(); - - if (textBoxPos != NoTextBox) - { - addAndMakeVisible (valueBox = getLookAndFeel().createSliderTextBox (*this)); - - valueBox->setWantsKeyboardFocus (false); - valueBox->setText (previousTextBoxContent, false); - - valueBox->setEditable (editableText && isEnabled()); - valueBox->addListener (this); - - if (style == LinearBar) - valueBox->addMouseListener (this, false); - - valueBox->setTooltip (getTooltip()); - } - - if (style == IncDecButtons) - { - addAndMakeVisible (incButton = lf.createSliderButton (true)); - incButton->addButtonListener (this); - - addAndMakeVisible (decButton = lf.createSliderButton (false)); - decButton->addButtonListener (this); - - if (incDecButtonMode != incDecButtonsNotDraggable) - { - incButton->addMouseListener (this, false); - decButton->addMouseListener (this, false); - } - else - { - incButton->setRepeatSpeed (300, 100, 20); - incButton->addMouseListener (decButton, false); - - decButton->setRepeatSpeed (300, 100, 20); - decButton->addMouseListener (incButton, false); - } - } - - setComponentEffect (lf.getSliderEffect()); - - resized(); - repaint(); -} - -//============================================================================== -void Slider::setRange (const double newMin, - const double newMax, - const double newInt) -{ - if (minimum != newMin - || maximum != newMax - || interval != newInt) - { - minimum = newMin; - maximum = newMax; - interval = newInt; - - // figure out the number of DPs needed to display all values at this - // interval setting. - numDecimalPlaces = 7; - - if (newInt != 0) - { - int v = abs ((int) (newInt * 10000000)); - - while ((v % 10) == 0) - { - --numDecimalPlaces; - v /= 10; - } - } - - // keep the current values inside the new range.. - if (style != TwoValueHorizontal && style != TwoValueVertical) - { - setValue (currentValue, false, false); - } - else - { - setMinValue (getMinValue(), false, false); - setMaxValue (getMaxValue(), false, false); - } - - updateText(); - } -} - -void Slider::triggerChangeMessage (const bool synchronous) -{ - if (synchronous) - handleAsyncUpdate(); - else - triggerAsyncUpdate(); - - valueChanged(); -} - -double Slider::getValue() const throw() -{ - // for a two-value style slider, you should use the getMinValue() and getMaxValue() - // methods to get the two values. - jassert (style != TwoValueHorizontal && style != TwoValueVertical); - - return currentValue; -} - -void Slider::setValue (double newValue, - const bool sendUpdateMessage, - const bool sendMessageSynchronously) -{ - // for a two-value style slider, you should use the setMinValue() and setMaxValue() - // methods to set the two values. - jassert (style != TwoValueHorizontal && style != TwoValueVertical); - - newValue = constrainedValue (newValue); - - if (style == ThreeValueHorizontal || style == ThreeValueVertical) - { - jassert (valueMin <= valueMax); - newValue = jlimit (valueMin, valueMax, newValue); - } - - if (currentValue != newValue) - { - if (valueBox != 0) - valueBox->hideEditor (true); - - currentValue = newValue; - updateText(); - repaint(); - - if (popupDisplay != 0) - { - ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition (getTextFromValue (currentValue)); - popupDisplay->repaint(); - } - - if (sendUpdateMessage) - triggerChangeMessage (sendMessageSynchronously); - } -} - -double Slider::getMinValue() const throw() -{ - // The minimum value only applies to sliders that are in two- or three-value mode. - jassert (style == TwoValueHorizontal || style == TwoValueVertical - || style == ThreeValueHorizontal || style == ThreeValueVertical); - - return valueMin; -} - -double Slider::getMaxValue() const throw() -{ - // The maximum value only applies to sliders that are in two- or three-value mode. - jassert (style == TwoValueHorizontal || style == TwoValueVertical - || style == ThreeValueHorizontal || style == ThreeValueVertical); - - return valueMax; -} - -void Slider::setMinValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously, const bool allowNudgingOfOtherValues) -{ - // The minimum value only applies to sliders that are in two- or three-value mode. - jassert (style == TwoValueHorizontal || style == TwoValueVertical - || style == ThreeValueHorizontal || style == ThreeValueVertical); - - newValue = constrainedValue (newValue); - - if (style == TwoValueHorizontal || style == TwoValueVertical) - { - if (allowNudgingOfOtherValues && newValue > valueMax) - setMaxValue (newValue, sendUpdateMessage, sendMessageSynchronously); - - newValue = jmin (valueMax, newValue); - } - else - { - if (allowNudgingOfOtherValues && newValue > currentValue) - setValue (newValue, sendUpdateMessage, sendMessageSynchronously); - - newValue = jmin (currentValue, newValue); - } - - if (valueMin != newValue) - { - valueMin = newValue; - repaint(); - - if (popupDisplay != 0) - { - ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition (getTextFromValue (valueMin)); - popupDisplay->repaint(); - } - - if (sendUpdateMessage) - triggerChangeMessage (sendMessageSynchronously); - } -} - -void Slider::setMaxValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously, const bool allowNudgingOfOtherValues) -{ - // The maximum value only applies to sliders that are in two- or three-value mode. - jassert (style == TwoValueHorizontal || style == TwoValueVertical - || style == ThreeValueHorizontal || style == ThreeValueVertical); - - newValue = constrainedValue (newValue); - - if (style == TwoValueHorizontal || style == TwoValueVertical) - { - if (allowNudgingOfOtherValues && newValue < valueMin) - setMinValue (newValue, sendUpdateMessage, sendMessageSynchronously); - - newValue = jmax (valueMin, newValue); - } - else - { - if (allowNudgingOfOtherValues && newValue < currentValue) - setValue (newValue, sendUpdateMessage, sendMessageSynchronously); - - newValue = jmax (currentValue, newValue); - } - - if (valueMax != newValue) - { - valueMax = newValue; - repaint(); - - if (popupDisplay != 0) - { - ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition (getTextFromValue (valueMax)); - popupDisplay->repaint(); - } - - if (sendUpdateMessage) - triggerChangeMessage (sendMessageSynchronously); - } -} - -void Slider::setDoubleClickReturnValue (const bool isDoubleClickEnabled, - const double valueToSetOnDoubleClick) throw() -{ - doubleClickToValue = isDoubleClickEnabled; - doubleClickReturnValue = valueToSetOnDoubleClick; -} - -double Slider::getDoubleClickReturnValue (bool& isEnabled_) const throw() -{ - isEnabled_ = doubleClickToValue; - return doubleClickReturnValue; -} - -void Slider::updateText() -{ - if (valueBox != 0) - valueBox->setText (getTextFromValue (currentValue), false); -} - -void Slider::setTextValueSuffix (const String& suffix) -{ - if (textSuffix != suffix) - { - textSuffix = suffix; - updateText(); - } -} - -const String Slider::getTextFromValue (double v) -{ - if (numDecimalPlaces > 0) - return String (v, numDecimalPlaces) + textSuffix; - else - return String (roundDoubleToInt (v)) + textSuffix; -} - -double Slider::getValueFromText (const String& text) -{ - String t (text.trimStart()); - - if (t.endsWith (textSuffix)) - t = t.substring (0, t.length() - textSuffix.length()); - - while (t.startsWithChar (T('+'))) - t = t.substring (1).trimStart(); - - return t.initialSectionContainingOnly (T("0123456789.,-")) - .getDoubleValue(); -} - -double Slider::proportionOfLengthToValue (double proportion) -{ - if (skewFactor != 1.0 && proportion > 0.0) - proportion = exp (log (proportion) / skewFactor); - - return minimum + (maximum - minimum) * proportion; -} - -double Slider::valueToProportionOfLength (double value) -{ - const double n = (value - minimum) / (maximum - minimum); - - return skewFactor == 1.0 ? n : pow (n, skewFactor); -} - -double Slider::snapValue (double attemptedValue, const bool) -{ - return attemptedValue; -} - -//============================================================================== -void Slider::startedDragging() -{ -} - -void Slider::stoppedDragging() -{ -} - -void Slider::valueChanged() -{ -} - -//============================================================================== -void Slider::enablementChanged() -{ - repaint(); -} - -void Slider::setPopupMenuEnabled (const bool menuEnabled_) throw() -{ - menuEnabled = menuEnabled_; -} - -void Slider::setScrollWheelEnabled (const bool enabled) throw() -{ - scrollWheelEnabled = enabled; -} - -//============================================================================== -void Slider::labelTextChanged (Label* label) -{ - const double newValue = snapValue (getValueFromText (label->getText()), false); - - if (getValue() != newValue) - { - sendDragStart(); - setValue (newValue, true, true); - sendDragEnd(); - } - - updateText(); // force a clean-up of the text, needed in case setValue() hasn't done this. -} - -void Slider::buttonClicked (Button* button) -{ - if (style == IncDecButtons) - { - sendDragStart(); - - if (button == incButton) - setValue (snapValue (getValue() + interval, false), true, true); - else if (button == decButton) - setValue (snapValue (getValue() - interval, false), true, true); - - sendDragEnd(); - } -} - -//============================================================================== -double Slider::constrainedValue (double value) const throw() -{ - if (interval > 0) - value = minimum + interval * floor ((value - minimum) / interval + 0.5); - - if (value <= minimum || maximum <= minimum) - value = minimum; - else if (value >= maximum) - value = maximum; - - return value; -} - -float Slider::getLinearSliderPos (const double value) -{ - double sliderPosProportional; - - if (maximum > minimum) - { - if (value < minimum) - { - sliderPosProportional = 0.0; - } - else if (value > maximum) - { - sliderPosProportional = 1.0; - } - else - { - sliderPosProportional = valueToProportionOfLength (value); - jassert (sliderPosProportional >= 0 && sliderPosProportional <= 1.0); - } - } - else - { - sliderPosProportional = 0.5; - } - - if (isVertical() || style == IncDecButtons) - sliderPosProportional = 1.0 - sliderPosProportional; - - return (float) (sliderRegionStart + sliderPosProportional * sliderRegionSize); -} - -bool Slider::isHorizontal() const throw() -{ - return style == LinearHorizontal - || style == LinearBar - || style == TwoValueHorizontal - || style == ThreeValueHorizontal; -} - -bool Slider::isVertical() const throw() -{ - return style == LinearVertical - || style == TwoValueVertical - || style == ThreeValueVertical; -} - -bool Slider::incDecDragDirectionIsHorizontal() const throw() -{ - return incDecButtonMode == incDecButtonsDraggable_Horizontal - || (incDecButtonMode == incDecButtonsDraggable_AutoDirection && incDecButtonsSideBySide); -} - -float Slider::getPositionOfValue (const double value) -{ - if (isHorizontal() || isVertical()) - { - return getLinearSliderPos (value); - } - else - { - jassertfalse // not a valid call on a slider that doesn't work linearly! - return 0.0f; - } -} - -//============================================================================== -void Slider::paint (Graphics& g) -{ - if (style != IncDecButtons) - { - if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag) - { - const float sliderPos = (float) valueToProportionOfLength (currentValue); - jassert (sliderPos >= 0 && sliderPos <= 1.0f); - - getLookAndFeel().drawRotarySlider (g, - sliderRect.getX(), - sliderRect.getY(), - sliderRect.getWidth(), - sliderRect.getHeight(), - sliderPos, - rotaryStart, rotaryEnd, - *this); - } - else - { - getLookAndFeel().drawLinearSlider (g, - sliderRect.getX(), - sliderRect.getY(), - sliderRect.getWidth(), - sliderRect.getHeight(), - getLinearSliderPos (currentValue), - getLinearSliderPos (valueMin), - getLinearSliderPos (valueMax), - style, - *this); - } - - if (style == LinearBar && valueBox == 0) - { - g.setColour (findColour (Slider::textBoxOutlineColourId)); - g.drawRect (0, 0, getWidth(), getHeight(), 1); - } - } -} - -void Slider::resized() -{ - int minXSpace = 0; - int minYSpace = 0; - - if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight) - minXSpace = 30; - else - minYSpace = 15; - - const int tbw = jmax (0, jmin (textBoxWidth, getWidth() - minXSpace)); - const int tbh = jmax (0, jmin (textBoxHeight, getHeight() - minYSpace)); - - if (style == LinearBar) - { - if (valueBox != 0) - valueBox->setBounds (0, 0, getWidth(), getHeight()); - } - else - { - if (textBoxPos == NoTextBox) - { - sliderRect.setBounds (0, 0, getWidth(), getHeight()); - } - else if (textBoxPos == TextBoxLeft) - { - valueBox->setBounds (0, (getHeight() - tbh) / 2, tbw, tbh); - sliderRect.setBounds (tbw, 0, getWidth() - tbw, getHeight()); - } - else if (textBoxPos == TextBoxRight) - { - valueBox->setBounds (getWidth() - tbw, (getHeight() - tbh) / 2, tbw, tbh); - sliderRect.setBounds (0, 0, getWidth() - tbw, getHeight()); - } - else if (textBoxPos == TextBoxAbove) - { - valueBox->setBounds ((getWidth() - tbw) / 2, 0, tbw, tbh); - sliderRect.setBounds (0, tbh, getWidth(), getHeight() - tbh); - } - else if (textBoxPos == TextBoxBelow) - { - valueBox->setBounds ((getWidth() - tbw) / 2, getHeight() - tbh, tbw, tbh); - sliderRect.setBounds (0, 0, getWidth(), getHeight() - tbh); - } - } - - const int indent = getLookAndFeel().getSliderThumbRadius (*this); - - if (style == LinearBar) - { - const int barIndent = 1; - sliderRegionStart = barIndent; - sliderRegionSize = getWidth() - barIndent * 2; - - sliderRect.setBounds (sliderRegionStart, barIndent, - sliderRegionSize, getHeight() - barIndent * 2); - } - else if (isHorizontal()) - { - sliderRegionStart = sliderRect.getX() + indent; - sliderRegionSize = jmax (1, sliderRect.getWidth() - indent * 2); - - sliderRect.setBounds (sliderRegionStart, sliderRect.getY(), - sliderRegionSize, sliderRect.getHeight()); - } - else if (isVertical()) - { - sliderRegionStart = sliderRect.getY() + indent; - sliderRegionSize = jmax (1, sliderRect.getHeight() - indent * 2); - - sliderRect.setBounds (sliderRect.getX(), sliderRegionStart, - sliderRect.getWidth(), sliderRegionSize); - } - else - { - sliderRegionStart = 0; - sliderRegionSize = 100; - } - - if (style == IncDecButtons) - { - Rectangle buttonRect (sliderRect); - - if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight) - buttonRect.expand (-2, 0); - else - buttonRect.expand (0, -2); - - incDecButtonsSideBySide = buttonRect.getWidth() > buttonRect.getHeight(); - - if (incDecButtonsSideBySide) - { - decButton->setBounds (buttonRect.getX(), - buttonRect.getY(), - buttonRect.getWidth() / 2, - buttonRect.getHeight()); - - decButton->setConnectedEdges (Button::ConnectedOnRight); - - incButton->setBounds (buttonRect.getCentreX(), - buttonRect.getY(), - buttonRect.getWidth() / 2, - buttonRect.getHeight()); - - incButton->setConnectedEdges (Button::ConnectedOnLeft); - } - else - { - incButton->setBounds (buttonRect.getX(), - buttonRect.getY(), - buttonRect.getWidth(), - buttonRect.getHeight() / 2); - - incButton->setConnectedEdges (Button::ConnectedOnBottom); - - decButton->setBounds (buttonRect.getX(), - buttonRect.getCentreY(), - buttonRect.getWidth(), - buttonRect.getHeight() / 2); - - decButton->setConnectedEdges (Button::ConnectedOnTop); - } - } -} - -void Slider::focusOfChildComponentChanged (FocusChangeType) -{ - repaint(); -} - -void Slider::mouseDown (const MouseEvent& e) -{ - mouseWasHidden = false; - incDecDragged = false; - mouseXWhenLastDragged = e.x; - mouseYWhenLastDragged = e.y; - mouseDragStartX = e.getMouseDownX(); - mouseDragStartY = e.getMouseDownY(); - - if (isEnabled()) - { - if (e.mods.isPopupMenu() && menuEnabled) - { - menuShown = true; - - PopupMenu m; - m.addItem (1, TRANS ("velocity-sensitive mode"), true, isVelocityBased); - m.addSeparator(); - - if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag) - { - PopupMenu rotaryMenu; - rotaryMenu.addItem (2, TRANS ("use circular dragging"), true, style == Rotary); - rotaryMenu.addItem (3, TRANS ("use left-right dragging"), true, style == RotaryHorizontalDrag); - rotaryMenu.addItem (4, TRANS ("use up-down dragging"), true, style == RotaryVerticalDrag); - - m.addSubMenu (TRANS ("rotary mode"), rotaryMenu); - } - - const int r = m.show(); - - if (r == 1) - { - setVelocityBasedMode (! isVelocityBased); - } - else if (r == 2) - { - setSliderStyle (Rotary); - } - else if (r == 3) - { - setSliderStyle (RotaryHorizontalDrag); - } - else if (r == 4) - { - setSliderStyle (RotaryVerticalDrag); - } - } - else if (maximum > minimum) - { - menuShown = false; - - if (valueBox != 0) - valueBox->hideEditor (true); - - sliderBeingDragged = 0; - - if (style == TwoValueHorizontal - || style == TwoValueVertical - || style == ThreeValueHorizontal - || style == ThreeValueVertical) - { - const float mousePos = (float) (isVertical() ? e.y : e.x); - - const float normalPosDistance = fabsf (getLinearSliderPos (currentValue) - mousePos); - const float minPosDistance = fabsf (getLinearSliderPos (valueMin) - 0.1f - mousePos); - const float maxPosDistance = fabsf (getLinearSliderPos (valueMax) + 0.1f - mousePos); - - if (style == TwoValueHorizontal || style == TwoValueVertical) - { - if (maxPosDistance <= minPosDistance) - sliderBeingDragged = 2; - else - sliderBeingDragged = 1; - } - else if (style == ThreeValueHorizontal || style == ThreeValueVertical) - { - if (normalPosDistance >= minPosDistance && maxPosDistance >= minPosDistance) - sliderBeingDragged = 1; - else if (normalPosDistance >= maxPosDistance) - sliderBeingDragged = 2; - } - } - - minMaxDiff = valueMax - valueMin; - - lastAngle = rotaryStart + (rotaryEnd - rotaryStart) - * valueToProportionOfLength (currentValue); - - if (sliderBeingDragged == 2) - valueWhenLastDragged = valueMax; - else if (sliderBeingDragged == 1) - valueWhenLastDragged = valueMin; - else - valueWhenLastDragged = currentValue; - - valueOnMouseDown = valueWhenLastDragged; - - if (popupDisplayEnabled) - { - SliderPopupDisplayComponent* const popup = new SliderPopupDisplayComponent (this); - popupDisplay = popup; - - if (parentForPopupDisplay != 0) - { - parentForPopupDisplay->addChildComponent (popup); - } - else - { - popup->addToDesktop (0); - } - - popup->setVisible (true); - } - - sendDragStart(); - - mouseDrag (e); - } - } -} - -void Slider::mouseUp (const MouseEvent&) -{ - if (isEnabled() - && (! menuShown) - && (maximum > minimum) - && (style != IncDecButtons || incDecDragged)) - { - restoreMouseIfHidden(); - - if (sendChangeOnlyOnRelease && valueOnMouseDown != currentValue) - triggerChangeMessage (false); - - sendDragEnd(); - - deleteAndZero (popupDisplay); - - if (style == IncDecButtons) - { - incButton->setState (Button::buttonNormal); - decButton->setState (Button::buttonNormal); - } - } -} - -void Slider::restoreMouseIfHidden() -{ - if (mouseWasHidden) - { - mouseWasHidden = false; - - Component* c = Component::getComponentUnderMouse(); - - if (c == 0) - c = this; - - c->enableUnboundedMouseMovement (false); - - const double pos = (sliderBeingDragged == 2) ? getMaxValue() - : ((sliderBeingDragged == 1) ? getMinValue() - : currentValue); - - if (style == RotaryHorizontalDrag || style == RotaryVerticalDrag) - { - int x, y, downX, downY; - Desktop::getMousePosition (x, y); - Desktop::getLastMouseDownPosition (downX, downY); - - if (style == RotaryHorizontalDrag) - { - const double posDiff = valueToProportionOfLength (pos) - valueToProportionOfLength (valueOnMouseDown); - x = roundDoubleToInt (pixelsForFullDragExtent * posDiff + downX); - y = downY; - } - else - { - const double posDiff = valueToProportionOfLength (valueOnMouseDown) - valueToProportionOfLength (pos); - x = downX; - y = roundDoubleToInt (pixelsForFullDragExtent * posDiff + downY); - } - - Desktop::setMousePosition (x, y); - } - else - { - const int pixelPos = (int) getLinearSliderPos (pos); - - int x = isHorizontal() ? pixelPos : (getWidth() / 2); - int y = isVertical() ? pixelPos : (getHeight() / 2); - - relativePositionToGlobal (x, y); - Desktop::setMousePosition (x, y); - } - } -} - -void Slider::modifierKeysChanged (const ModifierKeys& modifiers) -{ - if (isEnabled() - && style != IncDecButtons - && style != Rotary - && isVelocityBased == modifiers.isAnyModifierKeyDown()) - { - restoreMouseIfHidden(); - } -} - -static double smallestAngleBetween (double a1, double a2) -{ - return jmin (fabs (a1 - a2), - fabs (a1 + double_Pi * 2.0 - a2), - fabs (a2 + double_Pi * 2.0 - a1)); -} - -void Slider::mouseDrag (const MouseEvent& e) -{ - if (isEnabled() - && (! menuShown) - && (maximum > minimum)) - { - if (style == Rotary) - { - int dx = e.x - sliderRect.getCentreX(); - int dy = e.y - sliderRect.getCentreY(); - - if (dx * dx + dy * dy > 25) - { - double angle = atan2 ((double) dx, (double) -dy); - while (angle < 0.0) - angle += double_Pi * 2.0; - - if (rotaryStop && ! e.mouseWasClicked()) - { - if (fabs (angle - lastAngle) > double_Pi) - { - if (angle >= lastAngle) - angle -= double_Pi * 2.0; - else - angle += double_Pi * 2.0; - } - - if (angle >= lastAngle) - angle = jmin (angle, (double) jmax (rotaryStart, rotaryEnd)); - else - angle = jmax (angle, (double) jmin (rotaryStart, rotaryEnd)); - } - else - { - while (angle < rotaryStart) - angle += double_Pi * 2.0; - - if (angle > rotaryEnd) - { - if (smallestAngleBetween (angle, rotaryStart) <= smallestAngleBetween (angle, rotaryEnd)) - angle = rotaryStart; - else - angle = rotaryEnd; - } - } - - const double proportion = (angle - rotaryStart) / (rotaryEnd - rotaryStart); - - valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, proportion)); - - lastAngle = angle; - } - } - else - { - if (style == LinearBar && e.mouseWasClicked() - && valueBox != 0 && valueBox->isEditable()) - return; - - if (style == IncDecButtons && ! incDecDragged) - { - if (e.getDistanceFromDragStart() < 10 || e.mouseWasClicked()) - return; - - incDecDragged = true; - mouseDragStartX = e.x; - mouseDragStartY = e.y; - } - - if ((isVelocityBased == (userKeyOverridesVelocity ? e.mods.testFlags (ModifierKeys::ctrlModifier | ModifierKeys::commandModifier | ModifierKeys::altModifier) - : false)) - || ((maximum - minimum) / sliderRegionSize < interval)) - { - const int mousePos = (isHorizontal() || style == RotaryHorizontalDrag) ? e.x : e.y; - - double scaledMousePos = (mousePos - sliderRegionStart) / (double) sliderRegionSize; - - if (style == RotaryHorizontalDrag - || style == RotaryVerticalDrag - || style == IncDecButtons - || ((style == LinearHorizontal || style == LinearVertical || style == LinearBar) - && ! snapsToMousePos)) - { - const int mouseDiff = (style == RotaryHorizontalDrag - || style == LinearHorizontal - || style == LinearBar - || (style == IncDecButtons && incDecDragDirectionIsHorizontal())) - ? e.x - mouseDragStartX - : mouseDragStartY - e.y; - - double newPos = valueToProportionOfLength (valueOnMouseDown) - + mouseDiff * (1.0 / pixelsForFullDragExtent); - - valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, newPos)); - - if (style == IncDecButtons) - { - incButton->setState (mouseDiff < 0 ? Button::buttonNormal : Button::buttonDown); - decButton->setState (mouseDiff > 0 ? Button::buttonNormal : Button::buttonDown); - } - } - else - { - if (isVertical()) - scaledMousePos = 1.0 - scaledMousePos; - - valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, scaledMousePos)); - } - } - else - { - const int mouseDiff = (isHorizontal() || style == RotaryHorizontalDrag - || (style == IncDecButtons && incDecDragDirectionIsHorizontal())) - ? e.x - mouseXWhenLastDragged - : e.y - mouseYWhenLastDragged; - - const double maxSpeed = jmax (200, sliderRegionSize); - double speed = jlimit (0.0, maxSpeed, (double) abs (mouseDiff)); - - if (speed != 0) - { - speed = 0.2 * velocityModeSensitivity - * (1.0 + sin (double_Pi * (1.5 + jmin (0.5, velocityModeOffset - + jmax (0.0, (double) (speed - velocityModeThreshold)) - / maxSpeed)))); - - if (mouseDiff < 0) - speed = -speed; - - if (isVertical() || style == RotaryVerticalDrag - || (style == IncDecButtons && ! incDecDragDirectionIsHorizontal())) - speed = -speed; - - const double currentPos = valueToProportionOfLength (valueWhenLastDragged); - - valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + speed)); - - e.originalComponent->enableUnboundedMouseMovement (true, false); - mouseWasHidden = true; - } - } - } - - valueWhenLastDragged = jlimit (minimum, maximum, valueWhenLastDragged); - - if (sliderBeingDragged == 0) - { - setValue (snapValue (valueWhenLastDragged, true), - ! sendChangeOnlyOnRelease, true); - } - else if (sliderBeingDragged == 1) - { - setMinValue (snapValue (valueWhenLastDragged, true), - ! sendChangeOnlyOnRelease, false, true); - - if (e.mods.isShiftDown()) - setMaxValue (getMinValue() + minMaxDiff, false, false, true); - else - minMaxDiff = valueMax - valueMin; - } - else - { - jassert (sliderBeingDragged == 2); - - setMaxValue (snapValue (valueWhenLastDragged, true), - ! sendChangeOnlyOnRelease, false, true); - - if (e.mods.isShiftDown()) - setMinValue (getMaxValue() - minMaxDiff, false, false, true); - else - minMaxDiff = valueMax - valueMin; - } - - mouseXWhenLastDragged = e.x; - mouseYWhenLastDragged = e.y; - } -} - -void Slider::mouseDoubleClick (const MouseEvent&) -{ - if (doubleClickToValue - && isEnabled() - && style != IncDecButtons - && minimum <= doubleClickReturnValue - && maximum >= doubleClickReturnValue) - { - sendDragStart(); - setValue (doubleClickReturnValue, true, true); - sendDragEnd(); - } -} - -void Slider::mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY) -{ - if (scrollWheelEnabled && isEnabled() - && style != TwoValueHorizontal - && style != TwoValueVertical) - { - if (maximum > minimum && ! isMouseButtonDownAnywhere()) - { - if (valueBox != 0) - valueBox->hideEditor (false); - - const double proportionDelta = (wheelIncrementX != 0 ? -wheelIncrementX : wheelIncrementY) * 0.15f; - const double currentPos = valueToProportionOfLength (currentValue); - const double newValue = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + proportionDelta)); - - double delta = (newValue != currentValue) - ? jmax (fabs (newValue - currentValue), interval) : 0; - - if (currentValue > newValue) - delta = -delta; - - sendDragStart(); - setValue (snapValue (currentValue + delta, false), true, true); - sendDragEnd(); - } - } - else - { - Component::mouseWheelMove (e, wheelIncrementX, wheelIncrementY); - } -} - -void SliderListener::sliderDragStarted (Slider*) -{ -} - -void SliderListener::sliderDragEnded (Slider*) -{ -} - - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + JUCE is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../../../../juce_core/basics/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + +#include "juce_Slider.h" +#include "../lookandfeel/juce_LookAndFeel.h" +#include "../menus/juce_PopupMenu.h" +#include "../juce_Desktop.h" +#include "../special/juce_BubbleComponent.h" +#include "../../../../juce_core/text/juce_LocalisedStrings.h" + + +//============================================================================== +class SliderPopupDisplayComponent : public BubbleComponent +{ +public: + //============================================================================== + SliderPopupDisplayComponent (Slider* const owner_) + : owner (owner_), + font (15.0f, Font::bold) + { + setAlwaysOnTop (true); + } + + ~SliderPopupDisplayComponent() + { + } + + void paintContent (Graphics& g, int w, int h) + { + g.setFont (font); + g.setColour (Colours::black); + + g.drawFittedText (text, 0, 0, w, h, Justification::centred, 1); + } + + void getContentSize (int& w, int& h) + { + w = font.getStringWidth (text) + 18; + h = (int) (font.getHeight() * 1.6f); + } + + void updatePosition (const String& newText) + { + if (text != newText) + { + text = newText; + repaint(); + } + + BubbleComponent::setPosition (owner); + } + + //============================================================================== + juce_UseDebuggingNewOperator + +private: + Slider* owner; + Font font; + String text; + + SliderPopupDisplayComponent (const SliderPopupDisplayComponent&); + const SliderPopupDisplayComponent& operator= (const SliderPopupDisplayComponent&); +}; + +//============================================================================== +Slider::Slider (const String& name) + : Component (name), + listeners (2), + currentValue (0.0), + valueMin (0.0), + valueMax (0.0), + minimum (0), + maximum (10), + interval (0), + skewFactor (1.0), + velocityModeSensitivity (1.0), + velocityModeOffset (0.0), + velocityModeThreshold (1), + rotaryStart (float_Pi * 1.2f), + rotaryEnd (float_Pi * 2.8f), + numDecimalPlaces (7), + sliderRegionStart (0), + sliderRegionSize (1), + sliderBeingDragged (-1), + pixelsForFullDragExtent (250), + style (LinearHorizontal), + textBoxPos (TextBoxLeft), + textBoxWidth (80), + textBoxHeight (20), + incDecButtonMode (incDecButtonsNotDraggable), + editableText (true), + doubleClickToValue (false), + isVelocityBased (false), + userKeyOverridesVelocity (true), + rotaryStop (true), + incDecButtonsSideBySide (false), + sendChangeOnlyOnRelease (false), + popupDisplayEnabled (false), + menuEnabled (false), + menuShown (false), + scrollWheelEnabled (true), + snapsToMousePos (true), + valueBox (0), + incButton (0), + decButton (0), + popupDisplay (0), + parentForPopupDisplay (0) +{ + setWantsKeyboardFocus (false); + setRepaintsOnMouseActivity (true); + + lookAndFeelChanged(); + updateText(); +} + +Slider::~Slider() +{ + deleteAndZero (popupDisplay); + deleteAllChildren(); +} + + +//============================================================================== +void Slider::handleAsyncUpdate() +{ + cancelPendingUpdate(); + + for (int i = listeners.size(); --i >= 0;) + { + ((SliderListener*) listeners.getUnchecked (i))->sliderValueChanged (this); + i = jmin (i, listeners.size()); + } +} + +void Slider::sendDragStart() +{ + startedDragging(); + + for (int i = listeners.size(); --i >= 0;) + { + ((SliderListener*) listeners.getUnchecked (i))->sliderDragStarted (this); + i = jmin (i, listeners.size()); + } +} + +void Slider::sendDragEnd() +{ + stoppedDragging(); + + sliderBeingDragged = -1; + + for (int i = listeners.size(); --i >= 0;) + { + ((SliderListener*) listeners.getUnchecked (i))->sliderDragEnded (this); + i = jmin (i, listeners.size()); + } +} + +void Slider::addListener (SliderListener* const listener) throw() +{ + jassert (listener != 0); + if (listener != 0) + listeners.add (listener); +} + +void Slider::removeListener (SliderListener* const listener) throw() +{ + listeners.removeValue (listener); +} + +//============================================================================== +void Slider::setSliderStyle (const SliderStyle newStyle) +{ + if (style != newStyle) + { + style = newStyle; + repaint(); + lookAndFeelChanged(); + } +} + +void Slider::setRotaryParameters (const float startAngleRadians, + const float endAngleRadians, + const bool stopAtEnd) +{ + // make sure the values are sensible.. + jassert (rotaryStart >= 0 && rotaryEnd >= 0); + jassert (rotaryStart < float_Pi * 4.0f && rotaryEnd < float_Pi * 4.0f); + jassert (rotaryStart < rotaryEnd); + + rotaryStart = startAngleRadians; + rotaryEnd = endAngleRadians; + rotaryStop = stopAtEnd; +} + +void Slider::setVelocityBasedMode (const bool velBased) throw() +{ + isVelocityBased = velBased; +} + +void Slider::setVelocityModeParameters (const double sensitivity, + const int threshold, + const double offset, + const bool userCanPressKeyToSwapMode) throw() +{ + jassert (threshold >= 0); + jassert (sensitivity > 0); + jassert (offset >= 0); + + velocityModeSensitivity = sensitivity; + velocityModeOffset = offset; + velocityModeThreshold = threshold; + userKeyOverridesVelocity = userCanPressKeyToSwapMode; +} + +void Slider::setSkewFactor (const double factor) throw() +{ + skewFactor = factor; +} + +void Slider::setSkewFactorFromMidPoint (const double sliderValueToShowAtMidPoint) throw() +{ + if (maximum > minimum) + skewFactor = log (0.5) / log ((sliderValueToShowAtMidPoint - minimum) + / (maximum - minimum)); +} + +void Slider::setMouseDragSensitivity (const int distanceForFullScaleDrag) +{ + jassert (distanceForFullScaleDrag > 0); + + pixelsForFullDragExtent = distanceForFullScaleDrag; +} + +void Slider::setIncDecButtonsMode (const IncDecButtonMode mode) +{ + if (incDecButtonMode != mode) + { + incDecButtonMode = mode; + lookAndFeelChanged(); + } +} + +void Slider::setTextBoxStyle (const TextEntryBoxPosition newPosition, + const bool isReadOnly, + const int textEntryBoxWidth, + const int textEntryBoxHeight) +{ + textBoxPos = newPosition; + editableText = ! isReadOnly; + textBoxWidth = textEntryBoxWidth; + textBoxHeight = textEntryBoxHeight; + + repaint(); + lookAndFeelChanged(); +} + +void Slider::setTextBoxIsEditable (const bool shouldBeEditable) throw() +{ + editableText = shouldBeEditable; + + if (valueBox != 0) + valueBox->setEditable (shouldBeEditable && isEnabled()); +} + +void Slider::showTextBox() +{ + jassert (editableText); // this should probably be avoided in read-only sliders. + + if (valueBox != 0) + valueBox->showEditor(); +} + +void Slider::hideTextBox (const bool discardCurrentEditorContents) +{ + if (valueBox != 0) + { + valueBox->hideEditor (discardCurrentEditorContents); + + if (discardCurrentEditorContents) + updateText(); + } +} + +void Slider::setChangeNotificationOnlyOnRelease (const bool onlyNotifyOnRelease) throw() +{ + sendChangeOnlyOnRelease = onlyNotifyOnRelease; +} + +void Slider::setSliderSnapsToMousePosition (const bool shouldSnapToMouse) throw() +{ + snapsToMousePos = shouldSnapToMouse; +} + +void Slider::setPopupDisplayEnabled (const bool enabled, + Component* const parentComponentToUse) throw() +{ + popupDisplayEnabled = enabled; + parentForPopupDisplay = parentComponentToUse; +} + +//============================================================================== +void Slider::colourChanged() +{ + lookAndFeelChanged(); +} + +void Slider::lookAndFeelChanged() +{ + const String previousTextBoxContent (valueBox != 0 ? valueBox->getText() + : getTextFromValue (currentValue)); + + deleteAllChildren(); + valueBox = 0; + + LookAndFeel& lf = getLookAndFeel(); + + if (textBoxPos != NoTextBox) + { + addAndMakeVisible (valueBox = getLookAndFeel().createSliderTextBox (*this)); + + valueBox->setWantsKeyboardFocus (false); + valueBox->setText (previousTextBoxContent, false); + + valueBox->setEditable (editableText && isEnabled()); + valueBox->addListener (this); + + if (style == LinearBar) + valueBox->addMouseListener (this, false); + + valueBox->setTooltip (getTooltip()); + } + + if (style == IncDecButtons) + { + addAndMakeVisible (incButton = lf.createSliderButton (true)); + incButton->addButtonListener (this); + + addAndMakeVisible (decButton = lf.createSliderButton (false)); + decButton->addButtonListener (this); + + if (incDecButtonMode != incDecButtonsNotDraggable) + { + incButton->addMouseListener (this, false); + decButton->addMouseListener (this, false); + } + else + { + incButton->setRepeatSpeed (300, 100, 20); + incButton->addMouseListener (decButton, false); + + decButton->setRepeatSpeed (300, 100, 20); + decButton->addMouseListener (incButton, false); + } + } + + setComponentEffect (lf.getSliderEffect()); + + resized(); + repaint(); +} + +//============================================================================== +void Slider::setRange (const double newMin, + const double newMax, + const double newInt) +{ + if (minimum != newMin + || maximum != newMax + || interval != newInt) + { + minimum = newMin; + maximum = newMax; + interval = newInt; + + // figure out the number of DPs needed to display all values at this + // interval setting. + numDecimalPlaces = 7; + + if (newInt != 0) + { + int v = abs ((int) (newInt * 10000000)); + + while ((v % 10) == 0) + { + --numDecimalPlaces; + v /= 10; + } + } + + // keep the current values inside the new range.. + if (style != TwoValueHorizontal && style != TwoValueVertical) + { + setValue (currentValue, false, false); + } + else + { + setMinValue (getMinValue(), false, false); + setMaxValue (getMaxValue(), false, false); + } + + updateText(); + } +} + +void Slider::triggerChangeMessage (const bool synchronous) +{ + if (synchronous) + handleAsyncUpdate(); + else + triggerAsyncUpdate(); + + valueChanged(); +} + +double Slider::getValue() const throw() +{ + // for a two-value style slider, you should use the getMinValue() and getMaxValue() + // methods to get the two values. + jassert (style != TwoValueHorizontal && style != TwoValueVertical); + + return currentValue; +} + +void Slider::setValue (double newValue, + const bool sendUpdateMessage, + const bool sendMessageSynchronously) +{ + // for a two-value style slider, you should use the setMinValue() and setMaxValue() + // methods to set the two values. + jassert (style != TwoValueHorizontal && style != TwoValueVertical); + + newValue = constrainedValue (newValue); + + if (style == ThreeValueHorizontal || style == ThreeValueVertical) + { + jassert (valueMin <= valueMax); + newValue = jlimit (valueMin, valueMax, newValue); + } + + if (currentValue != newValue) + { + if (valueBox != 0) + valueBox->hideEditor (true); + + currentValue = newValue; + updateText(); + repaint(); + + if (popupDisplay != 0) + { + ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition (getTextFromValue (currentValue)); + popupDisplay->repaint(); + } + + if (sendUpdateMessage) + triggerChangeMessage (sendMessageSynchronously); + } +} + +double Slider::getMinValue() const throw() +{ + // The minimum value only applies to sliders that are in two- or three-value mode. + jassert (style == TwoValueHorizontal || style == TwoValueVertical + || style == ThreeValueHorizontal || style == ThreeValueVertical); + + return valueMin; +} + +double Slider::getMaxValue() const throw() +{ + // The maximum value only applies to sliders that are in two- or three-value mode. + jassert (style == TwoValueHorizontal || style == TwoValueVertical + || style == ThreeValueHorizontal || style == ThreeValueVertical); + + return valueMax; +} + +void Slider::setMinValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously, const bool allowNudgingOfOtherValues) +{ + // The minimum value only applies to sliders that are in two- or three-value mode. + jassert (style == TwoValueHorizontal || style == TwoValueVertical + || style == ThreeValueHorizontal || style == ThreeValueVertical); + + newValue = constrainedValue (newValue); + + if (style == TwoValueHorizontal || style == TwoValueVertical) + { + if (allowNudgingOfOtherValues && newValue > valueMax) + setMaxValue (newValue, sendUpdateMessage, sendMessageSynchronously); + + newValue = jmin (valueMax, newValue); + } + else + { + if (allowNudgingOfOtherValues && newValue > currentValue) + setValue (newValue, sendUpdateMessage, sendMessageSynchronously); + + newValue = jmin (currentValue, newValue); + } + + if (valueMin != newValue) + { + valueMin = newValue; + repaint(); + + if (popupDisplay != 0) + { + ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition (getTextFromValue (valueMin)); + popupDisplay->repaint(); + } + + if (sendUpdateMessage) + triggerChangeMessage (sendMessageSynchronously); + } +} + +void Slider::setMaxValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously, const bool allowNudgingOfOtherValues) +{ + // The maximum value only applies to sliders that are in two- or three-value mode. + jassert (style == TwoValueHorizontal || style == TwoValueVertical + || style == ThreeValueHorizontal || style == ThreeValueVertical); + + newValue = constrainedValue (newValue); + + if (style == TwoValueHorizontal || style == TwoValueVertical) + { + if (allowNudgingOfOtherValues && newValue < valueMin) + setMinValue (newValue, sendUpdateMessage, sendMessageSynchronously); + + newValue = jmax (valueMin, newValue); + } + else + { + if (allowNudgingOfOtherValues && newValue < currentValue) + setValue (newValue, sendUpdateMessage, sendMessageSynchronously); + + newValue = jmax (currentValue, newValue); + } + + if (valueMax != newValue) + { + valueMax = newValue; + repaint(); + + if (popupDisplay != 0) + { + ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition (getTextFromValue (valueMax)); + popupDisplay->repaint(); + } + + if (sendUpdateMessage) + triggerChangeMessage (sendMessageSynchronously); + } +} + +void Slider::setDoubleClickReturnValue (const bool isDoubleClickEnabled, + const double valueToSetOnDoubleClick) throw() +{ + doubleClickToValue = isDoubleClickEnabled; + doubleClickReturnValue = valueToSetOnDoubleClick; +} + +double Slider::getDoubleClickReturnValue (bool& isEnabled_) const throw() +{ + isEnabled_ = doubleClickToValue; + return doubleClickReturnValue; +} + +void Slider::updateText() +{ + if (valueBox != 0) + valueBox->setText (getTextFromValue (currentValue), false); +} + +void Slider::setTextValueSuffix (const String& suffix) +{ + if (textSuffix != suffix) + { + textSuffix = suffix; + updateText(); + } +} + +const String Slider::getTextFromValue (double v) +{ + if (numDecimalPlaces > 0) + return String (v, numDecimalPlaces) + textSuffix; + else + return String (roundDoubleToInt (v)) + textSuffix; +} + +double Slider::getValueFromText (const String& text) +{ + String t (text.trimStart()); + + if (t.endsWith (textSuffix)) + t = t.substring (0, t.length() - textSuffix.length()); + + while (t.startsWithChar (T('+'))) + t = t.substring (1).trimStart(); + + return t.initialSectionContainingOnly (T("0123456789.,-")) + .getDoubleValue(); +} + +double Slider::proportionOfLengthToValue (double proportion) +{ + if (skewFactor != 1.0 && proportion > 0.0) + proportion = exp (log (proportion) / skewFactor); + + return minimum + (maximum - minimum) * proportion; +} + +double Slider::valueToProportionOfLength (double value) +{ + const double n = (value - minimum) / (maximum - minimum); + + return skewFactor == 1.0 ? n : pow (n, skewFactor); +} + +double Slider::snapValue (double attemptedValue, const bool) +{ + return attemptedValue; +} + +//============================================================================== +void Slider::startedDragging() +{ +} + +void Slider::stoppedDragging() +{ +} + +void Slider::valueChanged() +{ +} + +//============================================================================== +void Slider::enablementChanged() +{ + repaint(); +} + +void Slider::setPopupMenuEnabled (const bool menuEnabled_) throw() +{ + menuEnabled = menuEnabled_; +} + +void Slider::setScrollWheelEnabled (const bool enabled) throw() +{ + scrollWheelEnabled = enabled; +} + +//============================================================================== +void Slider::labelTextChanged (Label* label) +{ + const double newValue = snapValue (getValueFromText (label->getText()), false); + + if (getValue() != newValue) + { + sendDragStart(); + setValue (newValue, true, true); + sendDragEnd(); + } + + updateText(); // force a clean-up of the text, needed in case setValue() hasn't done this. +} + +void Slider::buttonClicked (Button* button) +{ + if (style == IncDecButtons) + { + sendDragStart(); + + if (button == incButton) + setValue (snapValue (getValue() + interval, false), true, true); + else if (button == decButton) + setValue (snapValue (getValue() - interval, false), true, true); + + sendDragEnd(); + } +} + +//============================================================================== +double Slider::constrainedValue (double value) const throw() +{ + if (interval > 0) + value = minimum + interval * floor ((value - minimum) / interval + 0.5); + + if (value <= minimum || maximum <= minimum) + value = minimum; + else if (value >= maximum) + value = maximum; + + return value; +} + +float Slider::getLinearSliderPos (const double value) +{ + double sliderPosProportional; + + if (maximum > minimum) + { + if (value < minimum) + { + sliderPosProportional = 0.0; + } + else if (value > maximum) + { + sliderPosProportional = 1.0; + } + else + { + sliderPosProportional = valueToProportionOfLength (value); + jassert (sliderPosProportional >= 0 && sliderPosProportional <= 1.0); + } + } + else + { + sliderPosProportional = 0.5; + } + + if (isVertical() || style == IncDecButtons) + sliderPosProportional = 1.0 - sliderPosProportional; + + return (float) (sliderRegionStart + sliderPosProportional * sliderRegionSize); +} + +bool Slider::isHorizontal() const throw() +{ + return style == LinearHorizontal + || style == LinearBar + || style == TwoValueHorizontal + || style == ThreeValueHorizontal; +} + +bool Slider::isVertical() const throw() +{ + return style == LinearVertical + || style == TwoValueVertical + || style == ThreeValueVertical; +} + +bool Slider::incDecDragDirectionIsHorizontal() const throw() +{ + return incDecButtonMode == incDecButtonsDraggable_Horizontal + || (incDecButtonMode == incDecButtonsDraggable_AutoDirection && incDecButtonsSideBySide); +} + +float Slider::getPositionOfValue (const double value) +{ + if (isHorizontal() || isVertical()) + { + return getLinearSliderPos (value); + } + else + { + jassertfalse // not a valid call on a slider that doesn't work linearly! + return 0.0f; + } +} + +//============================================================================== +void Slider::paint (Graphics& g) +{ + if (style != IncDecButtons) + { + if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag) + { + const float sliderPos = (float) valueToProportionOfLength (currentValue); + jassert (sliderPos >= 0 && sliderPos <= 1.0f); + + getLookAndFeel().drawRotarySlider (g, + sliderRect.getX(), + sliderRect.getY(), + sliderRect.getWidth(), + sliderRect.getHeight(), + sliderPos, + rotaryStart, rotaryEnd, + *this); + } + else + { + getLookAndFeel().drawLinearSlider (g, + sliderRect.getX(), + sliderRect.getY(), + sliderRect.getWidth(), + sliderRect.getHeight(), + getLinearSliderPos (currentValue), + getLinearSliderPos (valueMin), + getLinearSliderPos (valueMax), + style, + *this); + } + + if (style == LinearBar && valueBox == 0) + { + g.setColour (findColour (Slider::textBoxOutlineColourId)); + g.drawRect (0, 0, getWidth(), getHeight(), 1); + } + } +} + +void Slider::resized() +{ + int minXSpace = 0; + int minYSpace = 0; + + if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight) + minXSpace = 30; + else + minYSpace = 15; + + const int tbw = jmax (0, jmin (textBoxWidth, getWidth() - minXSpace)); + const int tbh = jmax (0, jmin (textBoxHeight, getHeight() - minYSpace)); + + if (style == LinearBar) + { + if (valueBox != 0) + valueBox->setBounds (0, 0, getWidth(), getHeight()); + } + else + { + if (textBoxPos == NoTextBox) + { + sliderRect.setBounds (0, 0, getWidth(), getHeight()); + } + else if (textBoxPos == TextBoxLeft) + { + valueBox->setBounds (0, (getHeight() - tbh) / 2, tbw, tbh); + sliderRect.setBounds (tbw, 0, getWidth() - tbw, getHeight()); + } + else if (textBoxPos == TextBoxRight) + { + valueBox->setBounds (getWidth() - tbw, (getHeight() - tbh) / 2, tbw, tbh); + sliderRect.setBounds (0, 0, getWidth() - tbw, getHeight()); + } + else if (textBoxPos == TextBoxAbove) + { + valueBox->setBounds ((getWidth() - tbw) / 2, 0, tbw, tbh); + sliderRect.setBounds (0, tbh, getWidth(), getHeight() - tbh); + } + else if (textBoxPos == TextBoxBelow) + { + valueBox->setBounds ((getWidth() - tbw) / 2, getHeight() - tbh, tbw, tbh); + sliderRect.setBounds (0, 0, getWidth(), getHeight() - tbh); + } + } + + const int indent = getLookAndFeel().getSliderThumbRadius (*this); + + if (style == LinearBar) + { + const int barIndent = 1; + sliderRegionStart = barIndent; + sliderRegionSize = getWidth() - barIndent * 2; + + sliderRect.setBounds (sliderRegionStart, barIndent, + sliderRegionSize, getHeight() - barIndent * 2); + } + else if (isHorizontal()) + { + sliderRegionStart = sliderRect.getX() + indent; + sliderRegionSize = jmax (1, sliderRect.getWidth() - indent * 2); + + sliderRect.setBounds (sliderRegionStart, sliderRect.getY(), + sliderRegionSize, sliderRect.getHeight()); + } + else if (isVertical()) + { + sliderRegionStart = sliderRect.getY() + indent; + sliderRegionSize = jmax (1, sliderRect.getHeight() - indent * 2); + + sliderRect.setBounds (sliderRect.getX(), sliderRegionStart, + sliderRect.getWidth(), sliderRegionSize); + } + else + { + sliderRegionStart = 0; + sliderRegionSize = 100; + } + + if (style == IncDecButtons) + { + Rectangle buttonRect (sliderRect); + + if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight) + buttonRect.expand (-2, 0); + else + buttonRect.expand (0, -2); + + incDecButtonsSideBySide = buttonRect.getWidth() > buttonRect.getHeight(); + + if (incDecButtonsSideBySide) + { + decButton->setBounds (buttonRect.getX(), + buttonRect.getY(), + buttonRect.getWidth() / 2, + buttonRect.getHeight()); + + decButton->setConnectedEdges (Button::ConnectedOnRight); + + incButton->setBounds (buttonRect.getCentreX(), + buttonRect.getY(), + buttonRect.getWidth() / 2, + buttonRect.getHeight()); + + incButton->setConnectedEdges (Button::ConnectedOnLeft); + } + else + { + incButton->setBounds (buttonRect.getX(), + buttonRect.getY(), + buttonRect.getWidth(), + buttonRect.getHeight() / 2); + + incButton->setConnectedEdges (Button::ConnectedOnBottom); + + decButton->setBounds (buttonRect.getX(), + buttonRect.getCentreY(), + buttonRect.getWidth(), + buttonRect.getHeight() / 2); + + decButton->setConnectedEdges (Button::ConnectedOnTop); + } + } +} + +void Slider::focusOfChildComponentChanged (FocusChangeType) +{ + repaint(); +} + +void Slider::mouseDown (const MouseEvent& e) +{ + mouseWasHidden = false; + incDecDragged = false; + mouseXWhenLastDragged = e.x; + mouseYWhenLastDragged = e.y; + mouseDragStartX = e.getMouseDownX(); + mouseDragStartY = e.getMouseDownY(); + + if (isEnabled()) + { + if (e.mods.isPopupMenu() && menuEnabled) + { + menuShown = true; + + PopupMenu m; + m.addItem (1, TRANS ("velocity-sensitive mode"), true, isVelocityBased); + m.addSeparator(); + + if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag) + { + PopupMenu rotaryMenu; + rotaryMenu.addItem (2, TRANS ("use circular dragging"), true, style == Rotary); + rotaryMenu.addItem (3, TRANS ("use left-right dragging"), true, style == RotaryHorizontalDrag); + rotaryMenu.addItem (4, TRANS ("use up-down dragging"), true, style == RotaryVerticalDrag); + + m.addSubMenu (TRANS ("rotary mode"), rotaryMenu); + } + + const int r = m.show(); + + if (r == 1) + { + setVelocityBasedMode (! isVelocityBased); + } + else if (r == 2) + { + setSliderStyle (Rotary); + } + else if (r == 3) + { + setSliderStyle (RotaryHorizontalDrag); + } + else if (r == 4) + { + setSliderStyle (RotaryVerticalDrag); + } + } + else if (maximum > minimum) + { + menuShown = false; + + if (valueBox != 0) + valueBox->hideEditor (true); + + sliderBeingDragged = 0; + + if (style == TwoValueHorizontal + || style == TwoValueVertical + || style == ThreeValueHorizontal + || style == ThreeValueVertical) + { + const float mousePos = (float) (isVertical() ? e.y : e.x); + + const float normalPosDistance = fabsf (getLinearSliderPos (currentValue) - mousePos); + const float minPosDistance = fabsf (getLinearSliderPos (valueMin) - 0.1f - mousePos); + const float maxPosDistance = fabsf (getLinearSliderPos (valueMax) + 0.1f - mousePos); + + if (style == TwoValueHorizontal || style == TwoValueVertical) + { + if (maxPosDistance <= minPosDistance) + sliderBeingDragged = 2; + else + sliderBeingDragged = 1; + } + else if (style == ThreeValueHorizontal || style == ThreeValueVertical) + { + if (normalPosDistance >= minPosDistance && maxPosDistance >= minPosDistance) + sliderBeingDragged = 1; + else if (normalPosDistance >= maxPosDistance) + sliderBeingDragged = 2; + } + } + + minMaxDiff = valueMax - valueMin; + + lastAngle = rotaryStart + (rotaryEnd - rotaryStart) + * valueToProportionOfLength (currentValue); + + if (sliderBeingDragged == 2) + valueWhenLastDragged = valueMax; + else if (sliderBeingDragged == 1) + valueWhenLastDragged = valueMin; + else + valueWhenLastDragged = currentValue; + + valueOnMouseDown = valueWhenLastDragged; + + if (popupDisplayEnabled) + { + SliderPopupDisplayComponent* const popup = new SliderPopupDisplayComponent (this); + popupDisplay = popup; + + if (parentForPopupDisplay != 0) + { + parentForPopupDisplay->addChildComponent (popup); + } + else + { + popup->addToDesktop (0); + } + + popup->setVisible (true); + } + + sendDragStart(); + + mouseDrag (e); + } + } +} + +void Slider::mouseUp (const MouseEvent&) +{ + if (isEnabled() + && (! menuShown) + && (maximum > minimum) + && (style != IncDecButtons || incDecDragged)) + { + restoreMouseIfHidden(); + + if (sendChangeOnlyOnRelease && valueOnMouseDown != currentValue) + triggerChangeMessage (false); + + sendDragEnd(); + + deleteAndZero (popupDisplay); + + if (style == IncDecButtons) + { + incButton->setState (Button::buttonNormal); + decButton->setState (Button::buttonNormal); + } + } +} + +void Slider::restoreMouseIfHidden() +{ + if (mouseWasHidden) + { + mouseWasHidden = false; + + Component* c = Component::getComponentUnderMouse(); + + if (c == 0) + c = this; + + c->enableUnboundedMouseMovement (false); + + const double pos = (sliderBeingDragged == 2) ? getMaxValue() + : ((sliderBeingDragged == 1) ? getMinValue() + : currentValue); + + if (style == RotaryHorizontalDrag || style == RotaryVerticalDrag) + { + int x, y, downX, downY; + Desktop::getMousePosition (x, y); + Desktop::getLastMouseDownPosition (downX, downY); + + if (style == RotaryHorizontalDrag) + { + const double posDiff = valueToProportionOfLength (pos) - valueToProportionOfLength (valueOnMouseDown); + x = roundDoubleToInt (pixelsForFullDragExtent * posDiff + downX); + y = downY; + } + else + { + const double posDiff = valueToProportionOfLength (valueOnMouseDown) - valueToProportionOfLength (pos); + x = downX; + y = roundDoubleToInt (pixelsForFullDragExtent * posDiff + downY); + } + + Desktop::setMousePosition (x, y); + } + else + { + const int pixelPos = (int) getLinearSliderPos (pos); + + int x = isHorizontal() ? pixelPos : (getWidth() / 2); + int y = isVertical() ? pixelPos : (getHeight() / 2); + + relativePositionToGlobal (x, y); + Desktop::setMousePosition (x, y); + } + } +} + +void Slider::modifierKeysChanged (const ModifierKeys& modifiers) +{ + if (isEnabled() + && style != IncDecButtons + && style != Rotary + && isVelocityBased == modifiers.isAnyModifierKeyDown()) + { + restoreMouseIfHidden(); + } +} + +static double smallestAngleBetween (double a1, double a2) +{ + return jmin (fabs (a1 - a2), + fabs (a1 + double_Pi * 2.0 - a2), + fabs (a2 + double_Pi * 2.0 - a1)); +} + +void Slider::mouseDrag (const MouseEvent& e) +{ + if (isEnabled() + && (! menuShown) + && (maximum > minimum)) + { + if (style == Rotary) + { + int dx = e.x - sliderRect.getCentreX(); + int dy = e.y - sliderRect.getCentreY(); + + if (dx * dx + dy * dy > 25) + { + double angle = atan2 ((double) dx, (double) -dy); + while (angle < 0.0) + angle += double_Pi * 2.0; + + if (rotaryStop && ! e.mouseWasClicked()) + { + if (fabs (angle - lastAngle) > double_Pi) + { + if (angle >= lastAngle) + angle -= double_Pi * 2.0; + else + angle += double_Pi * 2.0; + } + + if (angle >= lastAngle) + angle = jmin (angle, (double) jmax (rotaryStart, rotaryEnd)); + else + angle = jmax (angle, (double) jmin (rotaryStart, rotaryEnd)); + } + else + { + while (angle < rotaryStart) + angle += double_Pi * 2.0; + + if (angle > rotaryEnd) + { + if (smallestAngleBetween (angle, rotaryStart) <= smallestAngleBetween (angle, rotaryEnd)) + angle = rotaryStart; + else + angle = rotaryEnd; + } + } + + const double proportion = (angle - rotaryStart) / (rotaryEnd - rotaryStart); + + valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, proportion)); + + lastAngle = angle; + } + } + else + { + if (style == LinearBar && e.mouseWasClicked() + && valueBox != 0 && valueBox->isEditable()) + return; + + if (style == IncDecButtons && ! incDecDragged) + { + if (e.getDistanceFromDragStart() < 10 || e.mouseWasClicked()) + return; + + incDecDragged = true; + mouseDragStartX = e.x; + mouseDragStartY = e.y; + } + + if ((isVelocityBased == (userKeyOverridesVelocity ? e.mods.testFlags (ModifierKeys::ctrlModifier | ModifierKeys::commandModifier | ModifierKeys::altModifier) + : false)) + || ((maximum - minimum) / sliderRegionSize < interval)) + { + const int mousePos = (isHorizontal() || style == RotaryHorizontalDrag) ? e.x : e.y; + + double scaledMousePos = (mousePos - sliderRegionStart) / (double) sliderRegionSize; + + if (style == RotaryHorizontalDrag + || style == RotaryVerticalDrag + || style == IncDecButtons + || ((style == LinearHorizontal || style == LinearVertical || style == LinearBar) + && ! snapsToMousePos)) + { + const int mouseDiff = (style == RotaryHorizontalDrag + || style == LinearHorizontal + || style == LinearBar + || (style == IncDecButtons && incDecDragDirectionIsHorizontal())) + ? e.x - mouseDragStartX + : mouseDragStartY - e.y; + + double newPos = valueToProportionOfLength (valueOnMouseDown) + + mouseDiff * (1.0 / pixelsForFullDragExtent); + + valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, newPos)); + + if (style == IncDecButtons) + { + incButton->setState (mouseDiff < 0 ? Button::buttonNormal : Button::buttonDown); + decButton->setState (mouseDiff > 0 ? Button::buttonNormal : Button::buttonDown); + } + } + else + { + if (isVertical()) + scaledMousePos = 1.0 - scaledMousePos; + + valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, scaledMousePos)); + } + } + else + { + const int mouseDiff = (isHorizontal() || style == RotaryHorizontalDrag + || (style == IncDecButtons && incDecDragDirectionIsHorizontal())) + ? e.x - mouseXWhenLastDragged + : e.y - mouseYWhenLastDragged; + + const double maxSpeed = jmax (200, sliderRegionSize); + double speed = jlimit (0.0, maxSpeed, (double) abs (mouseDiff)); + + if (speed != 0) + { + speed = 0.2 * velocityModeSensitivity + * (1.0 + sin (double_Pi * (1.5 + jmin (0.5, velocityModeOffset + + jmax (0.0, (double) (speed - velocityModeThreshold)) + / maxSpeed)))); + + if (mouseDiff < 0) + speed = -speed; + + if (isVertical() || style == RotaryVerticalDrag + || (style == IncDecButtons && ! incDecDragDirectionIsHorizontal())) + speed = -speed; + + const double currentPos = valueToProportionOfLength (valueWhenLastDragged); + + valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + speed)); + + e.originalComponent->enableUnboundedMouseMovement (true, false); + mouseWasHidden = true; + } + } + } + + valueWhenLastDragged = jlimit (minimum, maximum, valueWhenLastDragged); + + if (sliderBeingDragged == 0) + { + setValue (snapValue (valueWhenLastDragged, true), + ! sendChangeOnlyOnRelease, true); + } + else if (sliderBeingDragged == 1) + { + setMinValue (snapValue (valueWhenLastDragged, true), + ! sendChangeOnlyOnRelease, false, true); + + if (e.mods.isShiftDown()) + setMaxValue (getMinValue() + minMaxDiff, false, false, true); + else + minMaxDiff = valueMax - valueMin; + } + else + { + jassert (sliderBeingDragged == 2); + + setMaxValue (snapValue (valueWhenLastDragged, true), + ! sendChangeOnlyOnRelease, false, true); + + if (e.mods.isShiftDown()) + setMinValue (getMaxValue() - minMaxDiff, false, false, true); + else + minMaxDiff = valueMax - valueMin; + } + + mouseXWhenLastDragged = e.x; + mouseYWhenLastDragged = e.y; + } +} + +void Slider::mouseDoubleClick (const MouseEvent&) +{ + if (doubleClickToValue + && isEnabled() + && style != IncDecButtons + && minimum <= doubleClickReturnValue + && maximum >= doubleClickReturnValue) + { + sendDragStart(); + setValue (doubleClickReturnValue, true, true); + sendDragEnd(); + } +} + +void Slider::mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY) +{ + if (scrollWheelEnabled && isEnabled() + && style != TwoValueHorizontal + && style != TwoValueVertical) + { + if (maximum > minimum && ! isMouseButtonDownAnywhere()) + { + if (valueBox != 0) + valueBox->hideEditor (false); + + const double proportionDelta = (wheelIncrementX != 0 ? -wheelIncrementX : wheelIncrementY) * 0.15f; + const double currentPos = valueToProportionOfLength (currentValue); + const double newValue = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + proportionDelta)); + + double delta = (newValue != currentValue) + ? jmax (fabs (newValue - currentValue), interval) : 0; + + if (currentValue > newValue) + delta = -delta; + + sendDragStart(); + setValue (snapValue (currentValue + delta, false), true, true); + sendDragEnd(); + } + } + else + { + Component::mouseWheelMove (e, wheelIncrementX, wheelIncrementY); + } +} + +void SliderListener::sliderDragStarted (Slider*) +{ +} + +void SliderListener::sliderDragEnded (Slider*) +{ +} + + +END_JUCE_NAMESPACE diff --git a/src/juce_appframework/gui/components/controls/juce_Slider.h b/src/juce_appframework/gui/components/controls/juce_Slider.h index ccba2120e9..6d0ad49881 100644 --- a/src/juce_appframework/gui/components/controls/juce_Slider.h +++ b/src/juce_appframework/gui/components/controls/juce_Slider.h @@ -1,751 +1,751 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - JUCE is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#ifndef __JUCE_SLIDER_JUCEHEADER__ -#define __JUCE_SLIDER_JUCEHEADER__ - -#include "juce_SliderListener.h" -#include "juce_Label.h" -#include "../buttons/juce_Button.h" -#include "../../../events/juce_AsyncUpdater.h" -#include "../../../../juce_core/containers/juce_SortedSet.h" - - -//============================================================================== -/** - A slider control for changing a value. - - The slider can be horizontal, vertical, or rotary, and can optionally have - a text-box inside it to show an editable display of the current value. - - To use it, create a Slider object and use the setSliderStyle() method - to set up the type you want. To set up the text-entry box, use setTextBoxStyle(). - - To define the values that it can be set to, see the setRange() and setValue() methods. - - There are also lots of custom tweaks you can do by subclassing and overriding - some of the virtual methods, such as changing the scaling, changing the format of - the text display, custom ways of limiting the values, etc. - - You can register SliderListeners with a slider, which will be informed when the value - changes, or a subclass can override valueChanged() to be informed synchronously. - - @see SliderListener -*/ -class JUCE_API Slider : public Component, - public SettableTooltipClient, - private AsyncUpdater, - private ButtonListener, - private LabelListener -{ -public: - //============================================================================== - /** Creates a slider. - - When created, you'll need to set up the slider's style and range with setSliderStyle(), - setRange(), etc. - */ - Slider (const String& componentName); - - /** Destructor. */ - ~Slider(); - - //============================================================================== - /** The types of slider available. - - @see setSliderStyle, setRotaryParameters - */ - enum SliderStyle - { - LinearHorizontal, /**< A traditional horizontal slider. */ - LinearVertical, /**< A traditional vertical slider. */ - LinearBar, /**< A horizontal bar slider with the text label drawn on top of it. */ - Rotary, /**< A rotary control that you move by dragging the mouse in a circular motion, like a knob. - @see setRotaryParameters */ - RotaryHorizontalDrag, /**< A rotary control that you move by dragging the mouse left-to-right. - @see setRotaryParameters */ - RotaryVerticalDrag, /**< A rotary control that you move by dragging the mouse up-and-down. - @see setRotaryParameters */ - IncDecButtons, /**< A pair of buttons that increment or decrement the slider's value by the increment set in setRange(). */ - - TwoValueHorizontal, /**< A horizontal slider that has two thumbs instead of one, so it can show a minimum and maximum value. - @see setMinValue, setMaxValue */ - TwoValueVertical, /**< A vertical slider that has two thumbs instead of one, so it can show a minimum and maximum value. - @see setMinValue, setMaxValue */ - - ThreeValueHorizontal, /**< A horizontal slider that has three thumbs instead of one, so it can show a minimum and maximum - value, with the current value being somewhere between them. - @see setMinValue, setMaxValue */ - ThreeValueVertical, /**< A vertical slider that has three thumbs instead of one, so it can show a minimum and maximum - value, with the current value being somewhere between them. - @see setMinValue, setMaxValue */ - }; - - /** Changes the type of slider interface being used. - - @param newStyle the type of interface - @see setRotaryParameters, setVelocityBasedMode, - */ - void setSliderStyle (const SliderStyle newStyle); - - /** Returns the slider's current style. - - @see setSliderStyle - */ - SliderStyle getSliderStyle() const throw() { return style; } - - //============================================================================== - /** Changes the properties of a rotary slider. - - @param startAngleRadians the angle (in radians, clockwise from the top) at which - the slider's minimum value is represented - @param endAngleRadians the angle (in radians, clockwise from the top) at which - the slider's maximum value is represented. This must be - greater than startAngleRadians - @param stopAtEnd if true, then when the slider is dragged around past the - minimum or maximum, it'll stop there; if false, it'll wrap - back to the opposite value - */ - void setRotaryParameters (const float startAngleRadians, - const float endAngleRadians, - const bool stopAtEnd); - - /** Sets the distance the mouse has to move to drag the slider across - the full extent of its range. - - This only applies when in modes like RotaryHorizontalDrag, where it's using - relative mouse movements to adjust the slider. - */ - void setMouseDragSensitivity (const int distanceForFullScaleDrag); - - //============================================================================== - /** Changes the way the the mouse is used when dragging the slider. - - If true, this will turn on velocity-sensitive dragging, so that - the faster the mouse moves, the bigger the movement to the slider. This - helps when making accurate adjustments if the slider's range is quite large. - - If false, the slider will just try to snap to wherever the mouse is. - */ - void setVelocityBasedMode (const bool isVelocityBased) throw(); - - /** Changes aspects of the scaling used when in velocity-sensitive mode. - - These apply when you've used setVelocityBasedMode() to turn on velocity mode, - or if you're holding down ctrl. - - @param sensitivity higher values than 1.0 increase the range of acceleration used - @param threshold the minimum number of pixels that the mouse needs to move for it - to be treated as a movement - @param offset values greater than 0.0 increase the minimum speed that will be used when - the threshold is reached - @param userCanPressKeyToSwapMode if true, then the user can hold down the ctrl or command - key to toggle velocity-sensitive mode - */ - void setVelocityModeParameters (const double sensitivity = 1.0, - const int threshold = 1, - const double offset = 0.0, - const bool userCanPressKeyToSwapMode = true) throw(); - - //============================================================================== - /** Sets up a skew factor to alter the way values are distributed. - - You may want to use a range of values on the slider where more accuracy - is required towards one end of the range, so this will logarithmically - spread the values across the length of the slider. - - If the factor is < 1.0, the lower end of the range will fill more of the - slider's length; if the factor is > 1.0, the upper end of the range - will be expanded instead. A factor of 1.0 doesn't skew it at all. - - To set the skew position by using a mid-point, use the setSkewFactorFromMidPoint() - method instead. - - @see getSkewFactor, setSkewFactorFromMidPoint - */ - void setSkewFactor (const double factor) throw(); - - /** Sets up a skew factor to alter the way values are distributed. - - This allows you to specify the slider value that should appear in the - centre of the slider's visible range. - - @see setSkewFactor, getSkewFactor - */ - void setSkewFactorFromMidPoint (const double sliderValueToShowAtMidPoint) throw(); - - /** Returns the current skew factor. - - See setSkewFactor for more info. - - @see setSkewFactor, setSkewFactorFromMidPoint - */ - double getSkewFactor() const throw() { return skewFactor; } - - //============================================================================== - /** Used by setIncDecButtonsMode(). - */ - enum IncDecButtonMode - { - incDecButtonsNotDraggable, - incDecButtonsDraggable_AutoDirection, - incDecButtonsDraggable_Horizontal, - incDecButtonsDraggable_Vertical - }; - - /** When the style is IncDecButtons, this lets you turn on a mode where the mouse - can be dragged on the buttons to drag the values. - - By default this is turned off. When enabled, clicking on the buttons still works - them as normal, but by holding down the mouse on a button and dragging it a little - distance, it flips into a mode where the value can be dragged. The drag direction can - either be set explicitly to be vertical or horizontal, or can be set to - incDecButtonsDraggable_AutoDirection so that it depends on whether the buttons - are side-by-side or above each other. - */ - void setIncDecButtonsMode (const IncDecButtonMode mode); - - //============================================================================== - /** The position of the slider's text-entry box. - - @see setTextBoxStyle - */ - enum TextEntryBoxPosition - { - NoTextBox, /**< Doesn't display a text box. */ - TextBoxLeft, /**< Puts the text box to the left of the slider, vertically centred. */ - TextBoxRight, /**< Puts the text box to the right of the slider, vertically centred. */ - TextBoxAbove, /**< Puts the text box above the slider, horizontally centred. */ - TextBoxBelow /**< Puts the text box below the slider, horizontally centred. */ - }; - - /** Changes the location and properties of the text-entry box. - - @param newPosition where it should go (or NoTextBox to not have one at all) - @param isReadOnly if true, it's a read-only display - @param textEntryBoxWidth the width of the text-box in pixels. Make sure this leaves enough - room for the slider as well! - @param textEntryBoxHeight the height of the text-box in pixels. Make sure this leaves enough - room for the slider as well! - - @see setTextBoxIsEditable, getValueFromText, getTextFromValue - */ - void setTextBoxStyle (const TextEntryBoxPosition newPosition, - const bool isReadOnly, - const int textEntryBoxWidth, - const int textEntryBoxHeight); - - /** Returns the status of the text-box. - @see setTextBoxStyle - */ - const TextEntryBoxPosition getTextBoxPosition() const throw() { return textBoxPos; } - - /** Returns the width used for the text-box. - @see setTextBoxStyle - */ - int getTextBoxWidth() const throw() { return textBoxWidth; } - - /** Returns the height used for the text-box. - @see setTextBoxStyle - */ - int getTextBoxHeight() const throw() { return textBoxHeight; } - - /** Makes the text-box editable. - - By default this is true, and the user can enter values into the textbox, - but it can be turned off if that's not suitable. - - @see setTextBoxStyle, getValueFromText, getTextFromValue - */ - void setTextBoxIsEditable (const bool shouldBeEditable) throw(); - - /** Returns true if the text-box is read-only. - @see setTextBoxStyle - */ - bool isTextBoxEditable() const throw() { return editableText; } - - /** If the text-box is editable, this will give it the focus so that the user can - type directly into it. - - This is basically the effect as the user clicking on it. - */ - void showTextBox(); - - /** If the text-box currently has focus and is being edited, this resets it and takes keyboard - focus away from it. - - @param discardCurrentEditorContents if true, the slider's value will be left - unchanged; if false, the current contents of the - text editor will be used to set the slider position - before it is hidden. - */ - void hideTextBox (const bool discardCurrentEditorContents); - - - //============================================================================== - /** Changes the slider's current value. - - This will trigger a callback to SliderListener::sliderValueChanged() for any listeners - that are registered, and will synchronously call the valueChanged() method in case subclasses - want to handle it. - - @param newValue the new value to set - this will be restricted by the - minimum and maximum range, and will be snapped to the - nearest interval if one has been set - @param sendUpdateMessage if false, a change to the value will not trigger a call to - any SliderListeners or the valueChanged() method - @param sendMessageSynchronously if true, then a call to the SliderListeners will be made - synchronously; if false, it will be asynchronous - */ - void setValue (double newValue, - const bool sendUpdateMessage = true, - const bool sendMessageSynchronously = false); - - /** Returns the slider's current value. */ - double getValue() const throw(); - - //============================================================================== - /** Sets the limits that the slider's value can take. - - @param newMinimum the lowest value allowed - @param newMaximum the highest value allowed - @param newInterval the steps in which the value is allowed to increase - if this - is not zero, the value will always be (newMinimum + (newInterval * an integer)). - */ - void setRange (const double newMinimum, - const double newMaximum, - const double newInterval = 0); - - /** Returns the current maximum value. - @see setRange - */ - double getMaximum() const throw() { return maximum; } - - /** Returns the current minimum value. - @see setRange - */ - double getMinimum() const throw() { return minimum; } - - /** Returns the current step-size for values. - @see setRange - */ - double getInterval() const throw() { return interval; } - - //============================================================================== - /** For a slider with two or three thumbs, this returns the lower of its values. - - For a two-value slider, the values are controlled with getMinValue() and getMaxValue(). - A slider with three values also uses the normal getValue() and setValue() methods to - control the middle value. - - @see setMinValue, getMaxValue, TwoValueHorizontal, TwoValueVertical, ThreeValueHorizontal, ThreeValueVertical - */ - double getMinValue() const throw(); - - /** For a slider with two or three thumbs, this sets the lower of its values. - - This will trigger a callback to SliderListener::sliderValueChanged() for any listeners - that are registered, and will synchronously call the valueChanged() method in case subclasses - want to handle it. - - @param newValue the new value to set - this will be restricted by the - minimum and maximum range, and will be snapped to the nearest - interval if one has been set. - @param sendUpdateMessage if false, a change to the value will not trigger a call to - any SliderListeners or the valueChanged() method - @param sendMessageSynchronously if true, then a call to the SliderListeners will be made - synchronously; if false, it will be asynchronous - @param allowNudgingOfOtherValues if false, this value will be restricted to being below the - max value (in a two-value slider) or the mid value (in a three-value - slider). If false, then if this value goes beyond those values, - it will push them along with it. - @see getMinValue, setMaxValue, setValue - */ - void setMinValue (double newValue, - const bool sendUpdateMessage = true, - const bool sendMessageSynchronously = false, - const bool allowNudgingOfOtherValues = false); - - /** For a slider with two or three thumbs, this returns the higher of its values. - - For a two-value slider, the values are controlled with getMinValue() and getMaxValue(). - A slider with three values also uses the normal getValue() and setValue() methods to - control the middle value. - - @see getMinValue, TwoValueHorizontal, TwoValueVertical, ThreeValueHorizontal, ThreeValueVertical - */ - double getMaxValue() const throw(); - - /** For a slider with two or three thumbs, this sets the lower of its values. - - This will trigger a callback to SliderListener::sliderValueChanged() for any listeners - that are registered, and will synchronously call the valueChanged() method in case subclasses - want to handle it. - - @param newValue the new value to set - this will be restricted by the - minimum and maximum range, and will be snapped to the nearest - interval if one has been set. - @param sendUpdateMessage if false, a change to the value will not trigger a call to - any SliderListeners or the valueChanged() method - @param sendMessageSynchronously if true, then a call to the SliderListeners will be made - synchronously; if false, it will be asynchronous - @param allowNudgingOfOtherValues if false, this value will be restricted to being above the - min value (in a two-value slider) or the mid value (in a three-value - slider). If false, then if this value goes beyond those values, - it will push them along with it. - @see getMaxValue, setMinValue, setValue - */ - void setMaxValue (double newValue, - const bool sendUpdateMessage = true, - const bool sendMessageSynchronously = false, - const bool allowNudgingOfOtherValues = false); - - //============================================================================== - /** Adds a listener to be called when this slider's value changes. */ - void addListener (SliderListener* const listener) throw(); - - /** Removes a previously-registered listener. */ - void removeListener (SliderListener* const listener) throw(); - - //============================================================================== - /** This lets you choose whether double-clicking moves the slider to a given position. - - By default this is turned off, but it's handy if you want a double-click to act - as a quick way of resetting a slider. Just pass in the value you want it to - go to when double-clicked. - - @see getDoubleClickReturnValue - */ - void setDoubleClickReturnValue (const bool isDoubleClickEnabled, - const double valueToSetOnDoubleClick) throw(); - - /** Returns the values last set by setDoubleClickReturnValue() method. - - Sets isEnabled to true if double-click is enabled, and returns the value - that was set. - - @see setDoubleClickReturnValue - */ - double getDoubleClickReturnValue (bool& isEnabled) const throw(); - - //============================================================================== - /** Tells the slider whether to keep sending change messages while the user - is dragging the slider. - - If set to true, a change message will only be sent when the user has - dragged the slider and let go. If set to false (the default), then messages - will be continuously sent as they drag it while the mouse button is still - held down. - */ - void setChangeNotificationOnlyOnRelease (const bool onlyNotifyOnRelease) throw(); - - /** This lets you change whether the slider thumb jumps to the mouse position - when you click. - - By default, this is true. If it's false, then the slider moves with relative - motion when you drag it. - - This only applies to linear bars, and won't affect two- or three- value - sliders. - */ - void setSliderSnapsToMousePosition (const bool shouldSnapToMouse) throw(); - - /** If enabled, this gives the slider a pop-up bubble which appears while the - slider is being dragged. - - This can be handy if your slider doesn't have a text-box, so that users can - see the value just when they're changing it. - - If you pass a component as the parentComponentToUse parameter, the pop-up - bubble will be added as a child of that component when it's needed. If you - pass 0, the pop-up will be placed on the desktop instead (note that it's a - transparent window, so if you're using an OS that can't do transparent windows - you'll have to add it to a parent component instead). - */ - void setPopupDisplayEnabled (const bool isEnabled, - Component* const parentComponentToUse) throw(); - - /** If this is set to true, then right-clicking on the slider will pop-up - a menu to let the user change the way it works. - - By default this is turned off, but when turned on, the menu will include - things like velocity sensitivity, and for rotary sliders, whether they - use a linear or rotary mouse-drag to move them. - */ - void setPopupMenuEnabled (const bool menuEnabled) throw(); - - /** This can be used to stop the mouse scroll-wheel from moving the slider. - - By default it's enabled. - */ - void setScrollWheelEnabled (const bool enabled) throw(); - - /** Returns a number to indicate which thumb is currently being dragged by the - mouse. - - This will return 0 for the main thumb, 1 for the minimum-value thumb, 2 for - the maximum-value thumb, or -1 if none is currently down. - */ - int getThumbBeingDragged() const throw() { return sliderBeingDragged; } - - //============================================================================== - /** Callback to indicate that the user is about to start dragging the slider. - - @see SliderListener::sliderDragStarted - */ - virtual void startedDragging(); - - /** Callback to indicate that the user has just stopped dragging the slider. - - @see SliderListener::sliderDragEnded - */ - virtual void stoppedDragging(); - - /** Callback to indicate that the user has just moved the slider. - - @see SliderListener::sliderValueChanged - */ - virtual void valueChanged(); - - /** Callback to indicate that the user has just moved the slider. - Note - the valueChanged() method has changed its format and now no longer has - any parameters. Update your code to use the new version. - This version has been left here with an int as its return value to cause - a syntax error if you've got existing code that uses the old version. - */ - virtual int valueChanged (double) { jassertfalse; return 0; } - - //============================================================================== - /** Subclasses can override this to convert a text string to a value. - - When the user enters something into the text-entry box, this method is - called to convert it to a value. - - The default routine just tries to convert it to a double. - - @see getTextFromValue - */ - virtual double getValueFromText (const String& text); - - /** Turns the slider's current value into a text string. - - Subclasses can override this to customise the formatting of the text-entry box. - - The default implementation just turns the value into a string, using - a number of decimal places based on the range interval. If a suffix string - has been set using setTextValueSuffix(), this will be appended to the text. - - @see getValueFromText - */ - virtual const String getTextFromValue (double value); - - /** Sets a suffix to append to the end of the numeric value when it's displayed as - a string. - - This is used by the default implementation of getTextFromValue(), and is just - appended to the numeric value. For more advanced formatting, you can override - getTextFromValue() and do something else. - */ - void setTextValueSuffix (const String& suffix); - - //============================================================================== - /** Allows a user-defined mapping of distance along the slider to its value. - - The default implementation for this performs the skewing operation that - can be set up in the setSkewFactor() method. Override it if you need - some kind of custom mapping instead, but make sure you also implement the - inverse function in valueToProportionOfLength(). - - @param proportion a value 0 to 1.0, indicating a distance along the slider - @returns the slider value that is represented by this position - @see valueToProportionOfLength - */ - virtual double proportionOfLengthToValue (double proportion); - - /** Allows a user-defined mapping of value to the position of the slider along its length. - - The default implementation for this performs the skewing operation that - can be set up in the setSkewFactor() method. Override it if you need - some kind of custom mapping instead, but make sure you also implement the - inverse function in proportionOfLengthToValue(). - - @param value a valid slider value, between the range of values specified in - setRange() - @returns a value 0 to 1.0 indicating the distance along the slider that - represents this value - @see proportionOfLengthToValue - */ - virtual double valueToProportionOfLength (double value); - - /** Returns the X or Y coordinate of a value along the slider's length. - - If the slider is horizontal, this will be the X coordinate of the given - value, relative to the left of the slider. If it's vertical, then this will - be the Y coordinate, relative to the top of the slider. - - If the slider is rotary, this will throw an assertion and return 0. If the - value is out-of-range, it will be constrained to the length of the slider. - */ - float getPositionOfValue (const double value); - - //============================================================================== - /** This can be overridden to allow the slider to snap to user-definable values. - - If overridden, it will be called when the user tries to move the slider to - a given position, and allows a subclass to sanity-check this value, possibly - returning a different value to use instead. - - @param attemptedValue the value the user is trying to enter - @param userIsDragging true if the user is dragging with the mouse; false if - they are entering the value using the text box - @returns the value to use instead - */ - virtual double snapValue (double attemptedValue, const bool userIsDragging); - - - //============================================================================== - /** This can be called to force the text box to update its contents. - - (Not normally needed, as this is done automatically). - */ - void updateText(); - - - /** True if the slider moves horizontally. */ - bool isHorizontal() const throw(); - /** True if the slider moves vertically. */ - bool isVertical() const throw(); - - //============================================================================== - /** A set of colour IDs to use to change the colour of various aspects of the slider. - - These constants can be used either via the Component::setColour(), or LookAndFeel::setColour() - methods. - - @see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour - */ - enum ColourIds - { - backgroundColourId = 0x1001200, /**< A colour to use to fill the slider's background. */ - thumbColourId = 0x1001300, /**< The colour to draw the thumb with. It's up to the look - and feel class how this is used. */ - trackColourId = 0x1001310, /**< The colour to draw the groove that the thumb moves along. */ - rotarySliderFillColourId = 0x1001311, /**< For rotary sliders, this colour fills the outer curve. */ - rotarySliderOutlineColourId = 0x1001312, /**< For rotary sliders, this colour is used to draw the outer curve's outline. */ - - textBoxTextColourId = 0x1001400, /**< The colour for the text in the text-editor box used for editing the value. */ - textBoxBackgroundColourId = 0x1001500, /**< The background colour for the text-editor box. */ - textBoxHighlightColourId = 0x1001600, /**< The text highlight colour for the text-editor box. */ - textBoxOutlineColourId = 0x1001700 /**< The colour to use for a border around the text-editor box. */ - }; - - //============================================================================== - juce_UseDebuggingNewOperator - -protected: - /** @internal */ - void labelTextChanged (Label*); - /** @internal */ - void paint (Graphics& g); - /** @internal */ - void resized(); - /** @internal */ - void mouseDown (const MouseEvent& e); - /** @internal */ - void mouseUp (const MouseEvent& e); - /** @internal */ - void mouseDrag (const MouseEvent& e); - /** @internal */ - void mouseDoubleClick (const MouseEvent& e); - /** @internal */ - void mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY); - /** @internal */ - void modifierKeysChanged (const ModifierKeys& modifiers); - /** @internal */ - void buttonClicked (Button* button); - /** @internal */ - void lookAndFeelChanged(); - /** @internal */ - void enablementChanged(); - /** @internal */ - void focusOfChildComponentChanged (FocusChangeType cause); - /** @internal */ - void handleAsyncUpdate(); - /** @internal */ - void colourChanged(); - -private: - SortedSet listeners; - double currentValue, valueMin, valueMax; - double minimum, maximum, interval, doubleClickReturnValue; - double valueWhenLastDragged, valueOnMouseDown, skewFactor, lastAngle; - double velocityModeSensitivity, velocityModeOffset, minMaxDiff; - int velocityModeThreshold; - float rotaryStart, rotaryEnd; - int numDecimalPlaces, mouseXWhenLastDragged, mouseYWhenLastDragged; - int mouseDragStartX, mouseDragStartY; - int sliderRegionStart, sliderRegionSize; - int sliderBeingDragged; - int pixelsForFullDragExtent; - Rectangle sliderRect; - String textSuffix; - - SliderStyle style; - TextEntryBoxPosition textBoxPos; - int textBoxWidth, textBoxHeight; - IncDecButtonMode incDecButtonMode; - - bool editableText : 1, doubleClickToValue : 1; - bool isVelocityBased : 1, userKeyOverridesVelocity : 1, rotaryStop : 1; - bool incDecButtonsSideBySide : 1, sendChangeOnlyOnRelease : 1, popupDisplayEnabled : 1; - bool menuEnabled : 1, menuShown : 1, mouseWasHidden : 1, incDecDragged : 1; - bool scrollWheelEnabled : 1, snapsToMousePos : 1; - Font font; - Label* valueBox; - Button* incButton; - Button* decButton; - Component* popupDisplay; - Component* parentForPopupDisplay; - - float getLinearSliderPos (const double value); - void restoreMouseIfHidden(); - void sendDragStart(); - void sendDragEnd(); - double constrainedValue (double value) const throw(); - void triggerChangeMessage (const bool synchronous); - bool incDecDragDirectionIsHorizontal() const throw(); - - Slider (const Slider&); - const Slider& operator= (const Slider&); -}; - - -#endif // __JUCE_SLIDER_JUCEHEADER__ +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + JUCE is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#ifndef __JUCE_SLIDER_JUCEHEADER__ +#define __JUCE_SLIDER_JUCEHEADER__ + +#include "juce_SliderListener.h" +#include "juce_Label.h" +#include "../buttons/juce_Button.h" +#include "../../../events/juce_AsyncUpdater.h" +#include "../../../../juce_core/containers/juce_SortedSet.h" + + +//============================================================================== +/** + A slider control for changing a value. + + The slider can be horizontal, vertical, or rotary, and can optionally have + a text-box inside it to show an editable display of the current value. + + To use it, create a Slider object and use the setSliderStyle() method + to set up the type you want. To set up the text-entry box, use setTextBoxStyle(). + + To define the values that it can be set to, see the setRange() and setValue() methods. + + There are also lots of custom tweaks you can do by subclassing and overriding + some of the virtual methods, such as changing the scaling, changing the format of + the text display, custom ways of limiting the values, etc. + + You can register SliderListeners with a slider, which will be informed when the value + changes, or a subclass can override valueChanged() to be informed synchronously. + + @see SliderListener +*/ +class JUCE_API Slider : public Component, + public SettableTooltipClient, + private AsyncUpdater, + private ButtonListener, + private LabelListener +{ +public: + //============================================================================== + /** Creates a slider. + + When created, you'll need to set up the slider's style and range with setSliderStyle(), + setRange(), etc. + */ + Slider (const String& componentName); + + /** Destructor. */ + ~Slider(); + + //============================================================================== + /** The types of slider available. + + @see setSliderStyle, setRotaryParameters + */ + enum SliderStyle + { + LinearHorizontal, /**< A traditional horizontal slider. */ + LinearVertical, /**< A traditional vertical slider. */ + LinearBar, /**< A horizontal bar slider with the text label drawn on top of it. */ + Rotary, /**< A rotary control that you move by dragging the mouse in a circular motion, like a knob. + @see setRotaryParameters */ + RotaryHorizontalDrag, /**< A rotary control that you move by dragging the mouse left-to-right. + @see setRotaryParameters */ + RotaryVerticalDrag, /**< A rotary control that you move by dragging the mouse up-and-down. + @see setRotaryParameters */ + IncDecButtons, /**< A pair of buttons that increment or decrement the slider's value by the increment set in setRange(). */ + + TwoValueHorizontal, /**< A horizontal slider that has two thumbs instead of one, so it can show a minimum and maximum value. + @see setMinValue, setMaxValue */ + TwoValueVertical, /**< A vertical slider that has two thumbs instead of one, so it can show a minimum and maximum value. + @see setMinValue, setMaxValue */ + + ThreeValueHorizontal, /**< A horizontal slider that has three thumbs instead of one, so it can show a minimum and maximum + value, with the current value being somewhere between them. + @see setMinValue, setMaxValue */ + ThreeValueVertical, /**< A vertical slider that has three thumbs instead of one, so it can show a minimum and maximum + value, with the current value being somewhere between them. + @see setMinValue, setMaxValue */ + }; + + /** Changes the type of slider interface being used. + + @param newStyle the type of interface + @see setRotaryParameters, setVelocityBasedMode, + */ + void setSliderStyle (const SliderStyle newStyle); + + /** Returns the slider's current style. + + @see setSliderStyle + */ + SliderStyle getSliderStyle() const throw() { return style; } + + //============================================================================== + /** Changes the properties of a rotary slider. + + @param startAngleRadians the angle (in radians, clockwise from the top) at which + the slider's minimum value is represented + @param endAngleRadians the angle (in radians, clockwise from the top) at which + the slider's maximum value is represented. This must be + greater than startAngleRadians + @param stopAtEnd if true, then when the slider is dragged around past the + minimum or maximum, it'll stop there; if false, it'll wrap + back to the opposite value + */ + void setRotaryParameters (const float startAngleRadians, + const float endAngleRadians, + const bool stopAtEnd); + + /** Sets the distance the mouse has to move to drag the slider across + the full extent of its range. + + This only applies when in modes like RotaryHorizontalDrag, where it's using + relative mouse movements to adjust the slider. + */ + void setMouseDragSensitivity (const int distanceForFullScaleDrag); + + //============================================================================== + /** Changes the way the the mouse is used when dragging the slider. + + If true, this will turn on velocity-sensitive dragging, so that + the faster the mouse moves, the bigger the movement to the slider. This + helps when making accurate adjustments if the slider's range is quite large. + + If false, the slider will just try to snap to wherever the mouse is. + */ + void setVelocityBasedMode (const bool isVelocityBased) throw(); + + /** Changes aspects of the scaling used when in velocity-sensitive mode. + + These apply when you've used setVelocityBasedMode() to turn on velocity mode, + or if you're holding down ctrl. + + @param sensitivity higher values than 1.0 increase the range of acceleration used + @param threshold the minimum number of pixels that the mouse needs to move for it + to be treated as a movement + @param offset values greater than 0.0 increase the minimum speed that will be used when + the threshold is reached + @param userCanPressKeyToSwapMode if true, then the user can hold down the ctrl or command + key to toggle velocity-sensitive mode + */ + void setVelocityModeParameters (const double sensitivity = 1.0, + const int threshold = 1, + const double offset = 0.0, + const bool userCanPressKeyToSwapMode = true) throw(); + + //============================================================================== + /** Sets up a skew factor to alter the way values are distributed. + + You may want to use a range of values on the slider where more accuracy + is required towards one end of the range, so this will logarithmically + spread the values across the length of the slider. + + If the factor is < 1.0, the lower end of the range will fill more of the + slider's length; if the factor is > 1.0, the upper end of the range + will be expanded instead. A factor of 1.0 doesn't skew it at all. + + To set the skew position by using a mid-point, use the setSkewFactorFromMidPoint() + method instead. + + @see getSkewFactor, setSkewFactorFromMidPoint + */ + void setSkewFactor (const double factor) throw(); + + /** Sets up a skew factor to alter the way values are distributed. + + This allows you to specify the slider value that should appear in the + centre of the slider's visible range. + + @see setSkewFactor, getSkewFactor + */ + void setSkewFactorFromMidPoint (const double sliderValueToShowAtMidPoint) throw(); + + /** Returns the current skew factor. + + See setSkewFactor for more info. + + @see setSkewFactor, setSkewFactorFromMidPoint + */ + double getSkewFactor() const throw() { return skewFactor; } + + //============================================================================== + /** Used by setIncDecButtonsMode(). + */ + enum IncDecButtonMode + { + incDecButtonsNotDraggable, + incDecButtonsDraggable_AutoDirection, + incDecButtonsDraggable_Horizontal, + incDecButtonsDraggable_Vertical + }; + + /** When the style is IncDecButtons, this lets you turn on a mode where the mouse + can be dragged on the buttons to drag the values. + + By default this is turned off. When enabled, clicking on the buttons still works + them as normal, but by holding down the mouse on a button and dragging it a little + distance, it flips into a mode where the value can be dragged. The drag direction can + either be set explicitly to be vertical or horizontal, or can be set to + incDecButtonsDraggable_AutoDirection so that it depends on whether the buttons + are side-by-side or above each other. + */ + void setIncDecButtonsMode (const IncDecButtonMode mode); + + //============================================================================== + /** The position of the slider's text-entry box. + + @see setTextBoxStyle + */ + enum TextEntryBoxPosition + { + NoTextBox, /**< Doesn't display a text box. */ + TextBoxLeft, /**< Puts the text box to the left of the slider, vertically centred. */ + TextBoxRight, /**< Puts the text box to the right of the slider, vertically centred. */ + TextBoxAbove, /**< Puts the text box above the slider, horizontally centred. */ + TextBoxBelow /**< Puts the text box below the slider, horizontally centred. */ + }; + + /** Changes the location and properties of the text-entry box. + + @param newPosition where it should go (or NoTextBox to not have one at all) + @param isReadOnly if true, it's a read-only display + @param textEntryBoxWidth the width of the text-box in pixels. Make sure this leaves enough + room for the slider as well! + @param textEntryBoxHeight the height of the text-box in pixels. Make sure this leaves enough + room for the slider as well! + + @see setTextBoxIsEditable, getValueFromText, getTextFromValue + */ + void setTextBoxStyle (const TextEntryBoxPosition newPosition, + const bool isReadOnly, + const int textEntryBoxWidth, + const int textEntryBoxHeight); + + /** Returns the status of the text-box. + @see setTextBoxStyle + */ + const TextEntryBoxPosition getTextBoxPosition() const throw() { return textBoxPos; } + + /** Returns the width used for the text-box. + @see setTextBoxStyle + */ + int getTextBoxWidth() const throw() { return textBoxWidth; } + + /** Returns the height used for the text-box. + @see setTextBoxStyle + */ + int getTextBoxHeight() const throw() { return textBoxHeight; } + + /** Makes the text-box editable. + + By default this is true, and the user can enter values into the textbox, + but it can be turned off if that's not suitable. + + @see setTextBoxStyle, getValueFromText, getTextFromValue + */ + void setTextBoxIsEditable (const bool shouldBeEditable) throw(); + + /** Returns true if the text-box is read-only. + @see setTextBoxStyle + */ + bool isTextBoxEditable() const throw() { return editableText; } + + /** If the text-box is editable, this will give it the focus so that the user can + type directly into it. + + This is basically the effect as the user clicking on it. + */ + void showTextBox(); + + /** If the text-box currently has focus and is being edited, this resets it and takes keyboard + focus away from it. + + @param discardCurrentEditorContents if true, the slider's value will be left + unchanged; if false, the current contents of the + text editor will be used to set the slider position + before it is hidden. + */ + void hideTextBox (const bool discardCurrentEditorContents); + + + //============================================================================== + /** Changes the slider's current value. + + This will trigger a callback to SliderListener::sliderValueChanged() for any listeners + that are registered, and will synchronously call the valueChanged() method in case subclasses + want to handle it. + + @param newValue the new value to set - this will be restricted by the + minimum and maximum range, and will be snapped to the + nearest interval if one has been set + @param sendUpdateMessage if false, a change to the value will not trigger a call to + any SliderListeners or the valueChanged() method + @param sendMessageSynchronously if true, then a call to the SliderListeners will be made + synchronously; if false, it will be asynchronous + */ + void setValue (double newValue, + const bool sendUpdateMessage = true, + const bool sendMessageSynchronously = false); + + /** Returns the slider's current value. */ + double getValue() const throw(); + + //============================================================================== + /** Sets the limits that the slider's value can take. + + @param newMinimum the lowest value allowed + @param newMaximum the highest value allowed + @param newInterval the steps in which the value is allowed to increase - if this + is not zero, the value will always be (newMinimum + (newInterval * an integer)). + */ + void setRange (const double newMinimum, + const double newMaximum, + const double newInterval = 0); + + /** Returns the current maximum value. + @see setRange + */ + double getMaximum() const throw() { return maximum; } + + /** Returns the current minimum value. + @see setRange + */ + double getMinimum() const throw() { return minimum; } + + /** Returns the current step-size for values. + @see setRange + */ + double getInterval() const throw() { return interval; } + + //============================================================================== + /** For a slider with two or three thumbs, this returns the lower of its values. + + For a two-value slider, the values are controlled with getMinValue() and getMaxValue(). + A slider with three values also uses the normal getValue() and setValue() methods to + control the middle value. + + @see setMinValue, getMaxValue, TwoValueHorizontal, TwoValueVertical, ThreeValueHorizontal, ThreeValueVertical + */ + double getMinValue() const throw(); + + /** For a slider with two or three thumbs, this sets the lower of its values. + + This will trigger a callback to SliderListener::sliderValueChanged() for any listeners + that are registered, and will synchronously call the valueChanged() method in case subclasses + want to handle it. + + @param newValue the new value to set - this will be restricted by the + minimum and maximum range, and will be snapped to the nearest + interval if one has been set. + @param sendUpdateMessage if false, a change to the value will not trigger a call to + any SliderListeners or the valueChanged() method + @param sendMessageSynchronously if true, then a call to the SliderListeners will be made + synchronously; if false, it will be asynchronous + @param allowNudgingOfOtherValues if false, this value will be restricted to being below the + max value (in a two-value slider) or the mid value (in a three-value + slider). If false, then if this value goes beyond those values, + it will push them along with it. + @see getMinValue, setMaxValue, setValue + */ + void setMinValue (double newValue, + const bool sendUpdateMessage = true, + const bool sendMessageSynchronously = false, + const bool allowNudgingOfOtherValues = false); + + /** For a slider with two or three thumbs, this returns the higher of its values. + + For a two-value slider, the values are controlled with getMinValue() and getMaxValue(). + A slider with three values also uses the normal getValue() and setValue() methods to + control the middle value. + + @see getMinValue, TwoValueHorizontal, TwoValueVertical, ThreeValueHorizontal, ThreeValueVertical + */ + double getMaxValue() const throw(); + + /** For a slider with two or three thumbs, this sets the lower of its values. + + This will trigger a callback to SliderListener::sliderValueChanged() for any listeners + that are registered, and will synchronously call the valueChanged() method in case subclasses + want to handle it. + + @param newValue the new value to set - this will be restricted by the + minimum and maximum range, and will be snapped to the nearest + interval if one has been set. + @param sendUpdateMessage if false, a change to the value will not trigger a call to + any SliderListeners or the valueChanged() method + @param sendMessageSynchronously if true, then a call to the SliderListeners will be made + synchronously; if false, it will be asynchronous + @param allowNudgingOfOtherValues if false, this value will be restricted to being above the + min value (in a two-value slider) or the mid value (in a three-value + slider). If false, then if this value goes beyond those values, + it will push them along with it. + @see getMaxValue, setMinValue, setValue + */ + void setMaxValue (double newValue, + const bool sendUpdateMessage = true, + const bool sendMessageSynchronously = false, + const bool allowNudgingOfOtherValues = false); + + //============================================================================== + /** Adds a listener to be called when this slider's value changes. */ + void addListener (SliderListener* const listener) throw(); + + /** Removes a previously-registered listener. */ + void removeListener (SliderListener* const listener) throw(); + + //============================================================================== + /** This lets you choose whether double-clicking moves the slider to a given position. + + By default this is turned off, but it's handy if you want a double-click to act + as a quick way of resetting a slider. Just pass in the value you want it to + go to when double-clicked. + + @see getDoubleClickReturnValue + */ + void setDoubleClickReturnValue (const bool isDoubleClickEnabled, + const double valueToSetOnDoubleClick) throw(); + + /** Returns the values last set by setDoubleClickReturnValue() method. + + Sets isEnabled to true if double-click is enabled, and returns the value + that was set. + + @see setDoubleClickReturnValue + */ + double getDoubleClickReturnValue (bool& isEnabled) const throw(); + + //============================================================================== + /** Tells the slider whether to keep sending change messages while the user + is dragging the slider. + + If set to true, a change message will only be sent when the user has + dragged the slider and let go. If set to false (the default), then messages + will be continuously sent as they drag it while the mouse button is still + held down. + */ + void setChangeNotificationOnlyOnRelease (const bool onlyNotifyOnRelease) throw(); + + /** This lets you change whether the slider thumb jumps to the mouse position + when you click. + + By default, this is true. If it's false, then the slider moves with relative + motion when you drag it. + + This only applies to linear bars, and won't affect two- or three- value + sliders. + */ + void setSliderSnapsToMousePosition (const bool shouldSnapToMouse) throw(); + + /** If enabled, this gives the slider a pop-up bubble which appears while the + slider is being dragged. + + This can be handy if your slider doesn't have a text-box, so that users can + see the value just when they're changing it. + + If you pass a component as the parentComponentToUse parameter, the pop-up + bubble will be added as a child of that component when it's needed. If you + pass 0, the pop-up will be placed on the desktop instead (note that it's a + transparent window, so if you're using an OS that can't do transparent windows + you'll have to add it to a parent component instead). + */ + void setPopupDisplayEnabled (const bool isEnabled, + Component* const parentComponentToUse) throw(); + + /** If this is set to true, then right-clicking on the slider will pop-up + a menu to let the user change the way it works. + + By default this is turned off, but when turned on, the menu will include + things like velocity sensitivity, and for rotary sliders, whether they + use a linear or rotary mouse-drag to move them. + */ + void setPopupMenuEnabled (const bool menuEnabled) throw(); + + /** This can be used to stop the mouse scroll-wheel from moving the slider. + + By default it's enabled. + */ + void setScrollWheelEnabled (const bool enabled) throw(); + + /** Returns a number to indicate which thumb is currently being dragged by the + mouse. + + This will return 0 for the main thumb, 1 for the minimum-value thumb, 2 for + the maximum-value thumb, or -1 if none is currently down. + */ + int getThumbBeingDragged() const throw() { return sliderBeingDragged; } + + //============================================================================== + /** Callback to indicate that the user is about to start dragging the slider. + + @see SliderListener::sliderDragStarted + */ + virtual void startedDragging(); + + /** Callback to indicate that the user has just stopped dragging the slider. + + @see SliderListener::sliderDragEnded + */ + virtual void stoppedDragging(); + + /** Callback to indicate that the user has just moved the slider. + + @see SliderListener::sliderValueChanged + */ + virtual void valueChanged(); + + /** Callback to indicate that the user has just moved the slider. + Note - the valueChanged() method has changed its format and now no longer has + any parameters. Update your code to use the new version. + This version has been left here with an int as its return value to cause + a syntax error if you've got existing code that uses the old version. + */ + virtual int valueChanged (double) { jassertfalse; return 0; } + + //============================================================================== + /** Subclasses can override this to convert a text string to a value. + + When the user enters something into the text-entry box, this method is + called to convert it to a value. + + The default routine just tries to convert it to a double. + + @see getTextFromValue + */ + virtual double getValueFromText (const String& text); + + /** Turns the slider's current value into a text string. + + Subclasses can override this to customise the formatting of the text-entry box. + + The default implementation just turns the value into a string, using + a number of decimal places based on the range interval. If a suffix string + has been set using setTextValueSuffix(), this will be appended to the text. + + @see getValueFromText + */ + virtual const String getTextFromValue (double value); + + /** Sets a suffix to append to the end of the numeric value when it's displayed as + a string. + + This is used by the default implementation of getTextFromValue(), and is just + appended to the numeric value. For more advanced formatting, you can override + getTextFromValue() and do something else. + */ + void setTextValueSuffix (const String& suffix); + + //============================================================================== + /** Allows a user-defined mapping of distance along the slider to its value. + + The default implementation for this performs the skewing operation that + can be set up in the setSkewFactor() method. Override it if you need + some kind of custom mapping instead, but make sure you also implement the + inverse function in valueToProportionOfLength(). + + @param proportion a value 0 to 1.0, indicating a distance along the slider + @returns the slider value that is represented by this position + @see valueToProportionOfLength + */ + virtual double proportionOfLengthToValue (double proportion); + + /** Allows a user-defined mapping of value to the position of the slider along its length. + + The default implementation for this performs the skewing operation that + can be set up in the setSkewFactor() method. Override it if you need + some kind of custom mapping instead, but make sure you also implement the + inverse function in proportionOfLengthToValue(). + + @param value a valid slider value, between the range of values specified in + setRange() + @returns a value 0 to 1.0 indicating the distance along the slider that + represents this value + @see proportionOfLengthToValue + */ + virtual double valueToProportionOfLength (double value); + + /** Returns the X or Y coordinate of a value along the slider's length. + + If the slider is horizontal, this will be the X coordinate of the given + value, relative to the left of the slider. If it's vertical, then this will + be the Y coordinate, relative to the top of the slider. + + If the slider is rotary, this will throw an assertion and return 0. If the + value is out-of-range, it will be constrained to the length of the slider. + */ + float getPositionOfValue (const double value); + + //============================================================================== + /** This can be overridden to allow the slider to snap to user-definable values. + + If overridden, it will be called when the user tries to move the slider to + a given position, and allows a subclass to sanity-check this value, possibly + returning a different value to use instead. + + @param attemptedValue the value the user is trying to enter + @param userIsDragging true if the user is dragging with the mouse; false if + they are entering the value using the text box + @returns the value to use instead + */ + virtual double snapValue (double attemptedValue, const bool userIsDragging); + + + //============================================================================== + /** This can be called to force the text box to update its contents. + + (Not normally needed, as this is done automatically). + */ + void updateText(); + + + /** True if the slider moves horizontally. */ + bool isHorizontal() const throw(); + /** True if the slider moves vertically. */ + bool isVertical() const throw(); + + //============================================================================== + /** A set of colour IDs to use to change the colour of various aspects of the slider. + + These constants can be used either via the Component::setColour(), or LookAndFeel::setColour() + methods. + + @see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour + */ + enum ColourIds + { + backgroundColourId = 0x1001200, /**< A colour to use to fill the slider's background. */ + thumbColourId = 0x1001300, /**< The colour to draw the thumb with. It's up to the look + and feel class how this is used. */ + trackColourId = 0x1001310, /**< The colour to draw the groove that the thumb moves along. */ + rotarySliderFillColourId = 0x1001311, /**< For rotary sliders, this colour fills the outer curve. */ + rotarySliderOutlineColourId = 0x1001312, /**< For rotary sliders, this colour is used to draw the outer curve's outline. */ + + textBoxTextColourId = 0x1001400, /**< The colour for the text in the text-editor box used for editing the value. */ + textBoxBackgroundColourId = 0x1001500, /**< The background colour for the text-editor box. */ + textBoxHighlightColourId = 0x1001600, /**< The text highlight colour for the text-editor box. */ + textBoxOutlineColourId = 0x1001700 /**< The colour to use for a border around the text-editor box. */ + }; + + //============================================================================== + juce_UseDebuggingNewOperator + +protected: + /** @internal */ + void labelTextChanged (Label*); + /** @internal */ + void paint (Graphics& g); + /** @internal */ + void resized(); + /** @internal */ + void mouseDown (const MouseEvent& e); + /** @internal */ + void mouseUp (const MouseEvent& e); + /** @internal */ + void mouseDrag (const MouseEvent& e); + /** @internal */ + void mouseDoubleClick (const MouseEvent& e); + /** @internal */ + void mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY); + /** @internal */ + void modifierKeysChanged (const ModifierKeys& modifiers); + /** @internal */ + void buttonClicked (Button* button); + /** @internal */ + void lookAndFeelChanged(); + /** @internal */ + void enablementChanged(); + /** @internal */ + void focusOfChildComponentChanged (FocusChangeType cause); + /** @internal */ + void handleAsyncUpdate(); + /** @internal */ + void colourChanged(); + +private: + SortedSet listeners; + double currentValue, valueMin, valueMax; + double minimum, maximum, interval, doubleClickReturnValue; + double valueWhenLastDragged, valueOnMouseDown, skewFactor, lastAngle; + double velocityModeSensitivity, velocityModeOffset, minMaxDiff; + int velocityModeThreshold; + float rotaryStart, rotaryEnd; + int numDecimalPlaces, mouseXWhenLastDragged, mouseYWhenLastDragged; + int mouseDragStartX, mouseDragStartY; + int sliderRegionStart, sliderRegionSize; + int sliderBeingDragged; + int pixelsForFullDragExtent; + Rectangle sliderRect; + String textSuffix; + + SliderStyle style; + TextEntryBoxPosition textBoxPos; + int textBoxWidth, textBoxHeight; + IncDecButtonMode incDecButtonMode; + + bool editableText : 1, doubleClickToValue : 1; + bool isVelocityBased : 1, userKeyOverridesVelocity : 1, rotaryStop : 1; + bool incDecButtonsSideBySide : 1, sendChangeOnlyOnRelease : 1, popupDisplayEnabled : 1; + bool menuEnabled : 1, menuShown : 1, mouseWasHidden : 1, incDecDragged : 1; + bool scrollWheelEnabled : 1, snapsToMousePos : 1; + Font font; + Label* valueBox; + Button* incButton; + Button* decButton; + Component* popupDisplay; + Component* parentForPopupDisplay; + + float getLinearSliderPos (const double value); + void restoreMouseIfHidden(); + void sendDragStart(); + void sendDragEnd(); + double constrainedValue (double value) const throw(); + void triggerChangeMessage (const bool synchronous); + bool incDecDragDirectionIsHorizontal() const throw(); + + Slider (const Slider&); + const Slider& operator= (const Slider&); +}; + + +#endif // __JUCE_SLIDER_JUCEHEADER__ diff --git a/src/juce_appframework/gui/components/controls/juce_TreeView.cpp b/src/juce_appframework/gui/components/controls/juce_TreeView.cpp index 6e7fddc396..582457b894 100644 --- a/src/juce_appframework/gui/components/controls/juce_TreeView.cpp +++ b/src/juce_appframework/gui/components/controls/juce_TreeView.cpp @@ -1158,7 +1158,7 @@ void TreeViewItem::paintRecursively (Graphics& g, int width) g.saveState(); g.setOrigin (indent, 0); - if (g.reduceClipRegion (drawsInLeftMargin ? -indent : 0, 0, + if (g.reduceClipRegion (drawsInLeftMargin ? -indent : 0, 0, drawsInLeftMargin ? itemW + indent : itemW, itemHeight)) paintItem (g, itemW, itemHeight); diff --git a/src/juce_appframework/gui/components/controls/juce_TreeView.h b/src/juce_appframework/gui/components/controls/juce_TreeView.h index 059d1907b2..ee6aee2e9c 100644 --- a/src/juce_appframework/gui/components/controls/juce_TreeView.h +++ b/src/juce_appframework/gui/components/controls/juce_TreeView.h @@ -355,7 +355,7 @@ public: can draw all the way across to the left margin. Note that the context will still have its origin in the same place though, so the coordinates of anything to its left will be negative. It's - mostly useful if you want to draw a wider bar behind the + mostly useful if you want to draw a wider bar behind the highlighted item. */ void setDrawsInLeftMargin (bool canDrawInLeftMargin) throw(); diff --git a/src/juce_appframework/gui/components/juce_Desktop.h b/src/juce_appframework/gui/components/juce_Desktop.h index a86a3d3c0f..8fae06b68c 100644 --- a/src/juce_appframework/gui/components/juce_Desktop.h +++ b/src/juce_appframework/gui/components/juce_Desktop.h @@ -190,7 +190,7 @@ public: allowed to pop up when the mouse moves onto them. If this is false, it'll try to hide as much on-screen paraphenalia as possible. */ - void setKioskModeComponent (Component* componentToUse, + void setKioskModeComponent (Component* componentToUse, const bool allowMenusAndBars = true); /** Returns the component that is currently being used in kiosk-mode. diff --git a/src/juce_appframework/gui/components/layout/juce_ComponentBoundsConstrainer.cpp b/src/juce_appframework/gui/components/layout/juce_ComponentBoundsConstrainer.cpp index e02ba8e637..638a6029e7 100644 --- a/src/juce_appframework/gui/components/layout/juce_ComponentBoundsConstrainer.cpp +++ b/src/juce_appframework/gui/components/layout/juce_ComponentBoundsConstrainer.cpp @@ -189,9 +189,9 @@ void ComponentBoundsConstrainer::setBoundsForComponent (Component* const compone void ComponentBoundsConstrainer::checkComponentBounds (Component* component) { - setBoundsForComponent (component, + setBoundsForComponent (component, component->getX(), component->getY(), - component->getWidth(), component->getHeight(), + component->getWidth(), component->getHeight(), false, false, false, false); } diff --git a/src/juce_appframework/gui/components/layout/juce_ComponentBoundsConstrainer.h b/src/juce_appframework/gui/components/layout/juce_ComponentBoundsConstrainer.h index 8acfb81bca..0b41a926a1 100644 --- a/src/juce_appframework/gui/components/layout/juce_ComponentBoundsConstrainer.h +++ b/src/juce_appframework/gui/components/layout/juce_ComponentBoundsConstrainer.h @@ -172,7 +172,7 @@ public: const bool isStretchingBottom, const bool isStretchingRight); - /** Performs a check on the current size of a component, and moves or resizes + /** Performs a check on the current size of a component, and moves or resizes it if it fails the constraints. */ void checkComponentBounds (Component* component); diff --git a/src/juce_appframework/gui/components/layout/juce_TabbedButtonBar.cpp b/src/juce_appframework/gui/components/layout/juce_TabbedButtonBar.cpp index 4c61297073..1742c6e1db 100644 --- a/src/juce_appframework/gui/components/layout/juce_TabbedButtonBar.cpp +++ b/src/juce_appframework/gui/components/layout/juce_TabbedButtonBar.cpp @@ -1,550 +1,550 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - JUCE is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../../../../juce_core/basics/juce_StandardHeader.h" - -BEGIN_JUCE_NAMESPACE - -#include "juce_TabbedComponent.h" -#include "../menus/juce_PopupMenu.h" -#include "../lookandfeel/juce_LookAndFeel.h" - - -//============================================================================== -TabBarButton::TabBarButton (const String& name, - TabbedButtonBar* const owner_, - const int index) - : Button (name), - owner (owner_), - tabIndex (index), - overlapPixels (0) -{ - shadow.setShadowProperties (2.2f, 0.7f, 0, 0); - setComponentEffect (&shadow); - setWantsKeyboardFocus (false); -} - -TabBarButton::~TabBarButton() -{ -} - -void TabBarButton::paintButton (Graphics& g, - bool isMouseOverButton, - bool isButtonDown) -{ - int x, y, w, h; - getActiveArea (x, y, w, h); - - g.setOrigin (x, y); - - getLookAndFeel() - .drawTabButton (g, w, h, - owner->getTabBackgroundColour (tabIndex), - tabIndex, getButtonText(), *this, - owner->getOrientation(), - isMouseOverButton, isButtonDown, - getToggleState()); -} - -void TabBarButton::clicked (const ModifierKeys& mods) -{ - if (mods.isPopupMenu()) - owner->popupMenuClickOnTab (tabIndex, getButtonText()); - else - owner->setCurrentTabIndex (tabIndex); -} - -bool TabBarButton::hitTest (int mx, int my) -{ - int x, y, w, h; - getActiveArea (x, y, w, h); - - if (owner->getOrientation() == TabbedButtonBar::TabsAtLeft - || owner->getOrientation() == TabbedButtonBar::TabsAtRight) - { - if (((unsigned int) mx) < (unsigned int) getWidth() - && my >= y + overlapPixels - && my < y + h - overlapPixels) - return true; - } - else - { - if (mx >= x + overlapPixels && mx < x + w - overlapPixels - && ((unsigned int) my) < (unsigned int) getHeight()) - return true; - } - - Path p; - getLookAndFeel() - .createTabButtonShape (p, w, h, tabIndex, getButtonText(), *this, - owner->getOrientation(), - false, false, getToggleState()); - - return p.contains ((float) (mx - x), - (float) (my - y)); -} - -int TabBarButton::getBestTabLength (const int depth) -{ - return jlimit (depth * 2, - depth * 7, - getLookAndFeel().getTabButtonBestWidth (tabIndex, getButtonText(), depth, *this)); -} - -void TabBarButton::getActiveArea (int& x, int& y, int& w, int& h) -{ - x = 0; - y = 0; - int r = getWidth(); - int b = getHeight(); - - const int spaceAroundImage = getLookAndFeel().getTabButtonSpaceAroundImage(); - - if (owner->getOrientation() != TabbedButtonBar::TabsAtLeft) - r -= spaceAroundImage; - - if (owner->getOrientation() != TabbedButtonBar::TabsAtRight) - x += spaceAroundImage; - - if (owner->getOrientation() != TabbedButtonBar::TabsAtBottom) - y += spaceAroundImage; - - if (owner->getOrientation() != TabbedButtonBar::TabsAtTop) - b -= spaceAroundImage; - - w = r - x; - h = b - y; -} - - -//============================================================================== -class TabAreaBehindFrontButtonComponent : public Component -{ -public: - TabAreaBehindFrontButtonComponent (TabbedButtonBar* const owner_) - : owner (owner_) - { - setInterceptsMouseClicks (false, false); - } - - ~TabAreaBehindFrontButtonComponent() - { - } - - void paint (Graphics& g) - { - getLookAndFeel() - .drawTabAreaBehindFrontButton (g, getWidth(), getHeight(), - *owner, owner->getOrientation()); - } - - void enablementChanged() - { - repaint(); - } - -private: - TabbedButtonBar* const owner; - - TabAreaBehindFrontButtonComponent (const TabAreaBehindFrontButtonComponent&); - const TabAreaBehindFrontButtonComponent& operator= (const TabAreaBehindFrontButtonComponent&); -}; - - -//============================================================================== -TabbedButtonBar::TabbedButtonBar (const Orientation orientation_) - : orientation (orientation_), - currentTabIndex (-1), - extraTabsButton (0) -{ - setInterceptsMouseClicks (false, true); - addAndMakeVisible (behindFrontTab = new TabAreaBehindFrontButtonComponent (this)); - setFocusContainer (true); -} - -TabbedButtonBar::~TabbedButtonBar() -{ - deleteAllChildren(); -} - -//============================================================================== -void TabbedButtonBar::setOrientation (const Orientation newOrientation) -{ - orientation = newOrientation; - - for (int i = getNumChildComponents(); --i >= 0;) - getChildComponent (i)->resized(); - - resized(); -} - -TabBarButton* TabbedButtonBar::createTabButton (const String& name, const int index) -{ - return new TabBarButton (name, this, index); -} - -//============================================================================== -void TabbedButtonBar::clearTabs() -{ - tabs.clear(); - tabColours.clear(); - currentTabIndex = -1; - - deleteAndZero (extraTabsButton); - removeChildComponent (behindFrontTab); - deleteAllChildren(); - addChildComponent (behindFrontTab); - - setCurrentTabIndex (-1); -} - -void TabbedButtonBar::addTab (const String& tabName, - const Colour& tabBackgroundColour, - int insertIndex) -{ - jassert (tabName.isNotEmpty()); // you have to give them all a name.. - - if (tabName.isNotEmpty()) - { - if (((unsigned int) insertIndex) > (unsigned int) tabs.size()) - insertIndex = tabs.size(); - - for (int i = tabs.size(); --i >= insertIndex;) - { - TabBarButton* const tb = getTabButton (i); - - if (tb != 0) - tb->tabIndex++; - } - - tabs.insert (insertIndex, tabName); - tabColours.insert (insertIndex, tabBackgroundColour); - - TabBarButton* const tb = createTabButton (tabName, insertIndex); - jassert (tb != 0); // your createTabButton() mustn't return zero! - - addAndMakeVisible (tb, insertIndex); - - resized(); - - if (currentTabIndex < 0) - setCurrentTabIndex (0); - } -} - -void TabbedButtonBar::setTabName (const int tabIndex, - const String& newName) -{ - if (((unsigned int) tabIndex) < (unsigned int) tabs.size() - && tabs[tabIndex] != newName) - { - tabs.set (tabIndex, newName); - - TabBarButton* const tb = getTabButton (tabIndex); - - if (tb != 0) - tb->setButtonText (newName); - - resized(); - } -} - -void TabbedButtonBar::removeTab (const int tabIndex) -{ - if (((unsigned int) tabIndex) < (unsigned int) tabs.size()) - { - const int oldTabIndex = currentTabIndex; - if (currentTabIndex == tabIndex) - currentTabIndex = -1; - - tabs.remove (tabIndex); - tabColours.remove (tabIndex); - - TabBarButton* const tb = getTabButton (tabIndex); - - if (tb != 0) - delete tb; - - for (int i = tabIndex + 1; i <= tabs.size(); ++i) - { - TabBarButton* const tb = getTabButton (i); - - if (tb != 0) - tb->tabIndex--; - } - - resized(); - - setCurrentTabIndex (jlimit (0, jmax (0, tabs.size() - 1), oldTabIndex)); - } -} - -void TabbedButtonBar::moveTab (const int currentIndex, - const int newIndex) -{ - tabs.move (currentIndex, newIndex); - tabColours.move (currentIndex, newIndex); - resized(); -} - -int TabbedButtonBar::getNumTabs() const -{ - return tabs.size(); -} - -const StringArray TabbedButtonBar::getTabNames() const -{ - return tabs; -} - -void TabbedButtonBar::setCurrentTabIndex (int newIndex, const bool sendChangeMessage_) -{ - if (currentTabIndex != newIndex) - { - if (((unsigned int) newIndex) >= (unsigned int) tabs.size()) - newIndex = -1; - - currentTabIndex = newIndex; - - for (int i = 0; i < getNumChildComponents(); ++i) - { - TabBarButton* const tb = dynamic_cast (getChildComponent (i)); - - if (tb != 0) - tb->setToggleState (tb->tabIndex == newIndex, false); - } - - resized(); - - if (sendChangeMessage_) - sendChangeMessage (this); - - currentTabChanged (newIndex, newIndex >= 0 ? tabs [newIndex] : String::empty); - } -} - -TabBarButton* TabbedButtonBar::getTabButton (const int index) const -{ - for (int i = getNumChildComponents(); --i >= 0;) - { - TabBarButton* const tb = dynamic_cast (getChildComponent (i)); - - if (tb != 0 && tb->tabIndex == index) - return tb; - } - - return 0; -} - -void TabbedButtonBar::lookAndFeelChanged() -{ - deleteAndZero (extraTabsButton); - resized(); -} - -void TabbedButtonBar::resized() -{ - const double minimumScale = 0.7; - int depth = getWidth(); - int length = getHeight(); - - if (orientation == TabsAtTop || orientation == TabsAtBottom) - swapVariables (depth, length); - - const int overlap = getLookAndFeel().getTabButtonOverlap (depth) - + getLookAndFeel().getTabButtonSpaceAroundImage() * 2; - - int i, totalLength = overlap; - int numVisibleButtons = tabs.size(); - - for (i = 0; i < getNumChildComponents(); ++i) - { - TabBarButton* const tb = dynamic_cast (getChildComponent (i)); - - if (tb != 0) - { - totalLength += tb->getBestTabLength (depth) - overlap; - tb->overlapPixels = overlap / 2; - } - } - - double scale = 1.0; - - if (totalLength > length) - scale = jmax (minimumScale, length / (double) totalLength); - - const bool isTooBig = totalLength * scale > length; - int tabsButtonPos = 0; - - if (isTooBig) - { - if (extraTabsButton == 0) - { - addAndMakeVisible (extraTabsButton = getLookAndFeel().createTabBarExtrasButton()); - extraTabsButton->addButtonListener (this); - extraTabsButton->setAlwaysOnTop (true); - extraTabsButton->setTriggeredOnMouseDown (true); - } - - const int buttonSize = jmin (proportionOfWidth (0.7f), proportionOfHeight (0.7f)); - extraTabsButton->setSize (buttonSize, buttonSize); - - if (orientation == TabsAtTop || orientation == TabsAtBottom) - { - tabsButtonPos = getWidth() - buttonSize / 2 - 1; - extraTabsButton->setCentrePosition (tabsButtonPos, getHeight() / 2); - } - else - { - tabsButtonPos = getHeight() - buttonSize / 2 - 1; - extraTabsButton->setCentrePosition (getWidth() / 2, tabsButtonPos); - } - - totalLength = 0; - - for (i = 0; i < tabs.size(); ++i) - { - TabBarButton* const tb = getTabButton (i); - - if (tb != 0) - { - const int newLength = totalLength + tb->getBestTabLength (depth); - - if (i > 0 && newLength * minimumScale > tabsButtonPos) - { - totalLength += overlap; - break; - } - - numVisibleButtons = i + 1; - totalLength = newLength - overlap; - - } - } - - scale = jmax (minimumScale, tabsButtonPos / (double) totalLength); - } - else - { - deleteAndZero (extraTabsButton); - } - - int pos = 0; - - TabBarButton* frontTab = 0; - - for (i = 0; i < tabs.size(); ++i) - { - TabBarButton* const tb = getTabButton (i); - - if (tb != 0) - { - const int bestLength = roundDoubleToInt (scale * tb->getBestTabLength (depth)); - - if (i < numVisibleButtons) - { - if (orientation == TabsAtTop || orientation == TabsAtBottom) - tb->setBounds (pos, 0, bestLength, getHeight()); - else - tb->setBounds (0, pos, getWidth(), bestLength); - - tb->toBack(); - - if (tb->tabIndex == currentTabIndex) - frontTab = tb; - - tb->setVisible (true); - } - else - { - tb->setVisible (false); - } - - pos += bestLength - overlap; - } - } - - behindFrontTab->setBounds (0, 0, getWidth(), getHeight()); - - if (frontTab != 0) - { - frontTab->toFront (false); - behindFrontTab->toBehind (frontTab); - } -} - -//============================================================================== -const Colour TabbedButtonBar::getTabBackgroundColour (const int tabIndex) -{ - return tabColours [tabIndex]; -} - -void TabbedButtonBar::setTabBackgroundColour (const int tabIndex, const Colour& newColour) -{ - if (((unsigned int) tabIndex) < (unsigned int) tabColours.size() - && tabColours [tabIndex] != newColour) - { - tabColours.set (tabIndex, newColour); - repaint(); - } -} - -void TabbedButtonBar::buttonClicked (Button* button) -{ - if (extraTabsButton == button) - { - PopupMenu m; - - for (int i = 0; i < tabs.size(); ++i) - { - TabBarButton* const tb = getTabButton (i); - - if (tb != 0 && ! tb->isVisible()) - m.addItem (tb->tabIndex + 1, tabs[i], true, i == currentTabIndex); - } - - const int res = m.showAt (extraTabsButton); - - if (res != 0) - setCurrentTabIndex (res - 1); - } -} - -//============================================================================== -void TabbedButtonBar::currentTabChanged (const int, const String&) -{ -} - -void TabbedButtonBar::popupMenuClickOnTab (const int, const String&) -{ -} - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + JUCE is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../../../../juce_core/basics/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + +#include "juce_TabbedComponent.h" +#include "../menus/juce_PopupMenu.h" +#include "../lookandfeel/juce_LookAndFeel.h" + + +//============================================================================== +TabBarButton::TabBarButton (const String& name, + TabbedButtonBar* const owner_, + const int index) + : Button (name), + owner (owner_), + tabIndex (index), + overlapPixels (0) +{ + shadow.setShadowProperties (2.2f, 0.7f, 0, 0); + setComponentEffect (&shadow); + setWantsKeyboardFocus (false); +} + +TabBarButton::~TabBarButton() +{ +} + +void TabBarButton::paintButton (Graphics& g, + bool isMouseOverButton, + bool isButtonDown) +{ + int x, y, w, h; + getActiveArea (x, y, w, h); + + g.setOrigin (x, y); + + getLookAndFeel() + .drawTabButton (g, w, h, + owner->getTabBackgroundColour (tabIndex), + tabIndex, getButtonText(), *this, + owner->getOrientation(), + isMouseOverButton, isButtonDown, + getToggleState()); +} + +void TabBarButton::clicked (const ModifierKeys& mods) +{ + if (mods.isPopupMenu()) + owner->popupMenuClickOnTab (tabIndex, getButtonText()); + else + owner->setCurrentTabIndex (tabIndex); +} + +bool TabBarButton::hitTest (int mx, int my) +{ + int x, y, w, h; + getActiveArea (x, y, w, h); + + if (owner->getOrientation() == TabbedButtonBar::TabsAtLeft + || owner->getOrientation() == TabbedButtonBar::TabsAtRight) + { + if (((unsigned int) mx) < (unsigned int) getWidth() + && my >= y + overlapPixels + && my < y + h - overlapPixels) + return true; + } + else + { + if (mx >= x + overlapPixels && mx < x + w - overlapPixels + && ((unsigned int) my) < (unsigned int) getHeight()) + return true; + } + + Path p; + getLookAndFeel() + .createTabButtonShape (p, w, h, tabIndex, getButtonText(), *this, + owner->getOrientation(), + false, false, getToggleState()); + + return p.contains ((float) (mx - x), + (float) (my - y)); +} + +int TabBarButton::getBestTabLength (const int depth) +{ + return jlimit (depth * 2, + depth * 7, + getLookAndFeel().getTabButtonBestWidth (tabIndex, getButtonText(), depth, *this)); +} + +void TabBarButton::getActiveArea (int& x, int& y, int& w, int& h) +{ + x = 0; + y = 0; + int r = getWidth(); + int b = getHeight(); + + const int spaceAroundImage = getLookAndFeel().getTabButtonSpaceAroundImage(); + + if (owner->getOrientation() != TabbedButtonBar::TabsAtLeft) + r -= spaceAroundImage; + + if (owner->getOrientation() != TabbedButtonBar::TabsAtRight) + x += spaceAroundImage; + + if (owner->getOrientation() != TabbedButtonBar::TabsAtBottom) + y += spaceAroundImage; + + if (owner->getOrientation() != TabbedButtonBar::TabsAtTop) + b -= spaceAroundImage; + + w = r - x; + h = b - y; +} + + +//============================================================================== +class TabAreaBehindFrontButtonComponent : public Component +{ +public: + TabAreaBehindFrontButtonComponent (TabbedButtonBar* const owner_) + : owner (owner_) + { + setInterceptsMouseClicks (false, false); + } + + ~TabAreaBehindFrontButtonComponent() + { + } + + void paint (Graphics& g) + { + getLookAndFeel() + .drawTabAreaBehindFrontButton (g, getWidth(), getHeight(), + *owner, owner->getOrientation()); + } + + void enablementChanged() + { + repaint(); + } + +private: + TabbedButtonBar* const owner; + + TabAreaBehindFrontButtonComponent (const TabAreaBehindFrontButtonComponent&); + const TabAreaBehindFrontButtonComponent& operator= (const TabAreaBehindFrontButtonComponent&); +}; + + +//============================================================================== +TabbedButtonBar::TabbedButtonBar (const Orientation orientation_) + : orientation (orientation_), + currentTabIndex (-1), + extraTabsButton (0) +{ + setInterceptsMouseClicks (false, true); + addAndMakeVisible (behindFrontTab = new TabAreaBehindFrontButtonComponent (this)); + setFocusContainer (true); +} + +TabbedButtonBar::~TabbedButtonBar() +{ + deleteAllChildren(); +} + +//============================================================================== +void TabbedButtonBar::setOrientation (const Orientation newOrientation) +{ + orientation = newOrientation; + + for (int i = getNumChildComponents(); --i >= 0;) + getChildComponent (i)->resized(); + + resized(); +} + +TabBarButton* TabbedButtonBar::createTabButton (const String& name, const int index) +{ + return new TabBarButton (name, this, index); +} + +//============================================================================== +void TabbedButtonBar::clearTabs() +{ + tabs.clear(); + tabColours.clear(); + currentTabIndex = -1; + + deleteAndZero (extraTabsButton); + removeChildComponent (behindFrontTab); + deleteAllChildren(); + addChildComponent (behindFrontTab); + + setCurrentTabIndex (-1); +} + +void TabbedButtonBar::addTab (const String& tabName, + const Colour& tabBackgroundColour, + int insertIndex) +{ + jassert (tabName.isNotEmpty()); // you have to give them all a name.. + + if (tabName.isNotEmpty()) + { + if (((unsigned int) insertIndex) > (unsigned int) tabs.size()) + insertIndex = tabs.size(); + + for (int i = tabs.size(); --i >= insertIndex;) + { + TabBarButton* const tb = getTabButton (i); + + if (tb != 0) + tb->tabIndex++; + } + + tabs.insert (insertIndex, tabName); + tabColours.insert (insertIndex, tabBackgroundColour); + + TabBarButton* const tb = createTabButton (tabName, insertIndex); + jassert (tb != 0); // your createTabButton() mustn't return zero! + + addAndMakeVisible (tb, insertIndex); + + resized(); + + if (currentTabIndex < 0) + setCurrentTabIndex (0); + } +} + +void TabbedButtonBar::setTabName (const int tabIndex, + const String& newName) +{ + if (((unsigned int) tabIndex) < (unsigned int) tabs.size() + && tabs[tabIndex] != newName) + { + tabs.set (tabIndex, newName); + + TabBarButton* const tb = getTabButton (tabIndex); + + if (tb != 0) + tb->setButtonText (newName); + + resized(); + } +} + +void TabbedButtonBar::removeTab (const int tabIndex) +{ + if (((unsigned int) tabIndex) < (unsigned int) tabs.size()) + { + const int oldTabIndex = currentTabIndex; + if (currentTabIndex == tabIndex) + currentTabIndex = -1; + + tabs.remove (tabIndex); + tabColours.remove (tabIndex); + + TabBarButton* const tb = getTabButton (tabIndex); + + if (tb != 0) + delete tb; + + for (int i = tabIndex + 1; i <= tabs.size(); ++i) + { + TabBarButton* const tb = getTabButton (i); + + if (tb != 0) + tb->tabIndex--; + } + + resized(); + + setCurrentTabIndex (jlimit (0, jmax (0, tabs.size() - 1), oldTabIndex)); + } +} + +void TabbedButtonBar::moveTab (const int currentIndex, + const int newIndex) +{ + tabs.move (currentIndex, newIndex); + tabColours.move (currentIndex, newIndex); + resized(); +} + +int TabbedButtonBar::getNumTabs() const +{ + return tabs.size(); +} + +const StringArray TabbedButtonBar::getTabNames() const +{ + return tabs; +} + +void TabbedButtonBar::setCurrentTabIndex (int newIndex, const bool sendChangeMessage_) +{ + if (currentTabIndex != newIndex) + { + if (((unsigned int) newIndex) >= (unsigned int) tabs.size()) + newIndex = -1; + + currentTabIndex = newIndex; + + for (int i = 0; i < getNumChildComponents(); ++i) + { + TabBarButton* const tb = dynamic_cast (getChildComponent (i)); + + if (tb != 0) + tb->setToggleState (tb->tabIndex == newIndex, false); + } + + resized(); + + if (sendChangeMessage_) + sendChangeMessage (this); + + currentTabChanged (newIndex, newIndex >= 0 ? tabs [newIndex] : String::empty); + } +} + +TabBarButton* TabbedButtonBar::getTabButton (const int index) const +{ + for (int i = getNumChildComponents(); --i >= 0;) + { + TabBarButton* const tb = dynamic_cast (getChildComponent (i)); + + if (tb != 0 && tb->tabIndex == index) + return tb; + } + + return 0; +} + +void TabbedButtonBar::lookAndFeelChanged() +{ + deleteAndZero (extraTabsButton); + resized(); +} + +void TabbedButtonBar::resized() +{ + const double minimumScale = 0.7; + int depth = getWidth(); + int length = getHeight(); + + if (orientation == TabsAtTop || orientation == TabsAtBottom) + swapVariables (depth, length); + + const int overlap = getLookAndFeel().getTabButtonOverlap (depth) + + getLookAndFeel().getTabButtonSpaceAroundImage() * 2; + + int i, totalLength = overlap; + int numVisibleButtons = tabs.size(); + + for (i = 0; i < getNumChildComponents(); ++i) + { + TabBarButton* const tb = dynamic_cast (getChildComponent (i)); + + if (tb != 0) + { + totalLength += tb->getBestTabLength (depth) - overlap; + tb->overlapPixels = overlap / 2; + } + } + + double scale = 1.0; + + if (totalLength > length) + scale = jmax (minimumScale, length / (double) totalLength); + + const bool isTooBig = totalLength * scale > length; + int tabsButtonPos = 0; + + if (isTooBig) + { + if (extraTabsButton == 0) + { + addAndMakeVisible (extraTabsButton = getLookAndFeel().createTabBarExtrasButton()); + extraTabsButton->addButtonListener (this); + extraTabsButton->setAlwaysOnTop (true); + extraTabsButton->setTriggeredOnMouseDown (true); + } + + const int buttonSize = jmin (proportionOfWidth (0.7f), proportionOfHeight (0.7f)); + extraTabsButton->setSize (buttonSize, buttonSize); + + if (orientation == TabsAtTop || orientation == TabsAtBottom) + { + tabsButtonPos = getWidth() - buttonSize / 2 - 1; + extraTabsButton->setCentrePosition (tabsButtonPos, getHeight() / 2); + } + else + { + tabsButtonPos = getHeight() - buttonSize / 2 - 1; + extraTabsButton->setCentrePosition (getWidth() / 2, tabsButtonPos); + } + + totalLength = 0; + + for (i = 0; i < tabs.size(); ++i) + { + TabBarButton* const tb = getTabButton (i); + + if (tb != 0) + { + const int newLength = totalLength + tb->getBestTabLength (depth); + + if (i > 0 && newLength * minimumScale > tabsButtonPos) + { + totalLength += overlap; + break; + } + + numVisibleButtons = i + 1; + totalLength = newLength - overlap; + + } + } + + scale = jmax (minimumScale, tabsButtonPos / (double) totalLength); + } + else + { + deleteAndZero (extraTabsButton); + } + + int pos = 0; + + TabBarButton* frontTab = 0; + + for (i = 0; i < tabs.size(); ++i) + { + TabBarButton* const tb = getTabButton (i); + + if (tb != 0) + { + const int bestLength = roundDoubleToInt (scale * tb->getBestTabLength (depth)); + + if (i < numVisibleButtons) + { + if (orientation == TabsAtTop || orientation == TabsAtBottom) + tb->setBounds (pos, 0, bestLength, getHeight()); + else + tb->setBounds (0, pos, getWidth(), bestLength); + + tb->toBack(); + + if (tb->tabIndex == currentTabIndex) + frontTab = tb; + + tb->setVisible (true); + } + else + { + tb->setVisible (false); + } + + pos += bestLength - overlap; + } + } + + behindFrontTab->setBounds (0, 0, getWidth(), getHeight()); + + if (frontTab != 0) + { + frontTab->toFront (false); + behindFrontTab->toBehind (frontTab); + } +} + +//============================================================================== +const Colour TabbedButtonBar::getTabBackgroundColour (const int tabIndex) +{ + return tabColours [tabIndex]; +} + +void TabbedButtonBar::setTabBackgroundColour (const int tabIndex, const Colour& newColour) +{ + if (((unsigned int) tabIndex) < (unsigned int) tabColours.size() + && tabColours [tabIndex] != newColour) + { + tabColours.set (tabIndex, newColour); + repaint(); + } +} + +void TabbedButtonBar::buttonClicked (Button* button) +{ + if (extraTabsButton == button) + { + PopupMenu m; + + for (int i = 0; i < tabs.size(); ++i) + { + TabBarButton* const tb = getTabButton (i); + + if (tb != 0 && ! tb->isVisible()) + m.addItem (tb->tabIndex + 1, tabs[i], true, i == currentTabIndex); + } + + const int res = m.showAt (extraTabsButton); + + if (res != 0) + setCurrentTabIndex (res - 1); + } +} + +//============================================================================== +void TabbedButtonBar::currentTabChanged (const int, const String&) +{ +} + +void TabbedButtonBar::popupMenuClickOnTab (const int, const String&) +{ +} + +END_JUCE_NAMESPACE diff --git a/src/juce_appframework/gui/components/windows/juce_ComponentPeer.cpp b/src/juce_appframework/gui/components/windows/juce_ComponentPeer.cpp index c78e989349..9f35555f3c 100644 --- a/src/juce_appframework/gui/components/windows/juce_ComponentPeer.cpp +++ b/src/juce_appframework/gui/components/windows/juce_ComponentPeer.cpp @@ -1,788 +1,788 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - JUCE is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../../../../juce_core/basics/juce_StandardHeader.h" - -BEGIN_JUCE_NAMESPACE - -#include "../../../application/juce_Application.h" -#include "../juce_Component.h" -#include "../juce_ComponentDeletionWatcher.h" -#include "../juce_Desktop.h" -#include "../../../events/juce_MessageManager.h" -#include "../../../../juce_core/basics/juce_Time.h" -#include "../../../../juce_core/basics/juce_Random.h" -#include "../layout/juce_ComponentBoundsConstrainer.h" -#include "../mouse/juce_FileDragAndDropTarget.h" - -//#define JUCE_ENABLE_REPAINT_DEBUGGING 1 - - -//============================================================================== -// these are over in juce_component.cpp -extern int64 juce_recentMouseDownTimes[4]; -extern int juce_recentMouseDownX [4]; -extern int juce_recentMouseDownY [4]; -extern Component* juce_recentMouseDownComponent [4]; -extern int juce_LastMousePosX; -extern int juce_LastMousePosY; -extern int juce_MouseClickCounter; -extern bool juce_MouseHasMovedSignificantlySincePressed; - -static const int fakeMouseMoveMessage = 0x7fff00ff; - -static VoidArray heavyweightPeers (4); - - -//============================================================================== -ComponentPeer::ComponentPeer (Component* const component_, - const int styleFlags_) throw() - : component (component_), - styleFlags (styleFlags_), - lastPaintTime (0), - constrainer (0), - lastFocusedComponent (0), - dragAndDropTargetComponent (0), - lastDragAndDropCompUnderMouse (0), - fakeMouseMessageSent (false), - isWindowMinimised (false) -{ - heavyweightPeers.add (this); -} - -ComponentPeer::~ComponentPeer() -{ - heavyweightPeers.removeValue (this); - delete dragAndDropTargetComponent; - - Desktop::getInstance().triggerFocusCallback(); -} - -//============================================================================== -int ComponentPeer::getNumPeers() throw() -{ - return heavyweightPeers.size(); -} - -ComponentPeer* ComponentPeer::getPeer (const int index) throw() -{ - return (ComponentPeer*) heavyweightPeers [index]; -} - -ComponentPeer* ComponentPeer::getPeerFor (const Component* const component) throw() -{ - for (int i = heavyweightPeers.size(); --i >= 0;) - { - ComponentPeer* const peer = (ComponentPeer*) heavyweightPeers.getUnchecked(i); - - if (peer->getComponent() == component) - return peer; - } - - return 0; -} - -bool ComponentPeer::isValidPeer (const ComponentPeer* const peer) throw() -{ - return heavyweightPeers.contains (const_cast (peer)); -} - -void ComponentPeer::updateCurrentModifiers() throw() -{ - ModifierKeys::updateCurrentModifiers(); -} - -//============================================================================== -void ComponentPeer::handleMouseEnter (int x, int y, const int64 time) -{ - jassert (component->isValidComponent()); - updateCurrentModifiers(); - - Component* c = component->getComponentAt (x, y); - const ComponentDeletionWatcher deletionChecker (component); - - if (c != Component::componentUnderMouse && Component::componentUnderMouse != 0) - { - jassert (Component::componentUnderMouse->isValidComponent()); - - const int oldX = x; - const int oldY = y; - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - Component::componentUnderMouse->internalMouseExit (x, y, time); - Component::componentUnderMouse = 0; - - if (deletionChecker.hasBeenDeleted()) - return; - - c = component->getComponentAt (oldX, oldY); - } - - Component::componentUnderMouse = c; - - if (Component::componentUnderMouse != 0) - { - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - Component::componentUnderMouse->internalMouseEnter (x, y, time); - } -} - -void ComponentPeer::handleMouseMove (int x, int y, const int64 time) -{ - jassert (component->isValidComponent()); - updateCurrentModifiers(); - - fakeMouseMessageSent = false; - - const ComponentDeletionWatcher deletionChecker (component); - Component* c = component->getComponentAt (x, y); - - if (c != Component::componentUnderMouse) - { - const int oldX = x; - const int oldY = y; - - if (Component::componentUnderMouse != 0) - { - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - Component::componentUnderMouse->internalMouseExit (x, y, time); - x = oldX; - y = oldY; - - Component::componentUnderMouse = 0; - - if (deletionChecker.hasBeenDeleted()) - return; // if this window has just been deleted.. - - c = component->getComponentAt (x, y); - } - - Component::componentUnderMouse = c; - - if (c != 0) - { - component->relativePositionToOtherComponent (c, x, y); - c->internalMouseEnter (x, y, time); - x = oldX; - y = oldY; - - if (deletionChecker.hasBeenDeleted()) - return; // if this window has just been deleted.. - } - } - - if (Component::componentUnderMouse != 0) - { - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - Component::componentUnderMouse->internalMouseMove (x, y, time); - } -} - -void ComponentPeer::handleMouseDown (int x, int y, const int64 time) -{ - ++juce_MouseClickCounter; - - updateCurrentModifiers(); - - int numMouseButtonsDown = 0; - - if (ModifierKeys::getCurrentModifiers().isLeftButtonDown()) - ++numMouseButtonsDown; - - if (ModifierKeys::getCurrentModifiers().isRightButtonDown()) - ++numMouseButtonsDown; - - if (ModifierKeys::getCurrentModifiers().isMiddleButtonDown()) - ++numMouseButtonsDown; - - if (numMouseButtonsDown == 1) - { - Component::componentUnderMouse = component->getComponentAt (x, y); - - if (Component::componentUnderMouse != 0) - { - // can't set these in the mouseDownInt() method, because it's re-entrant, so do it here.. - - for (int i = numElementsInArray (juce_recentMouseDownTimes); --i > 0;) - { - juce_recentMouseDownTimes [i] = juce_recentMouseDownTimes [i - 1]; - juce_recentMouseDownX [i] = juce_recentMouseDownX [i - 1]; - juce_recentMouseDownY [i] = juce_recentMouseDownY [i - 1]; - juce_recentMouseDownComponent [i] = juce_recentMouseDownComponent [i - 1]; - } - - juce_recentMouseDownTimes[0] = time; - juce_recentMouseDownX[0] = x; - juce_recentMouseDownY[0] = y; - juce_recentMouseDownComponent[0] = Component::componentUnderMouse; - relativePositionToGlobal (juce_recentMouseDownX[0], juce_recentMouseDownY[0]); - juce_MouseHasMovedSignificantlySincePressed = false; - - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - Component::componentUnderMouse->internalMouseDown (x, y); - } - } -} - -void ComponentPeer::handleMouseDrag (int x, int y, const int64 time) -{ - updateCurrentModifiers(); - - if (Component::componentUnderMouse != 0) - { - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - - Component::componentUnderMouse->internalMouseDrag (x, y, time); - } -} - -void ComponentPeer::handleMouseUp (const int oldModifiers, int x, int y, const int64 time) -{ - updateCurrentModifiers(); - - int numMouseButtonsDown = 0; - - if ((oldModifiers & ModifierKeys::leftButtonModifier) != 0) - ++numMouseButtonsDown; - - if ((oldModifiers & ModifierKeys::rightButtonModifier) != 0) - ++numMouseButtonsDown; - - if ((oldModifiers & ModifierKeys::middleButtonModifier) != 0) - ++numMouseButtonsDown; - - if (numMouseButtonsDown == 1) - { - const ComponentDeletionWatcher deletionChecker (component); - Component* c = component->getComponentAt (x, y); - - if (c != Component::componentUnderMouse) - { - const int oldX = x; - const int oldY = y; - - if (Component::componentUnderMouse != 0) - { - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - Component::componentUnderMouse->internalMouseUp (oldModifiers, x, y, time); - x = oldX; - y = oldY; - - if (Component::componentUnderMouse != 0) - Component::componentUnderMouse->internalMouseExit (x, y, time); - - if (deletionChecker.hasBeenDeleted()) - return; - - c = component->getComponentAt (oldX, oldY); - } - - Component::componentUnderMouse = c; - - if (Component::componentUnderMouse != 0) - { - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - Component::componentUnderMouse->internalMouseEnter (x, y, time); - } - } - else - { - if (Component::componentUnderMouse != 0) - { - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - Component::componentUnderMouse->internalMouseUp (oldModifiers, x, y, time); - } - } - } -} - -void ComponentPeer::handleMouseExit (int x, int y, const int64 time) -{ - jassert (component->isValidComponent()); - updateCurrentModifiers(); - - if (Component::componentUnderMouse != 0) - { - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - - Component::componentUnderMouse->internalMouseExit (x, y, time); - Component::componentUnderMouse = 0; - } -} - -void ComponentPeer::handleMouseWheel (const int amountX, const int amountY, const int64 time) -{ - updateCurrentModifiers(); - - if (Component::componentUnderMouse != 0) - Component::componentUnderMouse->internalMouseWheel (amountX, amountY, time); -} - -void ComponentPeer::sendFakeMouseMove() throw() -{ - if ((! fakeMouseMessageSent) - && component->flags.hasHeavyweightPeerFlag - && ! ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown()) - { - if (! isMinimised()) - { - int realX, realY, realW, realH; - getBounds (realX, realY, realW, realH); - - component->bounds_.setBounds (realX, realY, realW, realH); - } - - int x, y; - component->getMouseXYRelative (x, y); - - if (((unsigned int) x) < (unsigned int) component->getWidth() - && ((unsigned int) y) < (unsigned int) component->getHeight() - && contains (x, y, false)) - { - postMessage (new Message (fakeMouseMoveMessage, x, y, 0)); - } - - fakeMouseMessageSent = true; - } -} - -void ComponentPeer::handleMessage (const Message& message) -{ - if (message.intParameter1 == fakeMouseMoveMessage) - { - if (! ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown()) - handleMouseMove (message.intParameter2, - message.intParameter3, - Time::currentTimeMillis()); - } -} - -//============================================================================== -void ComponentPeer::handlePaint (LowLevelGraphicsContext& contextToPaintTo) -{ - Graphics g (&contextToPaintTo); - -#if JUCE_ENABLE_REPAINT_DEBUGGING - g.saveState(); -#endif - - JUCE_TRY - { - component->paintEntireComponent (g); - } - JUCE_CATCH_EXCEPTION - -#if JUCE_ENABLE_REPAINT_DEBUGGING - // enabling this code will fill all areas that get repainted with a colour overlay, to show - // clearly when things are being repainted. - { - g.restoreState(); - - g.fillAll (Colour ((uint8) Random::getSystemRandom().nextInt (255), - (uint8) Random::getSystemRandom().nextInt (255), - (uint8) Random::getSystemRandom().nextInt (255), - (uint8) 0x50)); - } -#endif -} - -bool ComponentPeer::handleKeyPress (const int keyCode, - const juce_wchar textCharacter) -{ - updateCurrentModifiers(); - - Component* target = Component::currentlyFocusedComponent->isValidComponent() - ? Component::currentlyFocusedComponent - : component; - - if (target->isCurrentlyBlockedByAnotherModalComponent()) - { - Component* const currentModalComp = Component::getCurrentlyModalComponent(); - - if (currentModalComp != 0) - target = currentModalComp; - } - - const KeyPress keyInfo (keyCode, - ModifierKeys::getCurrentModifiers().getRawFlags() - & ModifierKeys::allKeyboardModifiers, - textCharacter); - - bool keyWasUsed = false; - - while (target != 0) - { - const ComponentDeletionWatcher deletionChecker (target); - - if (target->keyListeners_ != 0) - { - for (int i = target->keyListeners_->size(); --i >= 0;) - { - keyWasUsed = ((KeyListener*) target->keyListeners_->getUnchecked(i))->keyPressed (keyInfo, target); - - if (keyWasUsed || deletionChecker.hasBeenDeleted()) - return keyWasUsed; - - i = jmin (i, target->keyListeners_->size()); - } - } - - keyWasUsed = target->keyPressed (keyInfo); - - if (keyWasUsed || deletionChecker.hasBeenDeleted()) - break; - - if (keyInfo.isKeyCode (KeyPress::tabKey) && Component::getCurrentlyFocusedComponent() != 0) - { - Component::getCurrentlyFocusedComponent() - ->moveKeyboardFocusToSibling (! keyInfo.getModifiers().isShiftDown()); - - keyWasUsed = true; - break; - } - - target = target->parentComponent_; - } - - return keyWasUsed; -} - -bool ComponentPeer::handleKeyUpOrDown() -{ - updateCurrentModifiers(); - - Component* target = Component::currentlyFocusedComponent->isValidComponent() - ? Component::currentlyFocusedComponent - : component; - - if (target->isCurrentlyBlockedByAnotherModalComponent()) - { - Component* const currentModalComp = Component::getCurrentlyModalComponent(); - - if (currentModalComp != 0) - target = currentModalComp; - } - - bool keyWasUsed = false; - - while (target != 0) - { - const ComponentDeletionWatcher deletionChecker (target); - - keyWasUsed = target->keyStateChanged(); - - if (keyWasUsed || deletionChecker.hasBeenDeleted()) - break; - - if (target->keyListeners_ != 0) - { - for (int i = target->keyListeners_->size(); --i >= 0;) - { - keyWasUsed = ((KeyListener*) target->keyListeners_->getUnchecked(i))->keyStateChanged (target); - - if (keyWasUsed || deletionChecker.hasBeenDeleted()) - return keyWasUsed; - - i = jmin (i, target->keyListeners_->size()); - } - } - - target = target->parentComponent_; - } - - return keyWasUsed; -} - -void ComponentPeer::handleModifierKeysChange() -{ - updateCurrentModifiers(); - - Component* target = Component::getComponentUnderMouse(); - - if (target == 0) - target = Component::getCurrentlyFocusedComponent(); - - if (target == 0) - target = component; - - if (target->isValidComponent()) - target->internalModifierKeysChanged(); -} - -//============================================================================== -void ComponentPeer::handleBroughtToFront() -{ - updateCurrentModifiers(); - - if (component != 0) - component->internalBroughtToFront(); -} - -void ComponentPeer::setConstrainer (ComponentBoundsConstrainer* const newConstrainer) throw() -{ - constrainer = newConstrainer; -} - -void ComponentPeer::handleMovedOrResized() -{ - jassert (component->isValidComponent()); - updateCurrentModifiers(); - - const bool nowMinimised = isMinimised(); - - if (component->flags.hasHeavyweightPeerFlag && ! nowMinimised) - { - const ComponentDeletionWatcher deletionChecker (component); - - int realX, realY, realW, realH; - getBounds (realX, realY, realW, realH); - - const bool wasMoved = (component->getX() != realX || component->getY() != realY); - const bool wasResized = (component->getWidth() != realW || component->getHeight() != realH); - - if (wasMoved || wasResized) - { - component->bounds_.setBounds (realX, realY, realW, realH); - - if (wasResized) - component->repaint(); - - component->sendMovedResizedMessages (wasMoved, wasResized); - - if (deletionChecker.hasBeenDeleted()) - return; - } - } - - if (isWindowMinimised != nowMinimised) - { - isWindowMinimised = nowMinimised; - component->minimisationStateChanged (nowMinimised); - component->sendVisibilityChangeMessage(); - } - - if (! isFullScreen()) - lastNonFullscreenBounds = component->getBounds(); -} - -void ComponentPeer::handleFocusGain() -{ - updateCurrentModifiers(); - - if (component->isParentOf (lastFocusedComponent)) - { - Component::currentlyFocusedComponent = lastFocusedComponent; - Desktop::getInstance().triggerFocusCallback(); - lastFocusedComponent->internalFocusGain (Component::focusChangedDirectly); - } - else - { - if (! component->isCurrentlyBlockedByAnotherModalComponent()) - { - component->grabKeyboardFocus(); - } - else - { - Component* const currentModalComp = Component::getCurrentlyModalComponent(); - - if (currentModalComp != 0) - currentModalComp->toFront (! currentModalComp->hasKeyboardFocus (true)); - } - } -} - -void ComponentPeer::handleFocusLoss() -{ - updateCurrentModifiers(); - - if (component->hasKeyboardFocus (true)) - { - lastFocusedComponent = Component::currentlyFocusedComponent; - - if (lastFocusedComponent != 0) - { - Component::currentlyFocusedComponent = 0; - Desktop::getInstance().triggerFocusCallback(); - lastFocusedComponent->internalFocusLoss (Component::focusChangedByMouseClick); - } - } -} - -Component* ComponentPeer::getLastFocusedSubcomponent() const throw() -{ - return (component->isParentOf (lastFocusedComponent) && lastFocusedComponent->isShowing()) - ? lastFocusedComponent - : component; -} - -void ComponentPeer::handleScreenSizeChange() -{ - updateCurrentModifiers(); - - component->parentSizeChanged(); - handleMovedOrResized(); -} - -void ComponentPeer::setNonFullScreenBounds (const Rectangle& newBounds) throw() -{ - lastNonFullscreenBounds = newBounds; -} - -const Rectangle& ComponentPeer::getNonFullScreenBounds() const throw() -{ - return lastNonFullscreenBounds; -} - -//============================================================================== -static FileDragAndDropTarget* findDragAndDropTarget (Component* c, - const StringArray& files, - FileDragAndDropTarget* const lastOne) -{ - while (c != 0) - { - FileDragAndDropTarget* const t = dynamic_cast (c); - - if (t != 0 && (t == lastOne || t->isInterestedInFileDrag (files))) - return t; - - c = c->getParentComponent(); - } - - return 0; -} - -void ComponentPeer::handleFileDragMove (const StringArray& files, int x, int y) -{ - updateCurrentModifiers(); - - FileDragAndDropTarget* lastTarget = 0; - - if (dragAndDropTargetComponent != 0 && ! dragAndDropTargetComponent->hasBeenDeleted()) - lastTarget = const_cast (dynamic_cast (dragAndDropTargetComponent->getComponent())); - - FileDragAndDropTarget* newTarget = 0; - - Component* const compUnderMouse = component->getComponentAt (x, y); - - if (compUnderMouse != lastDragAndDropCompUnderMouse) - { - lastDragAndDropCompUnderMouse = compUnderMouse; - newTarget = findDragAndDropTarget (compUnderMouse, files, lastTarget); - - if (newTarget != lastTarget) - { - if (lastTarget != 0) - lastTarget->fileDragExit (files); - - deleteAndZero (dragAndDropTargetComponent); - - if (newTarget != 0) - { - Component* const targetComp = dynamic_cast (newTarget); - int mx = x, my = y; - component->relativePositionToOtherComponent (targetComp, mx, my); - - dragAndDropTargetComponent = new ComponentDeletionWatcher (dynamic_cast (newTarget)); - newTarget->fileDragEnter (files, mx, my); - } - } - } - else - { - newTarget = lastTarget; - } - - if (newTarget != 0) - { - Component* const targetComp = dynamic_cast (newTarget); - component->relativePositionToOtherComponent (targetComp, x, y); - - newTarget->fileDragMove (files, x, y); - } -} - -void ComponentPeer::handleFileDragExit (const StringArray& files) -{ - handleFileDragMove (files, -1, -1); - - jassert (dragAndDropTargetComponent == 0); - lastDragAndDropCompUnderMouse = 0; -} - -void ComponentPeer::handleFileDragDrop (const StringArray& files, int x, int y) -{ - handleFileDragMove (files, x, y); - - if (dragAndDropTargetComponent != 0 && ! dragAndDropTargetComponent->hasBeenDeleted()) - { - FileDragAndDropTarget* const target = const_cast (dynamic_cast (dragAndDropTargetComponent->getComponent())); - - deleteAndZero (dragAndDropTargetComponent); - lastDragAndDropCompUnderMouse = 0; - - if (target != 0) - { - Component* const targetComp = dynamic_cast (target); - - if (targetComp->isCurrentlyBlockedByAnotherModalComponent()) - { - targetComp->internalModalInputAttempt(); - - if (targetComp->isCurrentlyBlockedByAnotherModalComponent()) - return; - } - - component->relativePositionToOtherComponent (targetComp, x, y); - target->filesDropped (files, x, y); - } - } -} - -//============================================================================== -void ComponentPeer::handleUserClosingWindow() -{ - updateCurrentModifiers(); - - component->userTriedToCloseWindow(); -} - -//============================================================================== -void ComponentPeer::clearMaskedRegion() throw() -{ - maskedRegion.clear(); -} - -void ComponentPeer::addMaskedRegion (int x, int y, int w, int h) throw() -{ - maskedRegion.add (x, y, w, h); -} - - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + JUCE is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../../../../juce_core/basics/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + +#include "../../../application/juce_Application.h" +#include "../juce_Component.h" +#include "../juce_ComponentDeletionWatcher.h" +#include "../juce_Desktop.h" +#include "../../../events/juce_MessageManager.h" +#include "../../../../juce_core/basics/juce_Time.h" +#include "../../../../juce_core/basics/juce_Random.h" +#include "../layout/juce_ComponentBoundsConstrainer.h" +#include "../mouse/juce_FileDragAndDropTarget.h" + +//#define JUCE_ENABLE_REPAINT_DEBUGGING 1 + + +//============================================================================== +// these are over in juce_component.cpp +extern int64 juce_recentMouseDownTimes[4]; +extern int juce_recentMouseDownX [4]; +extern int juce_recentMouseDownY [4]; +extern Component* juce_recentMouseDownComponent [4]; +extern int juce_LastMousePosX; +extern int juce_LastMousePosY; +extern int juce_MouseClickCounter; +extern bool juce_MouseHasMovedSignificantlySincePressed; + +static const int fakeMouseMoveMessage = 0x7fff00ff; + +static VoidArray heavyweightPeers (4); + + +//============================================================================== +ComponentPeer::ComponentPeer (Component* const component_, + const int styleFlags_) throw() + : component (component_), + styleFlags (styleFlags_), + lastPaintTime (0), + constrainer (0), + lastFocusedComponent (0), + dragAndDropTargetComponent (0), + lastDragAndDropCompUnderMouse (0), + fakeMouseMessageSent (false), + isWindowMinimised (false) +{ + heavyweightPeers.add (this); +} + +ComponentPeer::~ComponentPeer() +{ + heavyweightPeers.removeValue (this); + delete dragAndDropTargetComponent; + + Desktop::getInstance().triggerFocusCallback(); +} + +//============================================================================== +int ComponentPeer::getNumPeers() throw() +{ + return heavyweightPeers.size(); +} + +ComponentPeer* ComponentPeer::getPeer (const int index) throw() +{ + return (ComponentPeer*) heavyweightPeers [index]; +} + +ComponentPeer* ComponentPeer::getPeerFor (const Component* const component) throw() +{ + for (int i = heavyweightPeers.size(); --i >= 0;) + { + ComponentPeer* const peer = (ComponentPeer*) heavyweightPeers.getUnchecked(i); + + if (peer->getComponent() == component) + return peer; + } + + return 0; +} + +bool ComponentPeer::isValidPeer (const ComponentPeer* const peer) throw() +{ + return heavyweightPeers.contains (const_cast (peer)); +} + +void ComponentPeer::updateCurrentModifiers() throw() +{ + ModifierKeys::updateCurrentModifiers(); +} + +//============================================================================== +void ComponentPeer::handleMouseEnter (int x, int y, const int64 time) +{ + jassert (component->isValidComponent()); + updateCurrentModifiers(); + + Component* c = component->getComponentAt (x, y); + const ComponentDeletionWatcher deletionChecker (component); + + if (c != Component::componentUnderMouse && Component::componentUnderMouse != 0) + { + jassert (Component::componentUnderMouse->isValidComponent()); + + const int oldX = x; + const int oldY = y; + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + Component::componentUnderMouse->internalMouseExit (x, y, time); + Component::componentUnderMouse = 0; + + if (deletionChecker.hasBeenDeleted()) + return; + + c = component->getComponentAt (oldX, oldY); + } + + Component::componentUnderMouse = c; + + if (Component::componentUnderMouse != 0) + { + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + Component::componentUnderMouse->internalMouseEnter (x, y, time); + } +} + +void ComponentPeer::handleMouseMove (int x, int y, const int64 time) +{ + jassert (component->isValidComponent()); + updateCurrentModifiers(); + + fakeMouseMessageSent = false; + + const ComponentDeletionWatcher deletionChecker (component); + Component* c = component->getComponentAt (x, y); + + if (c != Component::componentUnderMouse) + { + const int oldX = x; + const int oldY = y; + + if (Component::componentUnderMouse != 0) + { + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + Component::componentUnderMouse->internalMouseExit (x, y, time); + x = oldX; + y = oldY; + + Component::componentUnderMouse = 0; + + if (deletionChecker.hasBeenDeleted()) + return; // if this window has just been deleted.. + + c = component->getComponentAt (x, y); + } + + Component::componentUnderMouse = c; + + if (c != 0) + { + component->relativePositionToOtherComponent (c, x, y); + c->internalMouseEnter (x, y, time); + x = oldX; + y = oldY; + + if (deletionChecker.hasBeenDeleted()) + return; // if this window has just been deleted.. + } + } + + if (Component::componentUnderMouse != 0) + { + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + Component::componentUnderMouse->internalMouseMove (x, y, time); + } +} + +void ComponentPeer::handleMouseDown (int x, int y, const int64 time) +{ + ++juce_MouseClickCounter; + + updateCurrentModifiers(); + + int numMouseButtonsDown = 0; + + if (ModifierKeys::getCurrentModifiers().isLeftButtonDown()) + ++numMouseButtonsDown; + + if (ModifierKeys::getCurrentModifiers().isRightButtonDown()) + ++numMouseButtonsDown; + + if (ModifierKeys::getCurrentModifiers().isMiddleButtonDown()) + ++numMouseButtonsDown; + + if (numMouseButtonsDown == 1) + { + Component::componentUnderMouse = component->getComponentAt (x, y); + + if (Component::componentUnderMouse != 0) + { + // can't set these in the mouseDownInt() method, because it's re-entrant, so do it here.. + + for (int i = numElementsInArray (juce_recentMouseDownTimes); --i > 0;) + { + juce_recentMouseDownTimes [i] = juce_recentMouseDownTimes [i - 1]; + juce_recentMouseDownX [i] = juce_recentMouseDownX [i - 1]; + juce_recentMouseDownY [i] = juce_recentMouseDownY [i - 1]; + juce_recentMouseDownComponent [i] = juce_recentMouseDownComponent [i - 1]; + } + + juce_recentMouseDownTimes[0] = time; + juce_recentMouseDownX[0] = x; + juce_recentMouseDownY[0] = y; + juce_recentMouseDownComponent[0] = Component::componentUnderMouse; + relativePositionToGlobal (juce_recentMouseDownX[0], juce_recentMouseDownY[0]); + juce_MouseHasMovedSignificantlySincePressed = false; + + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + Component::componentUnderMouse->internalMouseDown (x, y); + } + } +} + +void ComponentPeer::handleMouseDrag (int x, int y, const int64 time) +{ + updateCurrentModifiers(); + + if (Component::componentUnderMouse != 0) + { + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + + Component::componentUnderMouse->internalMouseDrag (x, y, time); + } +} + +void ComponentPeer::handleMouseUp (const int oldModifiers, int x, int y, const int64 time) +{ + updateCurrentModifiers(); + + int numMouseButtonsDown = 0; + + if ((oldModifiers & ModifierKeys::leftButtonModifier) != 0) + ++numMouseButtonsDown; + + if ((oldModifiers & ModifierKeys::rightButtonModifier) != 0) + ++numMouseButtonsDown; + + if ((oldModifiers & ModifierKeys::middleButtonModifier) != 0) + ++numMouseButtonsDown; + + if (numMouseButtonsDown == 1) + { + const ComponentDeletionWatcher deletionChecker (component); + Component* c = component->getComponentAt (x, y); + + if (c != Component::componentUnderMouse) + { + const int oldX = x; + const int oldY = y; + + if (Component::componentUnderMouse != 0) + { + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + Component::componentUnderMouse->internalMouseUp (oldModifiers, x, y, time); + x = oldX; + y = oldY; + + if (Component::componentUnderMouse != 0) + Component::componentUnderMouse->internalMouseExit (x, y, time); + + if (deletionChecker.hasBeenDeleted()) + return; + + c = component->getComponentAt (oldX, oldY); + } + + Component::componentUnderMouse = c; + + if (Component::componentUnderMouse != 0) + { + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + Component::componentUnderMouse->internalMouseEnter (x, y, time); + } + } + else + { + if (Component::componentUnderMouse != 0) + { + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + Component::componentUnderMouse->internalMouseUp (oldModifiers, x, y, time); + } + } + } +} + +void ComponentPeer::handleMouseExit (int x, int y, const int64 time) +{ + jassert (component->isValidComponent()); + updateCurrentModifiers(); + + if (Component::componentUnderMouse != 0) + { + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + + Component::componentUnderMouse->internalMouseExit (x, y, time); + Component::componentUnderMouse = 0; + } +} + +void ComponentPeer::handleMouseWheel (const int amountX, const int amountY, const int64 time) +{ + updateCurrentModifiers(); + + if (Component::componentUnderMouse != 0) + Component::componentUnderMouse->internalMouseWheel (amountX, amountY, time); +} + +void ComponentPeer::sendFakeMouseMove() throw() +{ + if ((! fakeMouseMessageSent) + && component->flags.hasHeavyweightPeerFlag + && ! ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown()) + { + if (! isMinimised()) + { + int realX, realY, realW, realH; + getBounds (realX, realY, realW, realH); + + component->bounds_.setBounds (realX, realY, realW, realH); + } + + int x, y; + component->getMouseXYRelative (x, y); + + if (((unsigned int) x) < (unsigned int) component->getWidth() + && ((unsigned int) y) < (unsigned int) component->getHeight() + && contains (x, y, false)) + { + postMessage (new Message (fakeMouseMoveMessage, x, y, 0)); + } + + fakeMouseMessageSent = true; + } +} + +void ComponentPeer::handleMessage (const Message& message) +{ + if (message.intParameter1 == fakeMouseMoveMessage) + { + if (! ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown()) + handleMouseMove (message.intParameter2, + message.intParameter3, + Time::currentTimeMillis()); + } +} + +//============================================================================== +void ComponentPeer::handlePaint (LowLevelGraphicsContext& contextToPaintTo) +{ + Graphics g (&contextToPaintTo); + +#if JUCE_ENABLE_REPAINT_DEBUGGING + g.saveState(); +#endif + + JUCE_TRY + { + component->paintEntireComponent (g); + } + JUCE_CATCH_EXCEPTION + +#if JUCE_ENABLE_REPAINT_DEBUGGING + // enabling this code will fill all areas that get repainted with a colour overlay, to show + // clearly when things are being repainted. + { + g.restoreState(); + + g.fillAll (Colour ((uint8) Random::getSystemRandom().nextInt (255), + (uint8) Random::getSystemRandom().nextInt (255), + (uint8) Random::getSystemRandom().nextInt (255), + (uint8) 0x50)); + } +#endif +} + +bool ComponentPeer::handleKeyPress (const int keyCode, + const juce_wchar textCharacter) +{ + updateCurrentModifiers(); + + Component* target = Component::currentlyFocusedComponent->isValidComponent() + ? Component::currentlyFocusedComponent + : component; + + if (target->isCurrentlyBlockedByAnotherModalComponent()) + { + Component* const currentModalComp = Component::getCurrentlyModalComponent(); + + if (currentModalComp != 0) + target = currentModalComp; + } + + const KeyPress keyInfo (keyCode, + ModifierKeys::getCurrentModifiers().getRawFlags() + & ModifierKeys::allKeyboardModifiers, + textCharacter); + + bool keyWasUsed = false; + + while (target != 0) + { + const ComponentDeletionWatcher deletionChecker (target); + + if (target->keyListeners_ != 0) + { + for (int i = target->keyListeners_->size(); --i >= 0;) + { + keyWasUsed = ((KeyListener*) target->keyListeners_->getUnchecked(i))->keyPressed (keyInfo, target); + + if (keyWasUsed || deletionChecker.hasBeenDeleted()) + return keyWasUsed; + + i = jmin (i, target->keyListeners_->size()); + } + } + + keyWasUsed = target->keyPressed (keyInfo); + + if (keyWasUsed || deletionChecker.hasBeenDeleted()) + break; + + if (keyInfo.isKeyCode (KeyPress::tabKey) && Component::getCurrentlyFocusedComponent() != 0) + { + Component::getCurrentlyFocusedComponent() + ->moveKeyboardFocusToSibling (! keyInfo.getModifiers().isShiftDown()); + + keyWasUsed = true; + break; + } + + target = target->parentComponent_; + } + + return keyWasUsed; +} + +bool ComponentPeer::handleKeyUpOrDown() +{ + updateCurrentModifiers(); + + Component* target = Component::currentlyFocusedComponent->isValidComponent() + ? Component::currentlyFocusedComponent + : component; + + if (target->isCurrentlyBlockedByAnotherModalComponent()) + { + Component* const currentModalComp = Component::getCurrentlyModalComponent(); + + if (currentModalComp != 0) + target = currentModalComp; + } + + bool keyWasUsed = false; + + while (target != 0) + { + const ComponentDeletionWatcher deletionChecker (target); + + keyWasUsed = target->keyStateChanged(); + + if (keyWasUsed || deletionChecker.hasBeenDeleted()) + break; + + if (target->keyListeners_ != 0) + { + for (int i = target->keyListeners_->size(); --i >= 0;) + { + keyWasUsed = ((KeyListener*) target->keyListeners_->getUnchecked(i))->keyStateChanged (target); + + if (keyWasUsed || deletionChecker.hasBeenDeleted()) + return keyWasUsed; + + i = jmin (i, target->keyListeners_->size()); + } + } + + target = target->parentComponent_; + } + + return keyWasUsed; +} + +void ComponentPeer::handleModifierKeysChange() +{ + updateCurrentModifiers(); + + Component* target = Component::getComponentUnderMouse(); + + if (target == 0) + target = Component::getCurrentlyFocusedComponent(); + + if (target == 0) + target = component; + + if (target->isValidComponent()) + target->internalModifierKeysChanged(); +} + +//============================================================================== +void ComponentPeer::handleBroughtToFront() +{ + updateCurrentModifiers(); + + if (component != 0) + component->internalBroughtToFront(); +} + +void ComponentPeer::setConstrainer (ComponentBoundsConstrainer* const newConstrainer) throw() +{ + constrainer = newConstrainer; +} + +void ComponentPeer::handleMovedOrResized() +{ + jassert (component->isValidComponent()); + updateCurrentModifiers(); + + const bool nowMinimised = isMinimised(); + + if (component->flags.hasHeavyweightPeerFlag && ! nowMinimised) + { + const ComponentDeletionWatcher deletionChecker (component); + + int realX, realY, realW, realH; + getBounds (realX, realY, realW, realH); + + const bool wasMoved = (component->getX() != realX || component->getY() != realY); + const bool wasResized = (component->getWidth() != realW || component->getHeight() != realH); + + if (wasMoved || wasResized) + { + component->bounds_.setBounds (realX, realY, realW, realH); + + if (wasResized) + component->repaint(); + + component->sendMovedResizedMessages (wasMoved, wasResized); + + if (deletionChecker.hasBeenDeleted()) + return; + } + } + + if (isWindowMinimised != nowMinimised) + { + isWindowMinimised = nowMinimised; + component->minimisationStateChanged (nowMinimised); + component->sendVisibilityChangeMessage(); + } + + if (! isFullScreen()) + lastNonFullscreenBounds = component->getBounds(); +} + +void ComponentPeer::handleFocusGain() +{ + updateCurrentModifiers(); + + if (component->isParentOf (lastFocusedComponent)) + { + Component::currentlyFocusedComponent = lastFocusedComponent; + Desktop::getInstance().triggerFocusCallback(); + lastFocusedComponent->internalFocusGain (Component::focusChangedDirectly); + } + else + { + if (! component->isCurrentlyBlockedByAnotherModalComponent()) + { + component->grabKeyboardFocus(); + } + else + { + Component* const currentModalComp = Component::getCurrentlyModalComponent(); + + if (currentModalComp != 0) + currentModalComp->toFront (! currentModalComp->hasKeyboardFocus (true)); + } + } +} + +void ComponentPeer::handleFocusLoss() +{ + updateCurrentModifiers(); + + if (component->hasKeyboardFocus (true)) + { + lastFocusedComponent = Component::currentlyFocusedComponent; + + if (lastFocusedComponent != 0) + { + Component::currentlyFocusedComponent = 0; + Desktop::getInstance().triggerFocusCallback(); + lastFocusedComponent->internalFocusLoss (Component::focusChangedByMouseClick); + } + } +} + +Component* ComponentPeer::getLastFocusedSubcomponent() const throw() +{ + return (component->isParentOf (lastFocusedComponent) && lastFocusedComponent->isShowing()) + ? lastFocusedComponent + : component; +} + +void ComponentPeer::handleScreenSizeChange() +{ + updateCurrentModifiers(); + + component->parentSizeChanged(); + handleMovedOrResized(); +} + +void ComponentPeer::setNonFullScreenBounds (const Rectangle& newBounds) throw() +{ + lastNonFullscreenBounds = newBounds; +} + +const Rectangle& ComponentPeer::getNonFullScreenBounds() const throw() +{ + return lastNonFullscreenBounds; +} + +//============================================================================== +static FileDragAndDropTarget* findDragAndDropTarget (Component* c, + const StringArray& files, + FileDragAndDropTarget* const lastOne) +{ + while (c != 0) + { + FileDragAndDropTarget* const t = dynamic_cast (c); + + if (t != 0 && (t == lastOne || t->isInterestedInFileDrag (files))) + return t; + + c = c->getParentComponent(); + } + + return 0; +} + +void ComponentPeer::handleFileDragMove (const StringArray& files, int x, int y) +{ + updateCurrentModifiers(); + + FileDragAndDropTarget* lastTarget = 0; + + if (dragAndDropTargetComponent != 0 && ! dragAndDropTargetComponent->hasBeenDeleted()) + lastTarget = const_cast (dynamic_cast (dragAndDropTargetComponent->getComponent())); + + FileDragAndDropTarget* newTarget = 0; + + Component* const compUnderMouse = component->getComponentAt (x, y); + + if (compUnderMouse != lastDragAndDropCompUnderMouse) + { + lastDragAndDropCompUnderMouse = compUnderMouse; + newTarget = findDragAndDropTarget (compUnderMouse, files, lastTarget); + + if (newTarget != lastTarget) + { + if (lastTarget != 0) + lastTarget->fileDragExit (files); + + deleteAndZero (dragAndDropTargetComponent); + + if (newTarget != 0) + { + Component* const targetComp = dynamic_cast (newTarget); + int mx = x, my = y; + component->relativePositionToOtherComponent (targetComp, mx, my); + + dragAndDropTargetComponent = new ComponentDeletionWatcher (dynamic_cast (newTarget)); + newTarget->fileDragEnter (files, mx, my); + } + } + } + else + { + newTarget = lastTarget; + } + + if (newTarget != 0) + { + Component* const targetComp = dynamic_cast (newTarget); + component->relativePositionToOtherComponent (targetComp, x, y); + + newTarget->fileDragMove (files, x, y); + } +} + +void ComponentPeer::handleFileDragExit (const StringArray& files) +{ + handleFileDragMove (files, -1, -1); + + jassert (dragAndDropTargetComponent == 0); + lastDragAndDropCompUnderMouse = 0; +} + +void ComponentPeer::handleFileDragDrop (const StringArray& files, int x, int y) +{ + handleFileDragMove (files, x, y); + + if (dragAndDropTargetComponent != 0 && ! dragAndDropTargetComponent->hasBeenDeleted()) + { + FileDragAndDropTarget* const target = const_cast (dynamic_cast (dragAndDropTargetComponent->getComponent())); + + deleteAndZero (dragAndDropTargetComponent); + lastDragAndDropCompUnderMouse = 0; + + if (target != 0) + { + Component* const targetComp = dynamic_cast (target); + + if (targetComp->isCurrentlyBlockedByAnotherModalComponent()) + { + targetComp->internalModalInputAttempt(); + + if (targetComp->isCurrentlyBlockedByAnotherModalComponent()) + return; + } + + component->relativePositionToOtherComponent (targetComp, x, y); + target->filesDropped (files, x, y); + } + } +} + +//============================================================================== +void ComponentPeer::handleUserClosingWindow() +{ + updateCurrentModifiers(); + + component->userTriedToCloseWindow(); +} + +//============================================================================== +void ComponentPeer::clearMaskedRegion() throw() +{ + maskedRegion.clear(); +} + +void ComponentPeer::addMaskedRegion (int x, int y, int w, int h) throw() +{ + maskedRegion.add (x, y, w, h); +} + + +END_JUCE_NAMESPACE diff --git a/src/juce_core/containers/juce_BitArray.cpp b/src/juce_core/containers/juce_BitArray.cpp index 8029e2f979..3671d7b478 100644 --- a/src/juce_core/containers/juce_BitArray.cpp +++ b/src/juce_core/containers/juce_BitArray.cpp @@ -1,957 +1,957 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - JUCE is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../basics/juce_StandardHeader.h" - -BEGIN_JUCE_NAMESPACE - - -#include "juce_BitArray.h" -#include "juce_MemoryBlock.h" -#include "../basics/juce_Random.h" - - -//============================================================================== -BitArray::BitArray() throw() - : numValues (4), - highestBit (-1), - negative (false) -{ - values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); -} - -BitArray::BitArray (const int value) throw() - : numValues (4), - highestBit (31), - negative (value < 0) -{ - values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); - values[0] = abs (value); - highestBit = getHighestBit(); -} - -BitArray::BitArray (int64 value) throw() - : numValues (4), - highestBit (63), - negative (value < 0) -{ - values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); - - if (value < 0) - value = -value; - - values[0] = (unsigned int) value; - values[1] = (unsigned int) (value >> 32); - highestBit = getHighestBit(); -} - -BitArray::BitArray (const unsigned int value) throw() - : numValues (4), - highestBit (31), - negative (false) -{ - values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); - values[0] = value; - highestBit = getHighestBit(); -} - -BitArray::BitArray (const BitArray& other) throw() - : numValues (jmax (4, (other.highestBit >> 5) + 1)), - highestBit (other.getHighestBit()), - negative (other.negative) -{ - const int bytes = sizeof (unsigned int) * (numValues + 1); - values = (unsigned int*) juce_malloc (bytes); - memcpy (values, other.values, bytes); -} - -BitArray::~BitArray() throw() -{ - juce_free (values); -} - -const BitArray& BitArray::operator= (const BitArray& other) throw() -{ - if (this != &other) - { - juce_free (values); - - highestBit = other.getHighestBit(); - numValues = jmax (4, (highestBit >> 5) + 1); - negative = other.negative; - const int memSize = sizeof (unsigned int) * (numValues + 1); - values = (unsigned int*)juce_malloc (memSize); - memcpy (values, other.values, memSize); - } - - return *this; -} - -// result == 0 = the same -// result < 0 = this number is smaller -// result > 0 = this number is bigger -int BitArray::compare (const BitArray& other) const throw() -{ - if (isNegative() == other.isNegative()) - { - const int absComp = compareAbsolute (other); - return isNegative() ? -absComp : absComp; - } - else - { - return isNegative() ? -1 : 1; - } -} - -int BitArray::compareAbsolute (const BitArray& other) const throw() -{ - const int h1 = getHighestBit(); - const int h2 = other.getHighestBit(); - - if (h1 > h2) - return 1; - else if (h1 < h2) - return -1; - - for (int i = (h1 >> 5) + 1; --i >= 0;) - if (values[i] != other.values[i]) - return (values[i] > other.values[i]) ? 1 : -1; - - return 0; -} - -bool BitArray::operator== (const BitArray& other) const throw() -{ - return compare (other) == 0; -} - -bool BitArray::operator!= (const BitArray& other) const throw() -{ - return compare (other) != 0; -} - -bool BitArray::operator[] (const int bit) const throw() -{ - return bit >= 0 && bit <= highestBit - && ((values [bit >> 5] & (1 << (bit & 31))) != 0); -} - -bool BitArray::isEmpty() const throw() -{ - return getHighestBit() < 0; -} - -void BitArray::clear() throw() -{ - if (numValues > 16) - { - juce_free (values); - numValues = 4; - values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); - } - else - { - zeromem (values, sizeof (unsigned int) * (numValues + 1)); - } - - highestBit = -1; - negative = false; -} - -void BitArray::setBit (const int bit) throw() -{ - if (bit >= 0) - { - if (bit > highestBit) - { - ensureSize (bit >> 5); - highestBit = bit; - } - - values [bit >> 5] |= (1 << (bit & 31)); - } -} - -void BitArray::setBit (const int bit, - const bool shouldBeSet) throw() -{ - if (shouldBeSet) - setBit (bit); - else - clearBit (bit); -} - -void BitArray::clearBit (const int bit) throw() -{ - if (bit >= 0 && bit <= highestBit) - values [bit >> 5] &= ~(1 << (bit & 31)); -} - -void BitArray::setRange (int startBit, - int numBits, - const bool shouldBeSet) throw() -{ - while (--numBits >= 0) - setBit (startBit++, shouldBeSet); -} - -void BitArray::insertBit (const int bit, - const bool shouldBeSet) throw() -{ - if (bit >= 0) - shiftBits (1, bit); - - setBit (bit, shouldBeSet); -} - -//============================================================================== -void BitArray::andWith (const BitArray& other) throw() -{ - // this operation will only work with the absolute values - jassert (isNegative() == other.isNegative()); - - int n = numValues; - - while (n > other.numValues) - values[--n] = 0; - - while (--n >= 0) - values[n] &= other.values[n]; - - if (other.highestBit < highestBit) - highestBit = other.highestBit; - - highestBit = getHighestBit(); -} - -void BitArray::orWith (const BitArray& other) throw() -{ - if (other.highestBit < 0) - return; - - // this operation will only work with the absolute values - jassert (isNegative() == other.isNegative()); - - ensureSize (other.highestBit >> 5); - - int n = (other.highestBit >> 5) + 1; - - while (--n >= 0) - values[n] |= other.values[n]; - - if (other.highestBit > highestBit) - highestBit = other.highestBit; - - highestBit = getHighestBit(); -} - -void BitArray::xorWith (const BitArray& other) throw() -{ - if (other.highestBit < 0) - return; - - // this operation will only work with the absolute values - jassert (isNegative() == other.isNegative()); - - ensureSize (other.highestBit >> 5); - - int n = (other.highestBit >> 5) + 1; - - while (--n >= 0) - values[n] ^= other.values[n]; - - if (other.highestBit > highestBit) - highestBit = other.highestBit; - - highestBit = getHighestBit(); -} - -//============================================================================== -void BitArray::add (const BitArray& other) throw() -{ - if (other.isNegative()) - { - BitArray o (other); - o.negate(); - subtract (o); - return; - } - - if (isNegative()) - { - if (compareAbsolute (other) < 0) - { - BitArray temp (*this); - temp.negate(); - *this = other; - subtract (temp); - } - else - { - negate(); - subtract (other); - negate(); - } - - return; - } - - if (other.highestBit > highestBit) - highestBit = other.highestBit; - - ++highestBit; - - const int numInts = (highestBit >> 5) + 1; - ensureSize (numInts); - - int64 remainder = 0; - - for (int i = 0; i <= numInts; ++i) - { - if (i < numValues) - remainder += values[i]; - - if (i < other.numValues) - remainder += other.values[i]; - - values[i] = (unsigned int) remainder; - remainder >>= 32; - } - - jassert (remainder == 0); - highestBit = getHighestBit(); -} - -void BitArray::subtract (const BitArray& other) throw() -{ - if (other.isNegative()) - { - BitArray o (other); - o.negate(); - add (o); - return; - } - - if (! isNegative()) - { - if (compareAbsolute (other) < 0) - { - BitArray temp (*this); - *this = other; - subtract (temp); - negate(); - return; - } - } - else - { - negate(); - add (other); - negate(); - return; - } - - const int numInts = (highestBit >> 5) + 1; - const int maxOtherInts = (other.highestBit >> 5) + 1; - int64 amountToSubtract = 0; - - for (int i = 0; i <= numInts; ++i) - { - if (i <= maxOtherInts) - amountToSubtract += (int64)other.values[i]; - - if (values[i] >= amountToSubtract) - { - values[i] = (unsigned int) (values[i] - amountToSubtract); - amountToSubtract = 0; - } - else - { - const int64 n = ((int64) values[i] + (((int64) 1) << 32)) - amountToSubtract; - values[i] = (unsigned int) n; - amountToSubtract = 1; - } - } -} - -void BitArray::multiplyBy (const BitArray& other) throw() -{ - BitArray total; - highestBit = getHighestBit(); - const bool wasNegative = isNegative(); - setNegative (false); - - for (int i = 0; i <= highestBit; ++i) - { - if (operator[](i)) - { - BitArray n (other); - n.setNegative (false); - n.shiftBits (i); - total.add (n); - } - } - - *this = total; - negative = wasNegative ^ other.isNegative(); -} - -void BitArray::divideBy (const BitArray& divisor, BitArray& remainder) throw() -{ - jassert (this != &remainder); // (can't handle passing itself in to get the remainder) - - const int divHB = divisor.getHighestBit(); - const int ourHB = getHighestBit(); - - if (divHB < 0 || ourHB < 0) - { - // division by zero - remainder.clear(); - clear(); - } - else - { - remainder = *this; - remainder.setNegative (false); - const bool wasNegative = isNegative(); - clear(); - - BitArray temp (divisor); - temp.setNegative (false); - - int leftShift = ourHB - divHB; - temp.shiftBits (leftShift); - - while (leftShift >= 0) - { - if (remainder.compareAbsolute (temp) >= 0) - { - remainder.subtract (temp); - setBit (leftShift); - } - - if (--leftShift >= 0) - temp.shiftBits (-1); - } - - negative = wasNegative ^ divisor.isNegative(); - remainder.setNegative (wasNegative); - } -} - -void BitArray::modulo (const BitArray& divisor) throw() -{ - BitArray remainder; - divideBy (divisor, remainder); - *this = remainder; -} - -static const BitArray simpleGCD (BitArray* m, BitArray* n) throw() -{ - while (! m->isEmpty()) - { - if (n->compareAbsolute (*m) > 0) - swapVariables (m, n); - - m->subtract (*n); - } - - return *n; -} - -const BitArray BitArray::findGreatestCommonDivisor (BitArray n) const throw() -{ - BitArray m (*this); - - while (! n.isEmpty()) - { - if (abs (m.getHighestBit() - n.getHighestBit()) <= 16) - return simpleGCD (&m, &n); - - BitArray temp1 (m), temp2; - temp1.divideBy (n, temp2); - - m = n; - n = temp2; - } - - return m; -} - -void BitArray::exponentModulo (const BitArray& exponent, - const BitArray& modulus) throw() -{ - BitArray exp (exponent); - exp.modulo (modulus); - - BitArray value (*this); - value.modulo (modulus); - - clear(); - setBit (0); - - while (! exp.isEmpty()) - { - if (exp [0]) - { - multiplyBy (value); - this->modulo (modulus); - } - - value.multiplyBy (value); - value.modulo (modulus); - - exp.shiftBits (-1); - } -} - -void BitArray::inverseModulo (const BitArray& modulus) throw() -{ - const BitArray one (1); - - if (modulus == one || modulus.isNegative()) - { - clear(); - return; - } - - if (isNegative() || compareAbsolute (modulus) >= 0) - this->modulo (modulus); - - if (*this == one) - return; - - if (! (*this)[0]) - { - // not invertible - clear(); - return; - } - - BitArray a1 (modulus); - BitArray a2 (*this); - BitArray b1 (modulus); - BitArray b2 (1); - - while (a2 != one) - { - BitArray temp1, temp2, multiplier (a1); - multiplier.divideBy (a2, temp1); - - temp1 = a2; - temp1.multiplyBy (multiplier); - temp2 = a1; - temp2.subtract (temp1); - a1 = a2; - a2 = temp2; - - temp1 = b2; - temp1.multiplyBy (multiplier); - temp2 = b1; - temp2.subtract (temp1); - b1 = b2; - b2 = temp2; - } - - while (b2.isNegative()) - b2.add (modulus); - - b2.modulo (modulus); - *this = b2; -} - -//============================================================================== -void BitArray::shiftBits (int bits, const int startBit) throw() -{ - if (highestBit < 0) - return; - - if (startBit > 0) - { - if (bits < 0) - { - // right shift - for (int i = startBit; i <= highestBit; ++i) - setBit (i, operator[] (i - bits)); - - highestBit = getHighestBit(); - } - else if (bits > 0) - { - // left shift - for (int i = highestBit + 1; --i >= startBit;) - setBit (i + bits, operator[] (i)); - - while (--bits >= 0) - clearBit (bits + startBit); - } - } - else - { - if (bits < 0) - { - // right shift - bits = -bits; - - if (bits > highestBit) - { - clear(); - } - else - { - const int wordsToMove = bits >> 5; - int top = 1 + (highestBit >> 5) - wordsToMove; - highestBit -= bits; - - if (wordsToMove > 0) - { - int i; - for (i = 0; i < top; ++i) - values [i] = values [i + wordsToMove]; - - for (i = 0; i < wordsToMove; ++i) - values [top + i] = 0; - - bits &= 31; - } - - if (bits != 0) - { - const int invBits = 32 - bits; - - --top; - for (int i = 0; i < top; ++i) - values[i] = (values[i] >> bits) | (values [i + 1] << invBits); - - values[top] = (values[top] >> bits); - } - - highestBit = getHighestBit(); - } - } - else if (bits > 0) - { - // left shift - ensureSize (((highestBit + bits) >> 5) + 1); - - const int wordsToMove = bits >> 5; - int top = 1 + (highestBit >> 5); - highestBit += bits; - - if (wordsToMove > 0) - { - int i; - for (i = top; --i >= 0;) - values [i + wordsToMove] = values [i]; - - for (i = 0; i < wordsToMove; ++i) - values [i] = 0; - - bits &= 31; - } - - if (bits != 0) - { - const int invBits = 32 - bits; - - for (int i = top + 1 + wordsToMove; --i > wordsToMove;) - values[i] = (values[i] << bits) | (values [i - 1] >> invBits); - - values [wordsToMove] = values [wordsToMove] << bits; - } - - highestBit = getHighestBit(); - } - } -} - -const BitArray BitArray::getBitRange (int startBit, int numBits) const throw() -{ - BitArray r; - numBits = jmin (numBits, getHighestBit() + 1 - startBit); - r.ensureSize (numBits >> 5); - r.highestBit = numBits; - - int i = 0; - while (numBits > 0) - { - r.values[i++] = getBitRangeAsInt (startBit, jmin (32, numBits)); - numBits -= 32; - startBit += 32; - } - - r.highestBit = r.getHighestBit(); - - return r; -} - -int BitArray::getBitRangeAsInt (const int startBit, int numBits) const throw() -{ - if (numBits > 32) - { - jassertfalse // use getBitRange() if you need more than 32 bits.. - numBits = 32; - } - - numBits = jmin (numBits, highestBit + 1 - startBit); - - if (numBits <= 0) - return 0; - - const int pos = startBit >> 5; - const int offset = startBit & 31; - const int endSpace = 32 - numBits; - - uint32 n = ((uint32) values [pos]) >> offset; - - if (offset > endSpace) - n |= ((uint32) values [pos + 1]) << (32 - offset); - - return (int) (n & (((uint32) 0xffffffff) >> endSpace)); -} - -void BitArray::setBitRangeAsInt (const int startBit, int numBits, unsigned int valueToSet) throw() -{ - if (numBits > 32) - { - jassertfalse - numBits = 32; - } - - for (int i = 0; i < numBits; ++i) - { - setBit (startBit + i, (valueToSet & 1) != 0); - valueToSet >>= 1; - } -} - -//============================================================================== -bool BitArray::isNegative() const throw() -{ - return negative && ! isEmpty(); -} - -void BitArray::setNegative (const bool neg) throw() -{ - negative = neg; -} - -void BitArray::negate() throw() -{ - negative = (! negative) && ! isEmpty(); -} - -int BitArray::countNumberOfSetBits() const throw() -{ - int total = 0; - - for (int i = (highestBit >> 5) + 1; --i >= 0;) - { - unsigned int n = values[i]; - - if (n == 0xffffffff) - { - total += 32; - } - else - { - while (n != 0) - { - total += (n & 1); - n >>= 1; - } - } - } - - return total; -} - -int BitArray::getHighestBit() const throw() -{ - for (int i = highestBit + 1; --i >= 0;) - if ((values [i >> 5] & (1 << (i & 31))) != 0) - return i; - - return -1; -} - -int BitArray::findNextSetBit (int i) const throw() -{ - for (; i <= highestBit; ++i) - if ((values [i >> 5] & (1 << (i & 31))) != 0) - return i; - - return -1; -} - -int BitArray::findNextClearBit (int i) const throw() -{ - for (; i <= highestBit; ++i) - if ((values [i >> 5] & (1 << (i & 31))) == 0) - break; - - return i; -} - -void BitArray::ensureSize (const int numVals) throw() -{ - if (numVals + 2 >= numValues) - { - int oldSize = numValues; - numValues = ((numVals + 2) * 3) / 2; - values = (unsigned int*) juce_realloc (values, sizeof (unsigned int) * numValues + 4); - - while (oldSize < numValues) - values [oldSize++] = 0; - } -} - -//============================================================================== -const String BitArray::toString (const int base, const int minimumNumCharacters) const throw() -{ - String s; - BitArray v (*this); - - if (base == 2 || base == 8 || base == 16) - { - const int bits = (base == 2) ? 1 : (base == 8 ? 3 : 4); - static const tchar* const hexDigits = T("0123456789abcdef"); - - for (;;) - { - const int remainder = v.getBitRangeAsInt (0, bits); - - v.shiftBits (-bits); - - if (remainder == 0 && v.isEmpty()) - break; - - s = String::charToString (hexDigits [remainder]) + s; - } - } - else if (base == 10) - { - const BitArray ten (10); - BitArray remainder; - - for (;;) - { - v.divideBy (ten, remainder); - - if (remainder.isEmpty() && v.isEmpty()) - break; - - s = String (remainder.getBitRangeAsInt (0, 8)) + s; - } - } - else - { - jassertfalse // can't do the specified base - return String::empty; - } - - const int length = s.length(); - - if (length < minimumNumCharacters) - s = String::repeatedString (T("0"), minimumNumCharacters - length); - - return isNegative() ? T("-") + s : s; -} - -void BitArray::parseString (const String& text, - const int base) throw() -{ - clear(); - const tchar* t = (const tchar*) text; - - if (base == 2 || base == 8 || base == 16) - { - const int bits = (base == 2) ? 1 : (base == 8 ? 3 : 4); - - for (;;) - { - const tchar c = *t++; - const int digit = CharacterFunctions::getHexDigitValue (c); - - if (((unsigned int) digit) < (unsigned int) base) - { - shiftBits (bits); - add (digit); - } - else if (c == 0) - { - break; - } - } - } - else if (base == 10) - { - const BitArray ten ((unsigned int) 10); - - for (;;) - { - const tchar c = *t++; - - if (c >= T('0') && c <= T('9')) - { - multiplyBy (ten); - add ((int) (c - T('0'))); - } - else if (c == 0) - { - break; - } - } - } - - setNegative (text.trimStart().startsWithChar (T('-'))); -} - -const MemoryBlock BitArray::toMemoryBlock() const throw() -{ - const int numBytes = (getHighestBit() + 8) >> 3; - MemoryBlock mb (numBytes); - - for (int i = 0; i < numBytes; ++i) - mb[i] = (uint8) getBitRangeAsInt (i << 3, 8); - - return mb; -} - -void BitArray::loadFromMemoryBlock (const MemoryBlock& data) throw() -{ - clear(); - - for (int i = data.getSize(); --i >= 0;) - this->setBitRangeAsInt (i << 3, 8, data [i]); -} - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + JUCE is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../basics/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + + +#include "juce_BitArray.h" +#include "juce_MemoryBlock.h" +#include "../basics/juce_Random.h" + + +//============================================================================== +BitArray::BitArray() throw() + : numValues (4), + highestBit (-1), + negative (false) +{ + values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); +} + +BitArray::BitArray (const int value) throw() + : numValues (4), + highestBit (31), + negative (value < 0) +{ + values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); + values[0] = abs (value); + highestBit = getHighestBit(); +} + +BitArray::BitArray (int64 value) throw() + : numValues (4), + highestBit (63), + negative (value < 0) +{ + values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); + + if (value < 0) + value = -value; + + values[0] = (unsigned int) value; + values[1] = (unsigned int) (value >> 32); + highestBit = getHighestBit(); +} + +BitArray::BitArray (const unsigned int value) throw() + : numValues (4), + highestBit (31), + negative (false) +{ + values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); + values[0] = value; + highestBit = getHighestBit(); +} + +BitArray::BitArray (const BitArray& other) throw() + : numValues (jmax (4, (other.highestBit >> 5) + 1)), + highestBit (other.getHighestBit()), + negative (other.negative) +{ + const int bytes = sizeof (unsigned int) * (numValues + 1); + values = (unsigned int*) juce_malloc (bytes); + memcpy (values, other.values, bytes); +} + +BitArray::~BitArray() throw() +{ + juce_free (values); +} + +const BitArray& BitArray::operator= (const BitArray& other) throw() +{ + if (this != &other) + { + juce_free (values); + + highestBit = other.getHighestBit(); + numValues = jmax (4, (highestBit >> 5) + 1); + negative = other.negative; + const int memSize = sizeof (unsigned int) * (numValues + 1); + values = (unsigned int*)juce_malloc (memSize); + memcpy (values, other.values, memSize); + } + + return *this; +} + +// result == 0 = the same +// result < 0 = this number is smaller +// result > 0 = this number is bigger +int BitArray::compare (const BitArray& other) const throw() +{ + if (isNegative() == other.isNegative()) + { + const int absComp = compareAbsolute (other); + return isNegative() ? -absComp : absComp; + } + else + { + return isNegative() ? -1 : 1; + } +} + +int BitArray::compareAbsolute (const BitArray& other) const throw() +{ + const int h1 = getHighestBit(); + const int h2 = other.getHighestBit(); + + if (h1 > h2) + return 1; + else if (h1 < h2) + return -1; + + for (int i = (h1 >> 5) + 1; --i >= 0;) + if (values[i] != other.values[i]) + return (values[i] > other.values[i]) ? 1 : -1; + + return 0; +} + +bool BitArray::operator== (const BitArray& other) const throw() +{ + return compare (other) == 0; +} + +bool BitArray::operator!= (const BitArray& other) const throw() +{ + return compare (other) != 0; +} + +bool BitArray::operator[] (const int bit) const throw() +{ + return bit >= 0 && bit <= highestBit + && ((values [bit >> 5] & (1 << (bit & 31))) != 0); +} + +bool BitArray::isEmpty() const throw() +{ + return getHighestBit() < 0; +} + +void BitArray::clear() throw() +{ + if (numValues > 16) + { + juce_free (values); + numValues = 4; + values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); + } + else + { + zeromem (values, sizeof (unsigned int) * (numValues + 1)); + } + + highestBit = -1; + negative = false; +} + +void BitArray::setBit (const int bit) throw() +{ + if (bit >= 0) + { + if (bit > highestBit) + { + ensureSize (bit >> 5); + highestBit = bit; + } + + values [bit >> 5] |= (1 << (bit & 31)); + } +} + +void BitArray::setBit (const int bit, + const bool shouldBeSet) throw() +{ + if (shouldBeSet) + setBit (bit); + else + clearBit (bit); +} + +void BitArray::clearBit (const int bit) throw() +{ + if (bit >= 0 && bit <= highestBit) + values [bit >> 5] &= ~(1 << (bit & 31)); +} + +void BitArray::setRange (int startBit, + int numBits, + const bool shouldBeSet) throw() +{ + while (--numBits >= 0) + setBit (startBit++, shouldBeSet); +} + +void BitArray::insertBit (const int bit, + const bool shouldBeSet) throw() +{ + if (bit >= 0) + shiftBits (1, bit); + + setBit (bit, shouldBeSet); +} + +//============================================================================== +void BitArray::andWith (const BitArray& other) throw() +{ + // this operation will only work with the absolute values + jassert (isNegative() == other.isNegative()); + + int n = numValues; + + while (n > other.numValues) + values[--n] = 0; + + while (--n >= 0) + values[n] &= other.values[n]; + + if (other.highestBit < highestBit) + highestBit = other.highestBit; + + highestBit = getHighestBit(); +} + +void BitArray::orWith (const BitArray& other) throw() +{ + if (other.highestBit < 0) + return; + + // this operation will only work with the absolute values + jassert (isNegative() == other.isNegative()); + + ensureSize (other.highestBit >> 5); + + int n = (other.highestBit >> 5) + 1; + + while (--n >= 0) + values[n] |= other.values[n]; + + if (other.highestBit > highestBit) + highestBit = other.highestBit; + + highestBit = getHighestBit(); +} + +void BitArray::xorWith (const BitArray& other) throw() +{ + if (other.highestBit < 0) + return; + + // this operation will only work with the absolute values + jassert (isNegative() == other.isNegative()); + + ensureSize (other.highestBit >> 5); + + int n = (other.highestBit >> 5) + 1; + + while (--n >= 0) + values[n] ^= other.values[n]; + + if (other.highestBit > highestBit) + highestBit = other.highestBit; + + highestBit = getHighestBit(); +} + +//============================================================================== +void BitArray::add (const BitArray& other) throw() +{ + if (other.isNegative()) + { + BitArray o (other); + o.negate(); + subtract (o); + return; + } + + if (isNegative()) + { + if (compareAbsolute (other) < 0) + { + BitArray temp (*this); + temp.negate(); + *this = other; + subtract (temp); + } + else + { + negate(); + subtract (other); + negate(); + } + + return; + } + + if (other.highestBit > highestBit) + highestBit = other.highestBit; + + ++highestBit; + + const int numInts = (highestBit >> 5) + 1; + ensureSize (numInts); + + int64 remainder = 0; + + for (int i = 0; i <= numInts; ++i) + { + if (i < numValues) + remainder += values[i]; + + if (i < other.numValues) + remainder += other.values[i]; + + values[i] = (unsigned int) remainder; + remainder >>= 32; + } + + jassert (remainder == 0); + highestBit = getHighestBit(); +} + +void BitArray::subtract (const BitArray& other) throw() +{ + if (other.isNegative()) + { + BitArray o (other); + o.negate(); + add (o); + return; + } + + if (! isNegative()) + { + if (compareAbsolute (other) < 0) + { + BitArray temp (*this); + *this = other; + subtract (temp); + negate(); + return; + } + } + else + { + negate(); + add (other); + negate(); + return; + } + + const int numInts = (highestBit >> 5) + 1; + const int maxOtherInts = (other.highestBit >> 5) + 1; + int64 amountToSubtract = 0; + + for (int i = 0; i <= numInts; ++i) + { + if (i <= maxOtherInts) + amountToSubtract += (int64)other.values[i]; + + if (values[i] >= amountToSubtract) + { + values[i] = (unsigned int) (values[i] - amountToSubtract); + amountToSubtract = 0; + } + else + { + const int64 n = ((int64) values[i] + (((int64) 1) << 32)) - amountToSubtract; + values[i] = (unsigned int) n; + amountToSubtract = 1; + } + } +} + +void BitArray::multiplyBy (const BitArray& other) throw() +{ + BitArray total; + highestBit = getHighestBit(); + const bool wasNegative = isNegative(); + setNegative (false); + + for (int i = 0; i <= highestBit; ++i) + { + if (operator[](i)) + { + BitArray n (other); + n.setNegative (false); + n.shiftBits (i); + total.add (n); + } + } + + *this = total; + negative = wasNegative ^ other.isNegative(); +} + +void BitArray::divideBy (const BitArray& divisor, BitArray& remainder) throw() +{ + jassert (this != &remainder); // (can't handle passing itself in to get the remainder) + + const int divHB = divisor.getHighestBit(); + const int ourHB = getHighestBit(); + + if (divHB < 0 || ourHB < 0) + { + // division by zero + remainder.clear(); + clear(); + } + else + { + remainder = *this; + remainder.setNegative (false); + const bool wasNegative = isNegative(); + clear(); + + BitArray temp (divisor); + temp.setNegative (false); + + int leftShift = ourHB - divHB; + temp.shiftBits (leftShift); + + while (leftShift >= 0) + { + if (remainder.compareAbsolute (temp) >= 0) + { + remainder.subtract (temp); + setBit (leftShift); + } + + if (--leftShift >= 0) + temp.shiftBits (-1); + } + + negative = wasNegative ^ divisor.isNegative(); + remainder.setNegative (wasNegative); + } +} + +void BitArray::modulo (const BitArray& divisor) throw() +{ + BitArray remainder; + divideBy (divisor, remainder); + *this = remainder; +} + +static const BitArray simpleGCD (BitArray* m, BitArray* n) throw() +{ + while (! m->isEmpty()) + { + if (n->compareAbsolute (*m) > 0) + swapVariables (m, n); + + m->subtract (*n); + } + + return *n; +} + +const BitArray BitArray::findGreatestCommonDivisor (BitArray n) const throw() +{ + BitArray m (*this); + + while (! n.isEmpty()) + { + if (abs (m.getHighestBit() - n.getHighestBit()) <= 16) + return simpleGCD (&m, &n); + + BitArray temp1 (m), temp2; + temp1.divideBy (n, temp2); + + m = n; + n = temp2; + } + + return m; +} + +void BitArray::exponentModulo (const BitArray& exponent, + const BitArray& modulus) throw() +{ + BitArray exp (exponent); + exp.modulo (modulus); + + BitArray value (*this); + value.modulo (modulus); + + clear(); + setBit (0); + + while (! exp.isEmpty()) + { + if (exp [0]) + { + multiplyBy (value); + this->modulo (modulus); + } + + value.multiplyBy (value); + value.modulo (modulus); + + exp.shiftBits (-1); + } +} + +void BitArray::inverseModulo (const BitArray& modulus) throw() +{ + const BitArray one (1); + + if (modulus == one || modulus.isNegative()) + { + clear(); + return; + } + + if (isNegative() || compareAbsolute (modulus) >= 0) + this->modulo (modulus); + + if (*this == one) + return; + + if (! (*this)[0]) + { + // not invertible + clear(); + return; + } + + BitArray a1 (modulus); + BitArray a2 (*this); + BitArray b1 (modulus); + BitArray b2 (1); + + while (a2 != one) + { + BitArray temp1, temp2, multiplier (a1); + multiplier.divideBy (a2, temp1); + + temp1 = a2; + temp1.multiplyBy (multiplier); + temp2 = a1; + temp2.subtract (temp1); + a1 = a2; + a2 = temp2; + + temp1 = b2; + temp1.multiplyBy (multiplier); + temp2 = b1; + temp2.subtract (temp1); + b1 = b2; + b2 = temp2; + } + + while (b2.isNegative()) + b2.add (modulus); + + b2.modulo (modulus); + *this = b2; +} + +//============================================================================== +void BitArray::shiftBits (int bits, const int startBit) throw() +{ + if (highestBit < 0) + return; + + if (startBit > 0) + { + if (bits < 0) + { + // right shift + for (int i = startBit; i <= highestBit; ++i) + setBit (i, operator[] (i - bits)); + + highestBit = getHighestBit(); + } + else if (bits > 0) + { + // left shift + for (int i = highestBit + 1; --i >= startBit;) + setBit (i + bits, operator[] (i)); + + while (--bits >= 0) + clearBit (bits + startBit); + } + } + else + { + if (bits < 0) + { + // right shift + bits = -bits; + + if (bits > highestBit) + { + clear(); + } + else + { + const int wordsToMove = bits >> 5; + int top = 1 + (highestBit >> 5) - wordsToMove; + highestBit -= bits; + + if (wordsToMove > 0) + { + int i; + for (i = 0; i < top; ++i) + values [i] = values [i + wordsToMove]; + + for (i = 0; i < wordsToMove; ++i) + values [top + i] = 0; + + bits &= 31; + } + + if (bits != 0) + { + const int invBits = 32 - bits; + + --top; + for (int i = 0; i < top; ++i) + values[i] = (values[i] >> bits) | (values [i + 1] << invBits); + + values[top] = (values[top] >> bits); + } + + highestBit = getHighestBit(); + } + } + else if (bits > 0) + { + // left shift + ensureSize (((highestBit + bits) >> 5) + 1); + + const int wordsToMove = bits >> 5; + int top = 1 + (highestBit >> 5); + highestBit += bits; + + if (wordsToMove > 0) + { + int i; + for (i = top; --i >= 0;) + values [i + wordsToMove] = values [i]; + + for (i = 0; i < wordsToMove; ++i) + values [i] = 0; + + bits &= 31; + } + + if (bits != 0) + { + const int invBits = 32 - bits; + + for (int i = top + 1 + wordsToMove; --i > wordsToMove;) + values[i] = (values[i] << bits) | (values [i - 1] >> invBits); + + values [wordsToMove] = values [wordsToMove] << bits; + } + + highestBit = getHighestBit(); + } + } +} + +const BitArray BitArray::getBitRange (int startBit, int numBits) const throw() +{ + BitArray r; + numBits = jmin (numBits, getHighestBit() + 1 - startBit); + r.ensureSize (numBits >> 5); + r.highestBit = numBits; + + int i = 0; + while (numBits > 0) + { + r.values[i++] = getBitRangeAsInt (startBit, jmin (32, numBits)); + numBits -= 32; + startBit += 32; + } + + r.highestBit = r.getHighestBit(); + + return r; +} + +int BitArray::getBitRangeAsInt (const int startBit, int numBits) const throw() +{ + if (numBits > 32) + { + jassertfalse // use getBitRange() if you need more than 32 bits.. + numBits = 32; + } + + numBits = jmin (numBits, highestBit + 1 - startBit); + + if (numBits <= 0) + return 0; + + const int pos = startBit >> 5; + const int offset = startBit & 31; + const int endSpace = 32 - numBits; + + uint32 n = ((uint32) values [pos]) >> offset; + + if (offset > endSpace) + n |= ((uint32) values [pos + 1]) << (32 - offset); + + return (int) (n & (((uint32) 0xffffffff) >> endSpace)); +} + +void BitArray::setBitRangeAsInt (const int startBit, int numBits, unsigned int valueToSet) throw() +{ + if (numBits > 32) + { + jassertfalse + numBits = 32; + } + + for (int i = 0; i < numBits; ++i) + { + setBit (startBit + i, (valueToSet & 1) != 0); + valueToSet >>= 1; + } +} + +//============================================================================== +bool BitArray::isNegative() const throw() +{ + return negative && ! isEmpty(); +} + +void BitArray::setNegative (const bool neg) throw() +{ + negative = neg; +} + +void BitArray::negate() throw() +{ + negative = (! negative) && ! isEmpty(); +} + +int BitArray::countNumberOfSetBits() const throw() +{ + int total = 0; + + for (int i = (highestBit >> 5) + 1; --i >= 0;) + { + unsigned int n = values[i]; + + if (n == 0xffffffff) + { + total += 32; + } + else + { + while (n != 0) + { + total += (n & 1); + n >>= 1; + } + } + } + + return total; +} + +int BitArray::getHighestBit() const throw() +{ + for (int i = highestBit + 1; --i >= 0;) + if ((values [i >> 5] & (1 << (i & 31))) != 0) + return i; + + return -1; +} + +int BitArray::findNextSetBit (int i) const throw() +{ + for (; i <= highestBit; ++i) + if ((values [i >> 5] & (1 << (i & 31))) != 0) + return i; + + return -1; +} + +int BitArray::findNextClearBit (int i) const throw() +{ + for (; i <= highestBit; ++i) + if ((values [i >> 5] & (1 << (i & 31))) == 0) + break; + + return i; +} + +void BitArray::ensureSize (const int numVals) throw() +{ + if (numVals + 2 >= numValues) + { + int oldSize = numValues; + numValues = ((numVals + 2) * 3) / 2; + values = (unsigned int*) juce_realloc (values, sizeof (unsigned int) * numValues + 4); + + while (oldSize < numValues) + values [oldSize++] = 0; + } +} + +//============================================================================== +const String BitArray::toString (const int base, const int minimumNumCharacters) const throw() +{ + String s; + BitArray v (*this); + + if (base == 2 || base == 8 || base == 16) + { + const int bits = (base == 2) ? 1 : (base == 8 ? 3 : 4); + static const tchar* const hexDigits = T("0123456789abcdef"); + + for (;;) + { + const int remainder = v.getBitRangeAsInt (0, bits); + + v.shiftBits (-bits); + + if (remainder == 0 && v.isEmpty()) + break; + + s = String::charToString (hexDigits [remainder]) + s; + } + } + else if (base == 10) + { + const BitArray ten (10); + BitArray remainder; + + for (;;) + { + v.divideBy (ten, remainder); + + if (remainder.isEmpty() && v.isEmpty()) + break; + + s = String (remainder.getBitRangeAsInt (0, 8)) + s; + } + } + else + { + jassertfalse // can't do the specified base + return String::empty; + } + + const int length = s.length(); + + if (length < minimumNumCharacters) + s = String::repeatedString (T("0"), minimumNumCharacters - length); + + return isNegative() ? T("-") + s : s; +} + +void BitArray::parseString (const String& text, + const int base) throw() +{ + clear(); + const tchar* t = (const tchar*) text; + + if (base == 2 || base == 8 || base == 16) + { + const int bits = (base == 2) ? 1 : (base == 8 ? 3 : 4); + + for (;;) + { + const tchar c = *t++; + const int digit = CharacterFunctions::getHexDigitValue (c); + + if (((unsigned int) digit) < (unsigned int) base) + { + shiftBits (bits); + add (digit); + } + else if (c == 0) + { + break; + } + } + } + else if (base == 10) + { + const BitArray ten ((unsigned int) 10); + + for (;;) + { + const tchar c = *t++; + + if (c >= T('0') && c <= T('9')) + { + multiplyBy (ten); + add ((int) (c - T('0'))); + } + else if (c == 0) + { + break; + } + } + } + + setNegative (text.trimStart().startsWithChar (T('-'))); +} + +const MemoryBlock BitArray::toMemoryBlock() const throw() +{ + const int numBytes = (getHighestBit() + 8) >> 3; + MemoryBlock mb (numBytes); + + for (int i = 0; i < numBytes; ++i) + mb[i] = (uint8) getBitRangeAsInt (i << 3, 8); + + return mb; +} + +void BitArray::loadFromMemoryBlock (const MemoryBlock& data) throw() +{ + clear(); + + for (int i = data.getSize(); --i >= 0;) + this->setBitRangeAsInt (i << 3, 8, data [i]); +} + +END_JUCE_NAMESPACE diff --git a/src/juce_core/text/juce_StringPairArray.cpp b/src/juce_core/text/juce_StringPairArray.cpp index d0cabacb47..28554555df 100644 --- a/src/juce_core/text/juce_StringPairArray.cpp +++ b/src/juce_core/text/juce_StringPairArray.cpp @@ -1,158 +1,158 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - JUCE is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../basics/juce_StandardHeader.h" - -BEGIN_JUCE_NAMESPACE - - -#include "juce_StringPairArray.h" - - -//============================================================================== -StringPairArray::StringPairArray (const bool ignoreCase_) throw() - : ignoreCase (ignoreCase_) -{ -} - -StringPairArray::StringPairArray (const StringPairArray& other) throw() - : keys (other.keys), - values (other.values), - ignoreCase (other.ignoreCase) -{ -} - -StringPairArray::~StringPairArray() throw() -{ -} - -const StringPairArray& StringPairArray::operator= (const StringPairArray& other) throw() -{ - keys = other.keys; - values = other.values; - - return *this; -} - -bool StringPairArray::operator== (const StringPairArray& other) const throw() -{ - for (int i = keys.size(); --i >= 0;) - if (other [keys[i]] != values[i]) - return false; - - return true; -} - -bool StringPairArray::operator!= (const StringPairArray& other) const throw() -{ - return ! operator== (other); -} - -const String& StringPairArray::operator[] (const String& key) const throw() -{ - return values [keys.indexOf (key, ignoreCase)]; -} - -const String StringPairArray::getValue (const String& key, const String& defaultReturnValue) const -{ - const int i = keys.indexOf (key, ignoreCase); - - if (i >= 0) - return values[i]; - - return defaultReturnValue; -} - -void StringPairArray::set (const String& key, - const String& value) throw() -{ - const int i = keys.indexOf (key, ignoreCase); - - if (i >= 0) - { - values.set (i, value); - } - else - { - keys.add (key); - values.add (value); - } -} - -void StringPairArray::addArray (const StringPairArray& other) -{ - for (int i = 0; i < other.size(); ++i) - set (other.keys[i], other.values[i]); -} - -void StringPairArray::clear() throw() -{ - keys.clear(); - values.clear(); -} - -void StringPairArray::remove (const String& key) throw() -{ - remove (keys.indexOf (key, ignoreCase)); -} - -void StringPairArray::remove (const int index) throw() -{ - keys.remove (index); - values.remove (index); -} - -void StringPairArray::setIgnoresCase (const bool shouldIgnoreCase) throw() -{ - ignoreCase = shouldIgnoreCase; -} - -const String StringPairArray::getDescription() const -{ - String s; - - for (int i = 0; i < keys.size(); ++i) - { - s << keys[i] << T(" = ") << values[i]; - if (i < keys.size()) - s << T(", "); - } - - return s; -} - -void StringPairArray::minimiseStorageOverheads() throw() -{ - keys.minimiseStorageOverheads(); - values.minimiseStorageOverheads(); -} - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + JUCE is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../basics/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + + +#include "juce_StringPairArray.h" + + +//============================================================================== +StringPairArray::StringPairArray (const bool ignoreCase_) throw() + : ignoreCase (ignoreCase_) +{ +} + +StringPairArray::StringPairArray (const StringPairArray& other) throw() + : keys (other.keys), + values (other.values), + ignoreCase (other.ignoreCase) +{ +} + +StringPairArray::~StringPairArray() throw() +{ +} + +const StringPairArray& StringPairArray::operator= (const StringPairArray& other) throw() +{ + keys = other.keys; + values = other.values; + + return *this; +} + +bool StringPairArray::operator== (const StringPairArray& other) const throw() +{ + for (int i = keys.size(); --i >= 0;) + if (other [keys[i]] != values[i]) + return false; + + return true; +} + +bool StringPairArray::operator!= (const StringPairArray& other) const throw() +{ + return ! operator== (other); +} + +const String& StringPairArray::operator[] (const String& key) const throw() +{ + return values [keys.indexOf (key, ignoreCase)]; +} + +const String StringPairArray::getValue (const String& key, const String& defaultReturnValue) const +{ + const int i = keys.indexOf (key, ignoreCase); + + if (i >= 0) + return values[i]; + + return defaultReturnValue; +} + +void StringPairArray::set (const String& key, + const String& value) throw() +{ + const int i = keys.indexOf (key, ignoreCase); + + if (i >= 0) + { + values.set (i, value); + } + else + { + keys.add (key); + values.add (value); + } +} + +void StringPairArray::addArray (const StringPairArray& other) +{ + for (int i = 0; i < other.size(); ++i) + set (other.keys[i], other.values[i]); +} + +void StringPairArray::clear() throw() +{ + keys.clear(); + values.clear(); +} + +void StringPairArray::remove (const String& key) throw() +{ + remove (keys.indexOf (key, ignoreCase)); +} + +void StringPairArray::remove (const int index) throw() +{ + keys.remove (index); + values.remove (index); +} + +void StringPairArray::setIgnoresCase (const bool shouldIgnoreCase) throw() +{ + ignoreCase = shouldIgnoreCase; +} + +const String StringPairArray::getDescription() const +{ + String s; + + for (int i = 0; i < keys.size(); ++i) + { + s << keys[i] << T(" = ") << values[i]; + if (i < keys.size()) + s << T(", "); + } + + return s; +} + +void StringPairArray::minimiseStorageOverheads() throw() +{ + keys.minimiseStorageOverheads(); + values.minimiseStorageOverheads(); +} + +END_JUCE_NAMESPACE