This reverts commit 3f1c018bcc.
Signed-off-by: falkTX <falktx@gmail.com>
tags/v2.1-alpha1-winvst
| @@ -0,0 +1,119 @@ | |||
| #!/usr/bin/make -f | |||
| # Makefile for juce_data_structures # | |||
| # --------------------------------- # | |||
| # Created by falkTX | |||
| # | |||
| CWD=../.. | |||
| MODULENAME=juce_data_structures | |||
| include ../Makefile.mk | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| BUILD_CXX_FLAGS += $(JUCE_DATA_STRUCTURES_FLAGS) -I.. | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| ifeq ($(MACOS),true) | |||
| OBJS = $(OBJDIR)/$(MODULENAME).mm.o | |||
| OBJS_posix32 = $(OBJDIR)/$(MODULENAME).mm.posix32.o | |||
| OBJS_posix64 = $(OBJDIR)/$(MODULENAME).mm.posix64.o | |||
| else | |||
| OBJS = $(OBJDIR)/$(MODULENAME).cpp.o | |||
| OBJS_posix32 = $(OBJDIR)/$(MODULENAME).cpp.posix32.o | |||
| OBJS_posix64 = $(OBJDIR)/$(MODULENAME).cpp.posix64.o | |||
| endif | |||
| OBJS_win32 = $(OBJDIR)/$(MODULENAME).cpp.win32.o | |||
| OBJS_win64 = $(OBJDIR)/$(MODULENAME).cpp.win64.o | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| all: $(MODULEDIR)/$(MODULENAME).a | |||
| posix32: $(MODULEDIR)/$(MODULENAME).posix32.a | |||
| posix64: $(MODULEDIR)/$(MODULENAME).posix64.a | |||
| win32: $(MODULEDIR)/$(MODULENAME).win32.a | |||
| win64: $(MODULEDIR)/$(MODULENAME).win64.a | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| clean: | |||
| rm -f $(OBJDIR)/*.o $(MODULEDIR)/$(MODULENAME)*.a | |||
| debug: | |||
| $(MAKE) DEBUG=true | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| $(MODULEDIR)/$(MODULENAME).a: $(OBJS) | |||
| -@mkdir -p $(MODULEDIR) | |||
| @echo "Creating $(MODULENAME).a" | |||
| @rm -f $@ | |||
| @$(AR) crs $@ $^ | |||
| $(MODULEDIR)/$(MODULENAME).posix32.a: $(OBJS_posix32) | |||
| -@mkdir -p $(MODULEDIR) | |||
| @echo "Creating $(MODULENAME).posix32.a" | |||
| @rm -f $@ | |||
| @$(AR) crs $@ $^ | |||
| $(MODULEDIR)/$(MODULENAME).posix64.a: $(OBJS_posix64) | |||
| -@mkdir -p $(MODULEDIR) | |||
| @echo "Creating $(MODULENAME).posix64.a" | |||
| @rm -f $@ | |||
| @$(AR) crs $@ $^ | |||
| $(MODULEDIR)/$(MODULENAME).win32.a: $(OBJS_win32) | |||
| -@mkdir -p $(MODULEDIR) | |||
| @echo "Creating $(MODULENAME).win32.a" | |||
| @rm -f $@ | |||
| @$(AR) crs $@ $^ | |||
| $(MODULEDIR)/$(MODULENAME).win64.a: $(OBJS_win64) | |||
| -@mkdir -p $(MODULEDIR) | |||
| @echo "Creating $(MODULENAME).win64.a" | |||
| @rm -f $@ | |||
| @$(AR) crs $@ $^ | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| $(OBJDIR)/$(MODULENAME).cpp.o: $(MODULENAME).cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $<" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | |||
| $(OBJDIR)/$(MODULENAME).cpp.%32.o: $(MODULENAME).cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $< (32bit)" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ | |||
| $(OBJDIR)/$(MODULENAME).cpp.%64.o: $(MODULENAME).cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $< (64bit)" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| $(OBJDIR)/$(MODULENAME).mm.o: $(MODULENAME).cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $<" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ | |||
| $(OBJDIR)/$(MODULENAME).mm.%32.o: $(MODULENAME).cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $< (32bit)" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -ObjC++ -c -o $@ | |||
| $(OBJDIR)/$(MODULENAME).mm.%64.o: $(MODULENAME).cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $< (64bit)" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -ObjC++ -c -o $@ | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| -include $(OBJS:%.o=%.d) | |||
| -include $(OBJS_posix32:%.o=%.d) | |||
| -include $(OBJS_posix64:%.o=%.d) | |||
| -include $(OBJS_win32:%.o=%.d) | |||
| -include $(OBJS_win64:%.o=%.d) | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| @@ -0,0 +1,109 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| ApplicationProperties::ApplicationProperties() | |||
| : commonSettingsAreReadOnly (0) | |||
| { | |||
| } | |||
| ApplicationProperties::~ApplicationProperties() | |||
| { | |||
| closeFiles(); | |||
| } | |||
| //============================================================================== | |||
| void ApplicationProperties::setStorageParameters (const PropertiesFile::Options& newOptions) | |||
| { | |||
| options = newOptions; | |||
| } | |||
| //============================================================================== | |||
| void ApplicationProperties::openFiles() | |||
| { | |||
| // You need to call setStorageParameters() before trying to get hold of the properties! | |||
| jassert (options.applicationName.isNotEmpty()); | |||
| if (options.applicationName.isNotEmpty()) | |||
| { | |||
| PropertiesFile::Options o (options); | |||
| if (userProps == nullptr) | |||
| { | |||
| o.commonToAllUsers = false; | |||
| userProps = new PropertiesFile (o); | |||
| } | |||
| if (commonProps == nullptr) | |||
| { | |||
| o.commonToAllUsers = true; | |||
| commonProps = new PropertiesFile (o); | |||
| } | |||
| userProps->setFallbackPropertySet (commonProps); | |||
| } | |||
| } | |||
| PropertiesFile* ApplicationProperties::getUserSettings() | |||
| { | |||
| if (userProps == nullptr) | |||
| openFiles(); | |||
| return userProps; | |||
| } | |||
| PropertiesFile* ApplicationProperties::getCommonSettings (const bool returnUserPropsIfReadOnly) | |||
| { | |||
| if (commonProps == nullptr) | |||
| openFiles(); | |||
| if (returnUserPropsIfReadOnly) | |||
| { | |||
| if (commonSettingsAreReadOnly == 0) | |||
| commonSettingsAreReadOnly = commonProps->save() ? -1 : 1; | |||
| if (commonSettingsAreReadOnly > 0) | |||
| return userProps; | |||
| } | |||
| return commonProps; | |||
| } | |||
| bool ApplicationProperties::saveIfNeeded() | |||
| { | |||
| return (userProps == nullptr || userProps->saveIfNeeded()) | |||
| && (commonProps == nullptr || commonProps->saveIfNeeded()); | |||
| } | |||
| void ApplicationProperties::closeFiles() | |||
| { | |||
| userProps = nullptr; | |||
| commonProps = nullptr; | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,132 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Manages a collection of properties. | |||
| This is a slightly higher-level wrapper for managing PropertiesFile objects. | |||
| It holds two different PropertiesFile objects internally, one for user-specific | |||
| settings (stored in your user directory), and one for settings that are common to | |||
| all users (stored in a folder accessible to all users). | |||
| The class manages the creation of these files on-demand, allowing access via the | |||
| getUserSettings() and getCommonSettings() methods. | |||
| After creating an instance of an ApplicationProperties object, you should first | |||
| of all call setStorageParameters() to tell it the parameters to use to create | |||
| its files. | |||
| @see PropertiesFile | |||
| */ | |||
| class JUCE_API ApplicationProperties | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** | |||
| Creates an ApplicationProperties object. | |||
| Before using it, you must call setStorageParameters() to give it the info | |||
| it needs to create the property files. | |||
| */ | |||
| ApplicationProperties(); | |||
| /** Destructor. */ | |||
| ~ApplicationProperties(); | |||
| //============================================================================== | |||
| /** Gives the object the information it needs to create the appropriate properties files. | |||
| See the PropertiesFile::Options class for details about what options you need to set. | |||
| */ | |||
| void setStorageParameters (const PropertiesFile::Options& options); | |||
| /** Returns the current storage parameters. | |||
| @see setStorageParameters | |||
| */ | |||
| const PropertiesFile::Options& getStorageParameters() const noexcept { return options; } | |||
| //============================================================================== | |||
| /** Returns the user settings file. | |||
| The first time this is called, it will create and load the properties file. | |||
| Note that when you search the user PropertiesFile for a value that it doesn't contain, | |||
| the common settings are used as a second-chance place to look. This is done via the | |||
| PropertySet::setFallbackPropertySet() method - by default the common settings are set | |||
| to the fallback for the user settings. | |||
| @see getCommonSettings | |||
| */ | |||
| PropertiesFile* getUserSettings(); | |||
| /** Returns the common settings file. | |||
| The first time this is called, it will create and load the properties file. | |||
| @param returnUserPropsIfReadOnly if this is true, and the common properties file is | |||
| read-only (e.g. because the user doesn't have permission to write | |||
| to shared files), then this will return the user settings instead, | |||
| (like getUserSettings() would do). This is handy if you'd like to | |||
| write a value to the common settings, but if that's no possible, | |||
| then you'd rather write to the user settings than none at all. | |||
| If returnUserPropsIfReadOnly is false, this method will always return | |||
| the common settings, even if any changes to them can't be saved. | |||
| @see getUserSettings | |||
| */ | |||
| PropertiesFile* getCommonSettings (bool returnUserPropsIfReadOnly); | |||
| //============================================================================== | |||
| /** Saves both files if they need to be saved. | |||
| @see PropertiesFile::saveIfNeeded | |||
| */ | |||
| bool saveIfNeeded(); | |||
| /** Flushes and closes both files if they are open. | |||
| This flushes any pending changes to disk with PropertiesFile::saveIfNeeded() | |||
| and closes both files. They will then be re-opened the next time getUserSettings() | |||
| or getCommonSettings() is called. | |||
| */ | |||
| void closeFiles(); | |||
| private: | |||
| //============================================================================== | |||
| PropertiesFile::Options options; | |||
| ScopedPointer<PropertiesFile> userProps, commonProps; | |||
| int commonSettingsAreReadOnly; | |||
| void openFiles(); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ApplicationProperties) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,363 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| namespace PropertyFileConstants | |||
| { | |||
| JUCE_CONSTEXPR static const int magicNumber = (int) ByteOrder::littleEndianInt ('P', 'R', 'O', 'P'); | |||
| JUCE_CONSTEXPR static const int magicNumberCompressed = (int) ByteOrder::littleEndianInt ('C', 'P', 'R', 'P'); | |||
| JUCE_CONSTEXPR static const char* const fileTag = "PROPERTIES"; | |||
| JUCE_CONSTEXPR static const char* const valueTag = "VALUE"; | |||
| JUCE_CONSTEXPR static const char* const nameAttribute = "name"; | |||
| JUCE_CONSTEXPR static const char* const valueAttribute = "val"; | |||
| } | |||
| //============================================================================== | |||
| PropertiesFile::Options::Options() | |||
| : commonToAllUsers (false), | |||
| ignoreCaseOfKeyNames (false), | |||
| doNotSave (false), | |||
| millisecondsBeforeSaving (3000), | |||
| storageFormat (PropertiesFile::storeAsXML), | |||
| processLock (nullptr) | |||
| { | |||
| } | |||
| File PropertiesFile::Options::getDefaultFile() const | |||
| { | |||
| // mustn't have illegal characters in this name.. | |||
| jassert (applicationName == File::createLegalFileName (applicationName)); | |||
| #if JUCE_MAC || JUCE_IOS | |||
| File dir (commonToAllUsers ? "/Library/" | |||
| : "~/Library/"); | |||
| if (osxLibrarySubFolder != "Preferences" && ! osxLibrarySubFolder.startsWith ("Application Support")) | |||
| { | |||
| /* The PropertiesFile class always used to put its settings files in "Library/Preferences", but Apple | |||
| have changed their advice, and now stipulate that settings should go in "Library/Application Support". | |||
| Because older apps would be broken by a silent change in this class's behaviour, you must now | |||
| explicitly set the osxLibrarySubFolder value to indicate which path you want to use. | |||
| In newer apps, you should always set this to "Application Support" | |||
| or "Application Support/YourSubFolderName". | |||
| If your app needs to load settings files that were created by older versions of juce and | |||
| you want to maintain backwards-compatibility, then you can set this to "Preferences". | |||
| But.. for better Apple-compliance, the recommended approach would be to write some code that | |||
| finds your old settings files in ~/Library/Preferences, moves them to ~/Library/Application Support, | |||
| and then uses the new path. | |||
| */ | |||
| jassertfalse; | |||
| dir = dir.getChildFile ("Application Support"); | |||
| } | |||
| else | |||
| { | |||
| dir = dir.getChildFile (osxLibrarySubFolder); | |||
| } | |||
| if (folderName.isNotEmpty()) | |||
| dir = dir.getChildFile (folderName); | |||
| #elif JUCE_LINUX || JUCE_ANDROID | |||
| const File dir (File (commonToAllUsers ? "/var" : "~") | |||
| .getChildFile (folderName.isNotEmpty() ? folderName | |||
| : ("." + applicationName))); | |||
| #elif JUCE_WINDOWS | |||
| File dir (File::getSpecialLocation (commonToAllUsers ? File::commonApplicationDataDirectory | |||
| : File::userApplicationDataDirectory)); | |||
| if (dir == File()) | |||
| return {}; | |||
| dir = dir.getChildFile (folderName.isNotEmpty() ? folderName | |||
| : applicationName); | |||
| #endif | |||
| return (filenameSuffix.startsWithChar (L'.') | |||
| ? dir.getChildFile (applicationName).withFileExtension (filenameSuffix) | |||
| : dir.getChildFile (applicationName + "." + filenameSuffix)); | |||
| } | |||
| //============================================================================== | |||
| PropertiesFile::PropertiesFile (const File& f, const Options& o) | |||
| : PropertySet (o.ignoreCaseOfKeyNames), | |||
| file (f), options (o), | |||
| loadedOk (false), needsWriting (false) | |||
| { | |||
| reload(); | |||
| } | |||
| PropertiesFile::PropertiesFile (const Options& o) | |||
| : PropertySet (o.ignoreCaseOfKeyNames), | |||
| file (o.getDefaultFile()), options (o), | |||
| loadedOk (false), needsWriting (false) | |||
| { | |||
| reload(); | |||
| } | |||
| bool PropertiesFile::reload() | |||
| { | |||
| ProcessScopedLock pl (createProcessLock()); | |||
| if (pl != nullptr && ! pl->isLocked()) | |||
| return false; // locking failure.. | |||
| loadedOk = (! file.exists()) || loadAsBinary() || loadAsXml(); | |||
| return loadedOk; | |||
| } | |||
| PropertiesFile::~PropertiesFile() | |||
| { | |||
| saveIfNeeded(); | |||
| } | |||
| InterProcessLock::ScopedLockType* PropertiesFile::createProcessLock() const | |||
| { | |||
| return options.processLock != nullptr ? new InterProcessLock::ScopedLockType (*options.processLock) : nullptr; | |||
| } | |||
| bool PropertiesFile::saveIfNeeded() | |||
| { | |||
| const ScopedLock sl (getLock()); | |||
| return (! needsWriting) || save(); | |||
| } | |||
| bool PropertiesFile::needsToBeSaved() const | |||
| { | |||
| const ScopedLock sl (getLock()); | |||
| return needsWriting; | |||
| } | |||
| void PropertiesFile::setNeedsToBeSaved (const bool needsToBeSaved_) | |||
| { | |||
| const ScopedLock sl (getLock()); | |||
| needsWriting = needsToBeSaved_; | |||
| } | |||
| bool PropertiesFile::save() | |||
| { | |||
| const ScopedLock sl (getLock()); | |||
| stopTimer(); | |||
| if (options.doNotSave | |||
| || file == File() | |||
| || file.isDirectory() | |||
| || ! file.getParentDirectory().createDirectory()) | |||
| return false; | |||
| if (options.storageFormat == storeAsXML) | |||
| return saveAsXml(); | |||
| return saveAsBinary(); | |||
| } | |||
| bool PropertiesFile::loadAsXml() | |||
| { | |||
| XmlDocument parser (file); | |||
| ScopedPointer<XmlElement> doc (parser.getDocumentElement (true)); | |||
| if (doc != nullptr && doc->hasTagName (PropertyFileConstants::fileTag)) | |||
| { | |||
| doc = parser.getDocumentElement(); | |||
| if (doc != nullptr) | |||
| { | |||
| forEachXmlChildElementWithTagName (*doc, e, PropertyFileConstants::valueTag) | |||
| { | |||
| const String name (e->getStringAttribute (PropertyFileConstants::nameAttribute)); | |||
| if (name.isNotEmpty()) | |||
| { | |||
| getAllProperties().set (name, | |||
| e->getFirstChildElement() != nullptr | |||
| ? e->getFirstChildElement()->createDocument ("", true) | |||
| : e->getStringAttribute (PropertyFileConstants::valueAttribute)); | |||
| } | |||
| } | |||
| return true; | |||
| } | |||
| // must be a pretty broken XML file we're trying to parse here, | |||
| // or a sign that this object needs an InterProcessLock, | |||
| // or just a failure reading the file. This last reason is why | |||
| // we don't jassertfalse here. | |||
| } | |||
| return false; | |||
| } | |||
| bool PropertiesFile::saveAsXml() | |||
| { | |||
| XmlElement doc (PropertyFileConstants::fileTag); | |||
| const StringPairArray& props = getAllProperties(); | |||
| for (int i = 0; i < props.size(); ++i) | |||
| { | |||
| XmlElement* const e = doc.createNewChildElement (PropertyFileConstants::valueTag); | |||
| e->setAttribute (PropertyFileConstants::nameAttribute, props.getAllKeys() [i]); | |||
| // if the value seems to contain xml, store it as such.. | |||
| if (XmlElement* const childElement = XmlDocument::parse (props.getAllValues() [i])) | |||
| e->addChildElement (childElement); | |||
| else | |||
| e->setAttribute (PropertyFileConstants::valueAttribute, props.getAllValues() [i]); | |||
| } | |||
| ProcessScopedLock pl (createProcessLock()); | |||
| if (pl != nullptr && ! pl->isLocked()) | |||
| return false; // locking failure.. | |||
| if (doc.writeToFile (file, String())) | |||
| { | |||
| needsWriting = false; | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| bool PropertiesFile::loadAsBinary() | |||
| { | |||
| FileInputStream fileStream (file); | |||
| if (fileStream.openedOk()) | |||
| { | |||
| const int magicNumber = fileStream.readInt(); | |||
| if (magicNumber == PropertyFileConstants::magicNumberCompressed) | |||
| { | |||
| SubregionStream subStream (&fileStream, 4, -1, false); | |||
| GZIPDecompressorInputStream gzip (subStream); | |||
| return loadAsBinary (gzip); | |||
| } | |||
| if (magicNumber == PropertyFileConstants::magicNumber) | |||
| return loadAsBinary (fileStream); | |||
| } | |||
| return false; | |||
| } | |||
| bool PropertiesFile::loadAsBinary (InputStream& input) | |||
| { | |||
| BufferedInputStream in (input, 2048); | |||
| int numValues = in.readInt(); | |||
| while (--numValues >= 0 && ! in.isExhausted()) | |||
| { | |||
| const String key (in.readString()); | |||
| const String value (in.readString()); | |||
| jassert (key.isNotEmpty()); | |||
| if (key.isNotEmpty()) | |||
| getAllProperties().set (key, value); | |||
| } | |||
| return true; | |||
| } | |||
| bool PropertiesFile::saveAsBinary() | |||
| { | |||
| ProcessScopedLock pl (createProcessLock()); | |||
| if (pl != nullptr && ! pl->isLocked()) | |||
| return false; // locking failure.. | |||
| TemporaryFile tempFile (file); | |||
| ScopedPointer<OutputStream> out (tempFile.getFile().createOutputStream()); | |||
| if (out != nullptr) | |||
| { | |||
| if (options.storageFormat == storeAsCompressedBinary) | |||
| { | |||
| out->writeInt (PropertyFileConstants::magicNumberCompressed); | |||
| out->flush(); | |||
| out = new GZIPCompressorOutputStream (out.release(), 9, true); | |||
| } | |||
| else | |||
| { | |||
| // have you set up the storage option flags correctly? | |||
| jassert (options.storageFormat == storeAsBinary); | |||
| out->writeInt (PropertyFileConstants::magicNumber); | |||
| } | |||
| const StringPairArray& props = getAllProperties(); | |||
| const int numProperties = props.size(); | |||
| const StringArray& keys = props.getAllKeys(); | |||
| const StringArray& values = props.getAllValues(); | |||
| out->writeInt (numProperties); | |||
| for (int i = 0; i < numProperties; ++i) | |||
| { | |||
| out->writeString (keys[i]); | |||
| out->writeString (values[i]); | |||
| } | |||
| out = nullptr; | |||
| if (tempFile.overwriteTargetFileWithTemporary()) | |||
| { | |||
| needsWriting = false; | |||
| return true; | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| void PropertiesFile::timerCallback() | |||
| { | |||
| saveIfNeeded(); | |||
| } | |||
| void PropertiesFile::propertyChanged() | |||
| { | |||
| sendChangeMessage(); | |||
| needsWriting = true; | |||
| if (options.millisecondsBeforeSaving > 0) | |||
| startTimer (options.millisecondsBeforeSaving); | |||
| else if (options.millisecondsBeforeSaving == 0) | |||
| saveIfNeeded(); | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,252 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** Wrapper on a file that stores a list of key/value data pairs. | |||
| Useful for storing application settings, etc. See the PropertySet class for | |||
| the interfaces that read and write values. | |||
| Not designed for very large amounts of data, as it keeps all the values in | |||
| memory and writes them out to disk lazily when they are changed. | |||
| Because this class derives from ChangeBroadcaster, ChangeListeners can be registered | |||
| with it, and these will be signalled when a value changes. | |||
| @see PropertySet | |||
| */ | |||
| class JUCE_API PropertiesFile : public PropertySet, | |||
| public ChangeBroadcaster, | |||
| private Timer | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| enum StorageFormat | |||
| { | |||
| storeAsBinary, | |||
| storeAsCompressedBinary, | |||
| storeAsXML | |||
| }; | |||
| //============================================================================== | |||
| struct JUCE_API Options | |||
| { | |||
| /** Creates an empty Options structure. | |||
| You'll need to fill-in the data members appropriately before using this structure. | |||
| */ | |||
| Options(); | |||
| /** The name of your application - this is used to help generate the path and filename | |||
| at which the properties file will be stored. */ | |||
| String applicationName; | |||
| /** The suffix to use for your properties file. | |||
| It doesn't really matter what this is - you may want to use ".settings" or | |||
| ".properties" or something. If the suffix includes the prefixing dot (for example | |||
| ".settings") then the suffix of applicationName will be replaced with your suffix | |||
| ("MyApp.exe" -> "MyApp.settings"). If your filenameSuffix does NOT include the dot, | |||
| then the suffix will be appended to the applicationName ("MyApp.exe" -> | |||
| "MyApp.exe.settings"). | |||
| */ | |||
| String filenameSuffix; | |||
| /** The name of a subfolder in which you'd like your properties file to live. | |||
| See the getDefaultFile() method for more details about how this is used. | |||
| */ | |||
| String folderName; | |||
| /** If you're using properties files on a Mac, you must set this value - failure to | |||
| do so will cause a runtime assertion. | |||
| The PropertiesFile class always used to put its settings files in "Library/Preferences", but Apple | |||
| have changed their advice, and now stipulate that settings should go in "Library/Application Support". | |||
| Because older apps would be broken by a silent change in this class's behaviour, you must now | |||
| explicitly set the osxLibrarySubFolder value to indicate which path you want to use. | |||
| In newer apps, you should always set this to "Application Support" or | |||
| "Application Support/YourSubFolderName". | |||
| If your app needs to load settings files that were created by older versions of juce and | |||
| you want to maintain backwards-compatibility, then you can set this to "Preferences". | |||
| But.. for better Apple-compliance, the recommended approach would be to write some code that | |||
| finds your old settings files in ~/Library/Preferences, moves them to ~/Library/Application Support, | |||
| and then uses the new path. | |||
| */ | |||
| String osxLibrarySubFolder; | |||
| /** If true, the file will be created in a location that's shared between users. | |||
| The default constructor initialises this value to false. | |||
| */ | |||
| bool commonToAllUsers; | |||
| /** If true, this means that property names are matched in a case-insensitive manner. | |||
| See the PropertySet constructor for more info. | |||
| The default constructor initialises this value to false. | |||
| */ | |||
| bool ignoreCaseOfKeyNames; | |||
| /** If set to true, this prevents the file from being written to disk. */ | |||
| bool doNotSave; | |||
| /** If this is zero or greater, then after a value is changed, the object will wait | |||
| for this amount of time and then save the file. If this zero, the file will be | |||
| written to disk immediately on being changed (which might be slow, as it'll re-write | |||
| synchronously each time a value-change method is called). If it is less than zero, | |||
| the file won't be saved until save() or saveIfNeeded() are explicitly called. | |||
| The default constructor sets this to a reasonable value of a few seconds, so you | |||
| only need to change it if you need a special case. | |||
| */ | |||
| int millisecondsBeforeSaving; | |||
| /** Specifies whether the file should be written as XML, binary, etc. | |||
| The default constructor sets this to storeAsXML, so you only need to set it explicitly | |||
| if you want to use a different format. | |||
| */ | |||
| StorageFormat storageFormat; | |||
| /** An optional InterprocessLock object that will be used to prevent multiple threads or | |||
| processes from writing to the file at the same time. The PropertiesFile will keep a | |||
| pointer to this object but will not take ownership of it - the caller is responsible for | |||
| making sure that the lock doesn't get deleted before the PropertiesFile has been deleted. | |||
| The default constructor initialises this value to nullptr, so you don't need to touch it | |||
| unless you want to use a lock. | |||
| */ | |||
| InterProcessLock* processLock; | |||
| /** This can be called to suggest a file that should be used, based on the values | |||
| in this structure. | |||
| So on a Mac, this will return a file called: | |||
| ~/Library/[osxLibrarySubFolder]/[folderName]/[applicationName].[filenameSuffix] | |||
| On Windows it'll return something like: | |||
| C:\\Documents and Settings\\username\\Application Data\\[folderName]\\[applicationName].[filenameSuffix] | |||
| On Linux it'll return | |||
| ~/[folderName]/[applicationName].[filenameSuffix] | |||
| If the folderName variable is empty, it'll use the app name for this (or omit the | |||
| folder name on the Mac). | |||
| The paths will also vary depending on whether commonToAllUsers is true. | |||
| */ | |||
| File getDefaultFile() const; | |||
| }; | |||
| //============================================================================== | |||
| /** Creates a PropertiesFile object. | |||
| The file used will be chosen by calling PropertiesFile::Options::getDefaultFile() | |||
| for the options provided. To set the file explicitly, use the other constructor. | |||
| */ | |||
| explicit PropertiesFile (const Options& options); | |||
| /** Creates a PropertiesFile object. | |||
| Unlike the other constructor, this one allows you to explicitly set the file that you | |||
| want to be used, rather than using the default one. | |||
| */ | |||
| PropertiesFile (const File& file, | |||
| const Options& options); | |||
| /** Destructor. | |||
| When deleted, the file will first call saveIfNeeded() to flush any changes to disk. | |||
| */ | |||
| ~PropertiesFile(); | |||
| //============================================================================== | |||
| /** Returns true if this file was created from a valid (or non-existent) file. | |||
| If the file failed to load correctly because it was corrupt or had insufficient | |||
| access, this will be false. | |||
| */ | |||
| bool isValidFile() const noexcept { return loadedOk; } | |||
| //============================================================================== | |||
| /** This will flush all the values to disk if they've changed since the last | |||
| time they were saved. | |||
| Returns false if it fails to write to the file for some reason (maybe because | |||
| it's read-only or the directory doesn't exist or something). | |||
| @see save | |||
| */ | |||
| bool saveIfNeeded(); | |||
| /** This will force a write-to-disk of the current values, regardless of whether | |||
| anything has changed since the last save. | |||
| Returns false if it fails to write to the file for some reason (maybe because | |||
| it's read-only or the directory doesn't exist or something). | |||
| @see saveIfNeeded | |||
| */ | |||
| bool save(); | |||
| /** Returns true if the properties have been altered since the last time they were saved. | |||
| The file is flagged as needing to be saved when you change a value, but you can | |||
| explicitly set this flag with setNeedsToBeSaved(). | |||
| */ | |||
| bool needsToBeSaved() const; | |||
| /** Explicitly sets the flag to indicate whether the file needs saving or not. | |||
| @see needsToBeSaved | |||
| */ | |||
| void setNeedsToBeSaved (bool needsToBeSaved); | |||
| /** Attempts to reload the settings from the file. */ | |||
| bool reload(); | |||
| //============================================================================== | |||
| /** Returns the file that's being used. */ | |||
| const File& getFile() const noexcept { return file; } | |||
| protected: | |||
| /** @internal */ | |||
| void propertyChanged() override; | |||
| private: | |||
| //============================================================================== | |||
| File file; | |||
| Options options; | |||
| bool loadedOk, needsWriting; | |||
| typedef const ScopedPointer<InterProcessLock::ScopedLockType> ProcessScopedLock; | |||
| InterProcessLock::ScopedLockType* createProcessLock() const; | |||
| void timerCallback() override; | |||
| bool saveAsXml(); | |||
| bool saveAsBinary(); | |||
| bool loadAsXml(); | |||
| bool loadAsBinary(); | |||
| bool loadAsBinary (InputStream&); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertiesFile) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,44 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| #ifdef JUCE_DATA_STRUCTURES_H_INCLUDED | |||
| /* When you add this cpp file to your project, you mustn't include it in a file where you've | |||
| already included any other headers - just put it inside a file on its own, possibly with your config | |||
| flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix | |||
| header files that the compiler may be using. | |||
| */ | |||
| #error "Incorrect use of JUCE cpp file" | |||
| #endif | |||
| #include "juce_data_structures.h" | |||
| #include "values/juce_Value.cpp" | |||
| #include "values/juce_ValueTree.cpp" | |||
| #include "values/juce_ValueTreeSynchroniser.cpp" | |||
| #include "values/juce_CachedValue.cpp" | |||
| #include "undomanager/juce_UndoManager.cpp" | |||
| #include "app_properties/juce_ApplicationProperties.cpp" | |||
| #include "app_properties/juce_PropertiesFile.cpp" | |||
| @@ -0,0 +1,64 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| /******************************************************************************* | |||
| The block below describes the properties of this module, and is read by | |||
| the Projucer to automatically generate project code that uses it. | |||
| For details about the syntax and how to create or use a module, see the | |||
| JUCE Module Format.txt file. | |||
| BEGIN_JUCE_MODULE_DECLARATION | |||
| ID: juce_data_structures | |||
| vendor: juce | |||
| version: 5.1.2 | |||
| name: JUCE data model helper classes | |||
| description: Classes for undo/redo management, and smart data structures. | |||
| website: http://www.juce.com/juce | |||
| license: GPL/Commercial | |||
| dependencies: juce_events | |||
| END_JUCE_MODULE_DECLARATION | |||
| *******************************************************************************/ | |||
| #pragma once | |||
| #define JUCE_DATA_STRUCTURES_H_INCLUDED | |||
| //============================================================================== | |||
| #include <juce_events/juce_events.h> | |||
| #include "undomanager/juce_UndoableAction.h" | |||
| #include "undomanager/juce_UndoManager.h" | |||
| #include "values/juce_Value.h" | |||
| #include "values/juce_ValueTree.h" | |||
| #include "values/juce_ValueTreeSynchroniser.h" | |||
| #include "values/juce_CachedValue.h" | |||
| #include "app_properties/juce_PropertiesFile.h" | |||
| #include "app_properties/juce_ApplicationProperties.h" | |||
| @@ -0,0 +1,352 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| struct UndoManager::ActionSet | |||
| { | |||
| ActionSet (const String& transactionName) | |||
| : name (transactionName), | |||
| time (Time::getCurrentTime()) | |||
| {} | |||
| bool perform() const | |||
| { | |||
| for (int i = 0; i < actions.size(); ++i) | |||
| if (! actions.getUnchecked(i)->perform()) | |||
| return false; | |||
| return true; | |||
| } | |||
| bool undo() const | |||
| { | |||
| for (int i = actions.size(); --i >= 0;) | |||
| if (! actions.getUnchecked(i)->undo()) | |||
| return false; | |||
| return true; | |||
| } | |||
| int getTotalSize() const | |||
| { | |||
| int total = 0; | |||
| for (int i = actions.size(); --i >= 0;) | |||
| total += actions.getUnchecked(i)->getSizeInUnits(); | |||
| return total; | |||
| } | |||
| OwnedArray<UndoableAction> actions; | |||
| String name; | |||
| Time time; | |||
| }; | |||
| //============================================================================== | |||
| UndoManager::UndoManager (const int maxNumberOfUnitsToKeep, | |||
| const int minimumTransactions) | |||
| : totalUnitsStored (0), | |||
| nextIndex (0), | |||
| newTransaction (true), | |||
| reentrancyCheck (false) | |||
| { | |||
| setMaxNumberOfStoredUnits (maxNumberOfUnitsToKeep, | |||
| minimumTransactions); | |||
| } | |||
| UndoManager::~UndoManager() | |||
| { | |||
| } | |||
| //============================================================================== | |||
| void UndoManager::clearUndoHistory() | |||
| { | |||
| transactions.clear(); | |||
| totalUnitsStored = 0; | |||
| nextIndex = 0; | |||
| sendChangeMessage(); | |||
| } | |||
| int UndoManager::getNumberOfUnitsTakenUpByStoredCommands() const | |||
| { | |||
| return totalUnitsStored; | |||
| } | |||
| void UndoManager::setMaxNumberOfStoredUnits (const int maxNumberOfUnitsToKeep, | |||
| const int minimumTransactions) | |||
| { | |||
| maxNumUnitsToKeep = jmax (1, maxNumberOfUnitsToKeep); | |||
| minimumTransactionsToKeep = jmax (1, minimumTransactions); | |||
| } | |||
| //============================================================================== | |||
| bool UndoManager::perform (UndoableAction* const newAction, const String& actionName) | |||
| { | |||
| if (perform (newAction)) | |||
| { | |||
| if (actionName.isNotEmpty()) | |||
| setCurrentTransactionName (actionName); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| bool UndoManager::perform (UndoableAction* const newAction) | |||
| { | |||
| if (newAction != nullptr) | |||
| { | |||
| ScopedPointer<UndoableAction> action (newAction); | |||
| if (reentrancyCheck) | |||
| { | |||
| jassertfalse; // don't call perform() recursively from the UndoableAction::perform() | |||
| // or undo() methods, or else these actions will be discarded! | |||
| return false; | |||
| } | |||
| if (action->perform()) | |||
| { | |||
| ActionSet* actionSet = getCurrentSet(); | |||
| if (actionSet != nullptr && ! newTransaction) | |||
| { | |||
| if (UndoableAction* const lastAction = actionSet->actions.getLast()) | |||
| { | |||
| if (UndoableAction* const coalescedAction = lastAction->createCoalescedAction (action)) | |||
| { | |||
| action = coalescedAction; | |||
| totalUnitsStored -= lastAction->getSizeInUnits(); | |||
| actionSet->actions.removeLast(); | |||
| } | |||
| } | |||
| } | |||
| else | |||
| { | |||
| actionSet = new ActionSet (newTransactionName); | |||
| transactions.insert (nextIndex, actionSet); | |||
| ++nextIndex; | |||
| } | |||
| totalUnitsStored += action->getSizeInUnits(); | |||
| actionSet->actions.add (action.release()); | |||
| newTransaction = false; | |||
| moveFutureTransactionsToStash(); | |||
| dropOldTransactionsIfTooLarge(); | |||
| sendChangeMessage(); | |||
| return true; | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| void UndoManager::moveFutureTransactionsToStash() | |||
| { | |||
| if (nextIndex < transactions.size()) | |||
| { | |||
| stashedFutureTransactions.clear(); | |||
| while (nextIndex < transactions.size()) | |||
| { | |||
| ActionSet* removed = transactions.removeAndReturn (nextIndex); | |||
| stashedFutureTransactions.add (removed); | |||
| totalUnitsStored -= removed->getTotalSize(); | |||
| } | |||
| } | |||
| } | |||
| void UndoManager::restoreStashedFutureTransactions() | |||
| { | |||
| while (nextIndex < transactions.size()) | |||
| { | |||
| totalUnitsStored -= transactions.getUnchecked (nextIndex)->getTotalSize(); | |||
| transactions.remove (nextIndex); | |||
| } | |||
| for (int i = 0; i < stashedFutureTransactions.size(); ++i) | |||
| { | |||
| ActionSet* action = stashedFutureTransactions.removeAndReturn (i); | |||
| totalUnitsStored += action->getTotalSize(); | |||
| transactions.add (action); | |||
| } | |||
| stashedFutureTransactions.clearQuick (false); | |||
| } | |||
| void UndoManager::dropOldTransactionsIfTooLarge() | |||
| { | |||
| while (nextIndex > 0 | |||
| && totalUnitsStored > maxNumUnitsToKeep | |||
| && transactions.size() > minimumTransactionsToKeep) | |||
| { | |||
| totalUnitsStored -= transactions.getFirst()->getTotalSize(); | |||
| transactions.remove (0); | |||
| --nextIndex; | |||
| // if this fails, then some actions may not be returning | |||
| // consistent results from their getSizeInUnits() method | |||
| jassert (totalUnitsStored >= 0); | |||
| } | |||
| } | |||
| void UndoManager::beginNewTransaction() noexcept | |||
| { | |||
| beginNewTransaction (String()); | |||
| } | |||
| void UndoManager::beginNewTransaction (const String& actionName) noexcept | |||
| { | |||
| newTransaction = true; | |||
| newTransactionName = actionName; | |||
| } | |||
| void UndoManager::setCurrentTransactionName (const String& newName) noexcept | |||
| { | |||
| if (newTransaction) | |||
| newTransactionName = newName; | |||
| else if (ActionSet* action = getCurrentSet()) | |||
| action->name = newName; | |||
| } | |||
| String UndoManager::getCurrentTransactionName() const noexcept | |||
| { | |||
| if (ActionSet* action = getCurrentSet()) | |||
| return action->name; | |||
| return newTransactionName; | |||
| } | |||
| //============================================================================== | |||
| UndoManager::ActionSet* UndoManager::getCurrentSet() const noexcept { return transactions [nextIndex - 1]; } | |||
| UndoManager::ActionSet* UndoManager::getNextSet() const noexcept { return transactions [nextIndex]; } | |||
| bool UndoManager::canUndo() const noexcept { return getCurrentSet() != nullptr; } | |||
| bool UndoManager::canRedo() const noexcept { return getNextSet() != nullptr; } | |||
| bool UndoManager::undo() | |||
| { | |||
| if (const ActionSet* const s = getCurrentSet()) | |||
| { | |||
| const ScopedValueSetter<bool> setter (reentrancyCheck, true); | |||
| if (s->undo()) | |||
| --nextIndex; | |||
| else | |||
| clearUndoHistory(); | |||
| beginNewTransaction(); | |||
| sendChangeMessage(); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| bool UndoManager::redo() | |||
| { | |||
| if (const ActionSet* const s = getNextSet()) | |||
| { | |||
| const ScopedValueSetter<bool> setter (reentrancyCheck, true); | |||
| if (s->perform()) | |||
| ++nextIndex; | |||
| else | |||
| clearUndoHistory(); | |||
| beginNewTransaction(); | |||
| sendChangeMessage(); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| String UndoManager::getUndoDescription() const | |||
| { | |||
| if (auto* s = getCurrentSet()) | |||
| return s->name; | |||
| return {}; | |||
| } | |||
| String UndoManager::getRedoDescription() const | |||
| { | |||
| if (auto* s = getNextSet()) | |||
| return s->name; | |||
| return {}; | |||
| } | |||
| Time UndoManager::getTimeOfUndoTransaction() const | |||
| { | |||
| if (auto* s = getCurrentSet()) | |||
| return s->time; | |||
| return {}; | |||
| } | |||
| Time UndoManager::getTimeOfRedoTransaction() const | |||
| { | |||
| if (auto* s = getNextSet()) | |||
| return s->time; | |||
| return Time::getCurrentTime(); | |||
| } | |||
| bool UndoManager::undoCurrentTransactionOnly() | |||
| { | |||
| if ((! newTransaction) && undo()) | |||
| { | |||
| restoreStashedFutureTransactions(); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| void UndoManager::getActionsInCurrentTransaction (Array<const UndoableAction*>& actionsFound) const | |||
| { | |||
| if (! newTransaction) | |||
| if (const ActionSet* const s = getCurrentSet()) | |||
| for (int i = 0; i < s->actions.size(); ++i) | |||
| actionsFound.add (s->actions.getUnchecked(i)); | |||
| } | |||
| int UndoManager::getNumActionsInCurrentTransaction() const | |||
| { | |||
| if (! newTransaction) | |||
| if (const ActionSet* const s = getCurrentSet()) | |||
| return s->actions.size(); | |||
| return 0; | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,246 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Manages a list of undo/redo commands. | |||
| An UndoManager object keeps a list of past actions and can use these actions | |||
| to move backwards and forwards through an undo history. | |||
| To use it, create subclasses of UndoableAction which perform all the | |||
| actions you need, then when you need to actually perform an action, create one | |||
| and pass it to the UndoManager's perform() method. | |||
| The manager also uses the concept of 'transactions' to group the actions | |||
| together - all actions performed between calls to beginNewTransaction() are | |||
| grouped together and are all undone/redone as a group. | |||
| The UndoManager is a ChangeBroadcaster, so listeners can register to be told | |||
| when actions are performed or undone. | |||
| @see UndoableAction | |||
| */ | |||
| class JUCE_API UndoManager : public ChangeBroadcaster | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates an UndoManager. | |||
| @param maxNumberOfUnitsToKeep each UndoableAction object returns a value | |||
| to indicate how much storage it takes up | |||
| (UndoableAction::getSizeInUnits()), so this | |||
| lets you specify the maximum total number of | |||
| units that the undomanager is allowed to | |||
| keep in memory before letting the older actions | |||
| drop off the end of the list. | |||
| @param minimumTransactionsToKeep this specifies the minimum number of transactions | |||
| that will be kept, even if this involves exceeding | |||
| the amount of space specified in maxNumberOfUnitsToKeep | |||
| */ | |||
| UndoManager (int maxNumberOfUnitsToKeep = 30000, | |||
| int minimumTransactionsToKeep = 30); | |||
| /** Destructor. */ | |||
| ~UndoManager(); | |||
| //============================================================================== | |||
| /** Deletes all stored actions in the list. */ | |||
| void clearUndoHistory(); | |||
| /** Returns the current amount of space to use for storing UndoableAction objects. | |||
| @see setMaxNumberOfStoredUnits | |||
| */ | |||
| int getNumberOfUnitsTakenUpByStoredCommands() const; | |||
| /** Sets the amount of space that can be used for storing UndoableAction objects. | |||
| @param maxNumberOfUnitsToKeep each UndoableAction object returns a value | |||
| to indicate how much storage it takes up | |||
| (UndoableAction::getSizeInUnits()), so this | |||
| lets you specify the maximum total number of | |||
| units that the undomanager is allowed to | |||
| keep in memory before letting the older actions | |||
| drop off the end of the list. | |||
| @param minimumTransactionsToKeep this specifies the minimum number of transactions | |||
| that will be kept, even if this involves exceeding | |||
| the amount of space specified in maxNumberOfUnitsToKeep | |||
| @see getNumberOfUnitsTakenUpByStoredCommands | |||
| */ | |||
| void setMaxNumberOfStoredUnits (int maxNumberOfUnitsToKeep, | |||
| int minimumTransactionsToKeep); | |||
| //============================================================================== | |||
| /** Performs an action and adds it to the undo history list. | |||
| @param action the action to perform - this object will be deleted by | |||
| the UndoManager when no longer needed | |||
| @returns true if the command succeeds - see UndoableAction::perform | |||
| @see beginNewTransaction | |||
| */ | |||
| bool perform (UndoableAction* action); | |||
| /** Performs an action and also gives it a name. | |||
| @param action the action to perform - this object will be deleted by | |||
| the UndoManager when no longer needed | |||
| @param actionName if this string is non-empty, the current transaction will be | |||
| given this name; if it's empty, the current transaction name will | |||
| be left unchanged. See setCurrentTransactionName() | |||
| @returns true if the command succeeds - see UndoableAction::perform | |||
| @see beginNewTransaction | |||
| */ | |||
| bool perform (UndoableAction* action, const String& actionName); | |||
| /** Starts a new group of actions that together will be treated as a single transaction. | |||
| All actions that are passed to the perform() method between calls to this | |||
| method are grouped together and undone/redone together by a single call to | |||
| undo() or redo(). | |||
| */ | |||
| void beginNewTransaction() noexcept; | |||
| /** Starts a new group of actions that together will be treated as a single transaction. | |||
| All actions that are passed to the perform() method between calls to this | |||
| method are grouped together and undone/redone together by a single call to | |||
| undo() or redo(). | |||
| @param actionName a description of the transaction that is about to be | |||
| performed | |||
| */ | |||
| void beginNewTransaction (const String& actionName) noexcept; | |||
| /** Changes the name stored for the current transaction. | |||
| Each transaction is given a name when the beginNewTransaction() method is | |||
| called, but this can be used to change that name without starting a new | |||
| transaction. | |||
| */ | |||
| void setCurrentTransactionName (const String& newName) noexcept; | |||
| /** Returns the name of the current transaction. | |||
| @see setCurrentTransactionName | |||
| */ | |||
| String getCurrentTransactionName() const noexcept; | |||
| //============================================================================== | |||
| /** Returns true if there's at least one action in the list to undo. | |||
| @see getUndoDescription, undo, canRedo | |||
| */ | |||
| bool canUndo() const noexcept; | |||
| /** Returns the name of the transaction that will be rolled-back when undo() is called. | |||
| @see undo | |||
| */ | |||
| String getUndoDescription() const; | |||
| /** Tries to roll-back the last transaction. | |||
| @returns true if the transaction can be undone, and false if it fails, or | |||
| if there aren't any transactions to undo | |||
| */ | |||
| bool undo(); | |||
| /** Tries to roll-back any actions that were added to the current transaction. | |||
| This will perform an undo() only if there are some actions in the undo list | |||
| that were added after the last call to beginNewTransaction(). | |||
| This is useful because it lets you call beginNewTransaction(), then | |||
| perform an operation which may or may not actually perform some actions, and | |||
| then call this method to get rid of any actions that might have been done | |||
| without it rolling back the previous transaction if nothing was actually | |||
| done. | |||
| @returns true if any actions were undone. | |||
| */ | |||
| bool undoCurrentTransactionOnly(); | |||
| /** Returns a list of the UndoableAction objects that have been performed during the | |||
| transaction that is currently open. | |||
| Effectively, this is the list of actions that would be undone if undoCurrentTransactionOnly() | |||
| were to be called now. | |||
| The first item in the list is the earliest action performed. | |||
| */ | |||
| void getActionsInCurrentTransaction (Array<const UndoableAction*>& actionsFound) const; | |||
| /** Returns the number of UndoableAction objects that have been performed during the | |||
| transaction that is currently open. | |||
| @see getActionsInCurrentTransaction | |||
| */ | |||
| int getNumActionsInCurrentTransaction() const; | |||
| /** Returns the time to which the state would be restored if undo() was to be called. | |||
| If an undo isn't currently possible, it'll return Time(). | |||
| */ | |||
| Time getTimeOfUndoTransaction() const; | |||
| /** Returns the time to which the state would be restored if redo() was to be called. | |||
| If a redo isn't currently possible, it'll return Time::getCurrentTime(). | |||
| */ | |||
| Time getTimeOfRedoTransaction() const; | |||
| //============================================================================== | |||
| /** Returns true if there's at least one action in the list to redo. | |||
| @see getRedoDescription, redo, canUndo | |||
| */ | |||
| bool canRedo() const noexcept; | |||
| /** Returns the name of the transaction that will be redone when redo() is called. | |||
| @see redo | |||
| */ | |||
| String getRedoDescription() const; | |||
| /** Tries to redo the last transaction that was undone. | |||
| @returns true if the transaction can be redone, and false if it fails, or | |||
| if there aren't any transactions to redo | |||
| */ | |||
| bool redo(); | |||
| private: | |||
| //============================================================================== | |||
| struct ActionSet; | |||
| friend struct ContainerDeletePolicy<ActionSet>; | |||
| OwnedArray<ActionSet> transactions, stashedFutureTransactions; | |||
| String newTransactionName; | |||
| int totalUnitsStored, maxNumUnitsToKeep, minimumTransactionsToKeep, nextIndex; | |||
| bool newTransaction, reentrancyCheck; | |||
| ActionSet* getCurrentSet() const noexcept; | |||
| ActionSet* getNextSet() const noexcept; | |||
| void moveFutureTransactionsToStash(); | |||
| void restoreStashedFutureTransactions(); | |||
| void dropOldTransactionsIfTooLarge(); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UndoManager) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,99 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Used by the UndoManager class to store an action which can be done | |||
| and undone. | |||
| @see UndoManager | |||
| */ | |||
| class JUCE_API UndoableAction | |||
| { | |||
| protected: | |||
| /** Creates an action. */ | |||
| UndoableAction() noexcept {} | |||
| public: | |||
| /** Destructor. */ | |||
| virtual ~UndoableAction() {} | |||
| //============================================================================== | |||
| /** Overridden by a subclass to perform the action. | |||
| This method is called by the UndoManager, and shouldn't be used directly by | |||
| applications. | |||
| Be careful not to make any calls in a perform() method that could call | |||
| recursively back into the UndoManager::perform() method | |||
| @returns true if the action could be performed. | |||
| @see UndoManager::perform | |||
| */ | |||
| virtual bool perform() = 0; | |||
| /** Overridden by a subclass to undo the action. | |||
| This method is called by the UndoManager, and shouldn't be used directly by | |||
| applications. | |||
| Be careful not to make any calls in an undo() method that could call | |||
| recursively back into the UndoManager::perform() method | |||
| @returns true if the action could be undone without any errors. | |||
| @see UndoManager::perform | |||
| */ | |||
| virtual bool undo() = 0; | |||
| //============================================================================== | |||
| /** Returns a value to indicate how much memory this object takes up. | |||
| Because the UndoManager keeps a list of UndoableActions, this is used | |||
| to work out how much space each one will take up, so that the UndoManager | |||
| can work out how many to keep. | |||
| The default value returned here is 10 - units are arbitrary and | |||
| don't have to be accurate. | |||
| @see UndoManager::getNumberOfUnitsTakenUpByStoredCommands, | |||
| UndoManager::setMaxNumberOfStoredUnits | |||
| */ | |||
| virtual int getSizeInUnits() { return 10; } | |||
| /** Allows multiple actions to be coalesced into a single action object, to reduce storage space. | |||
| If possible, this method should create and return a single action that does the same job as | |||
| this one followed by the supplied action. | |||
| If it's not possible to merge the two actions, the method should return a nullptr. | |||
| */ | |||
| virtual UndoableAction* createCoalescedAction (UndoableAction* nextAction) { ignoreUnused (nextAction); return nullptr; } | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,159 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| #if JUCE_UNIT_TESTS | |||
| class CachedValueTests : public UnitTest | |||
| { | |||
| public: | |||
| CachedValueTests() : UnitTest ("CachedValues", "Values") {} | |||
| void runTest() override | |||
| { | |||
| beginTest ("default constructor"); | |||
| { | |||
| CachedValue<String> cv; | |||
| expect (cv.isUsingDefault()); | |||
| expect (cv.get() == String()); | |||
| } | |||
| beginTest ("without default value"); | |||
| { | |||
| ValueTree t ("root"); | |||
| t.setProperty ("testkey", "testvalue", nullptr); | |||
| CachedValue<String> cv (t, "testkey", nullptr); | |||
| expect (! cv.isUsingDefault()); | |||
| expect (cv.get() == "testvalue"); | |||
| cv.resetToDefault(); | |||
| expect (cv.isUsingDefault()); | |||
| expect (cv.get() == String()); | |||
| } | |||
| beginTest ("with default value"); | |||
| { | |||
| ValueTree t ("root"); | |||
| t.setProperty ("testkey", "testvalue", nullptr); | |||
| CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue"); | |||
| expect (! cv.isUsingDefault()); | |||
| expect (cv.get() == "testvalue"); | |||
| cv.resetToDefault(); | |||
| expect (cv.isUsingDefault()); | |||
| expect (cv.get() == "defaultvalue"); | |||
| } | |||
| beginTest ("with default value (int)"); | |||
| { | |||
| ValueTree t ("root"); | |||
| t.setProperty ("testkey", 23, nullptr); | |||
| CachedValue<int> cv (t, "testkey", nullptr, 34); | |||
| expect (! cv.isUsingDefault()); | |||
| expect (cv == 23); | |||
| expectEquals (cv.get(), 23); | |||
| cv.resetToDefault(); | |||
| expect (cv.isUsingDefault()); | |||
| expect (cv == 34); | |||
| } | |||
| beginTest ("with void value"); | |||
| { | |||
| ValueTree t ("root"); | |||
| t.setProperty ("testkey", var(), nullptr); | |||
| CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue"); | |||
| expect (! cv.isUsingDefault()); | |||
| expect (cv == ""); | |||
| expectEquals (cv.get(), String()); | |||
| } | |||
| beginTest ("with non-existent value"); | |||
| { | |||
| ValueTree t ("root"); | |||
| CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue"); | |||
| expect (cv.isUsingDefault()); | |||
| expect (cv == "defaultvalue"); | |||
| expect (cv.get() == "defaultvalue"); | |||
| } | |||
| beginTest ("with value changing"); | |||
| { | |||
| ValueTree t ("root"); | |||
| t.setProperty ("testkey", "oldvalue", nullptr); | |||
| CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue"); | |||
| expect (cv == "oldvalue"); | |||
| t.setProperty ("testkey", "newvalue", nullptr); | |||
| expect (cv != "oldvalue"); | |||
| expect (cv == "newvalue"); | |||
| } | |||
| beginTest ("set value"); | |||
| { | |||
| ValueTree t ("root"); | |||
| t.setProperty ("testkey", 23, nullptr); | |||
| CachedValue<int> cv (t, "testkey", nullptr, 45); | |||
| cv = 34; | |||
| expectEquals ((int) t["testkey"], 34); | |||
| cv.resetToDefault(); | |||
| expect (cv == 45); | |||
| expectEquals (cv.get(), 45); | |||
| expect (t["testkey"] == var()); | |||
| } | |||
| beginTest ("reset value"); | |||
| { | |||
| } | |||
| } | |||
| }; | |||
| static CachedValueTests cachedValueTests; | |||
| #endif | |||
| } // namespace juce | |||
| @@ -0,0 +1,312 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| This class acts as a typed wrapper around a property inside a ValueTree. | |||
| A CachedValue provides an easy way to read and write a ValueTree property with | |||
| a chosen type. So for example a CachedValue<int> allows you to read or write the | |||
| property as an int, and a CachedValue<String> lets you work with it as a String. | |||
| It also allows efficient access to the value, by caching a copy of it in the | |||
| type that is being used. | |||
| You can give the CachedValue an optional UndoManager which it will use when writing | |||
| to the underlying ValueTree. | |||
| If the property inside the ValueTree is missing, the CachedValue will automatically | |||
| return an optional default value, which can be specified when initialising the CachedValue. | |||
| To create one, you can either use the constructor to attach the CachedValue to a | |||
| ValueTree, or can create an uninitialised CachedValue with its default constructor and | |||
| then attach it later with the referTo() methods. | |||
| Common types like String, int, double which can be easily converted to a var should work | |||
| out-of-the-box, but if you want to use more complex custom types, you may need to implement | |||
| some template specialisations of VariantConverter which this class uses to convert between | |||
| the type and the ValueTree's internal var. | |||
| */ | |||
| template <typename Type> | |||
| class CachedValue : private ValueTree::Listener | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Default constructor. | |||
| Creates a default CachedValue not referring to any property. To initialise the | |||
| object, call one of the referTo() methods. | |||
| */ | |||
| CachedValue(); | |||
| /** Constructor. | |||
| Creates a CachedValue referring to a Value property inside a ValueTree. | |||
| If you use this constructor, the fallback value will be a default-constructed | |||
| instance of Type. | |||
| @param tree The ValueTree containing the property | |||
| @param propertyID The identifier of the property | |||
| @param undoManager The UndoManager to use when writing to the property | |||
| */ | |||
| CachedValue (ValueTree& tree, const Identifier& propertyID, | |||
| UndoManager* undoManager); | |||
| /** Constructor. | |||
| Creates a default Cached Value referring to a Value property inside a ValueTree, | |||
| and specifies a fallback value to use if the property does not exist. | |||
| @param tree The ValueTree containing the property | |||
| @param propertyID The identifier of the property | |||
| @param undoManager The UndoManager to use when writing to the property | |||
| @param defaultToUse The fallback default value to use. | |||
| */ | |||
| CachedValue (ValueTree& tree, const Identifier& propertyID, | |||
| UndoManager* undoManager, const Type& defaultToUse); | |||
| //============================================================================== | |||
| /** Returns the current value of the property. If the property does not exist, | |||
| returns the fallback default value. | |||
| This is the same as calling get(). | |||
| */ | |||
| operator Type() const noexcept { return cachedValue; } | |||
| /** Returns the current value of the property. If the property does not exist, | |||
| returns the fallback default value. | |||
| */ | |||
| Type get() const noexcept { return cachedValue; } | |||
| /** Dereference operator. Provides direct access to the property. */ | |||
| Type& operator*() noexcept { return cachedValue; } | |||
| /** Dereference operator. Provides direct access to members of the property | |||
| if it is of object type. | |||
| */ | |||
| Type* operator->() noexcept { return &cachedValue; } | |||
| /** Returns true if the current value of the property (or the fallback value) | |||
| is equal to other. | |||
| */ | |||
| template <typename OtherType> | |||
| bool operator== (const OtherType& other) const { return cachedValue == other; } | |||
| /** Returns true if the current value of the property (or the fallback value) | |||
| is not equal to other. | |||
| */ | |||
| template <typename OtherType> | |||
| bool operator!= (const OtherType& other) const { return cachedValue != other; } | |||
| //============================================================================== | |||
| /** Returns the current property as a Value object. */ | |||
| Value getPropertyAsValue(); | |||
| /** Returns true if the current property does not exist and the CachedValue is using | |||
| the fallback default value instead. | |||
| */ | |||
| bool isUsingDefault() const; | |||
| /** Returns the current fallback default value. */ | |||
| Type getDefault() const { return defaultValue; } | |||
| //============================================================================== | |||
| /** Sets the property. This will actually modify the property in the referenced ValueTree. */ | |||
| CachedValue& operator= (const Type& newValue); | |||
| /** Sets the property. This will actually modify the property in the referenced ValueTree. */ | |||
| void setValue (const Type& newValue, UndoManager* undoManagerToUse); | |||
| /** Removes the property from the referenced ValueTree and makes the CachedValue | |||
| return the fallback default value instead. | |||
| */ | |||
| void resetToDefault(); | |||
| /** Removes the property from the referenced ValueTree and makes the CachedValue | |||
| return the fallback default value instead. | |||
| */ | |||
| void resetToDefault (UndoManager* undoManagerToUse); | |||
| /** Resets the fallback default value. */ | |||
| void setDefault (const Type& value) { defaultValue = value; } | |||
| //============================================================================== | |||
| /** Makes the CachedValue refer to the specified property inside the given ValueTree. */ | |||
| void referTo (ValueTree& tree, const Identifier& property, UndoManager* um); | |||
| /** Makes the CachedValue refer to the specified property inside the given ValueTree, | |||
| and specifies a fallback value to use if the property does not exist. | |||
| */ | |||
| void referTo (ValueTree& tree, const Identifier& property, UndoManager* um, const Type& defaultVal); | |||
| /** Force an update in case the referenced property has been changed from elsewhere. | |||
| Note: The CachedValue is a ValueTree::Listener and therefore will be informed of | |||
| changes of the referenced property anyway (and update itself). But this may happen | |||
| asynchronously. forceUpdateOfCachedValue() forces an update immediately. | |||
| */ | |||
| void forceUpdateOfCachedValue(); | |||
| //============================================================================== | |||
| /** Returns a reference to the ValueTree containing the referenced property. */ | |||
| ValueTree& getValueTree() noexcept { return targetTree; } | |||
| /** Returns the property ID of the referenced property. */ | |||
| const Identifier& getPropertyID() const noexcept { return targetProperty; } | |||
| private: | |||
| //============================================================================== | |||
| ValueTree targetTree; | |||
| Identifier targetProperty; | |||
| UndoManager* undoManager; | |||
| Type defaultValue; | |||
| Type cachedValue; | |||
| //============================================================================== | |||
| void referToWithDefault (ValueTree&, const Identifier&, UndoManager*, const Type&); | |||
| Type getTypedValue() const; | |||
| void valueTreePropertyChanged (ValueTree& changedTree, const Identifier& changedProperty) override; | |||
| void valueTreeChildAdded (ValueTree&, ValueTree&) override {} | |||
| void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override {} | |||
| void valueTreeChildOrderChanged (ValueTree&, int, int) override {} | |||
| void valueTreeParentChanged (ValueTree&) override {} | |||
| JUCE_DECLARE_NON_COPYABLE (CachedValue) | |||
| }; | |||
| //============================================================================== | |||
| template <typename Type> | |||
| inline CachedValue<Type>::CachedValue() : undoManager (nullptr) {} | |||
| template <typename Type> | |||
| inline CachedValue<Type>::CachedValue (ValueTree& v, const Identifier& i, UndoManager* um) | |||
| : targetTree (v), targetProperty (i), undoManager (um), | |||
| defaultValue(), cachedValue (getTypedValue()) | |||
| { | |||
| targetTree.addListener (this); | |||
| } | |||
| template <typename Type> | |||
| inline CachedValue<Type>::CachedValue (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultToUse) | |||
| : targetTree (v), targetProperty (i), undoManager (um), | |||
| defaultValue (defaultToUse), cachedValue (getTypedValue()) | |||
| { | |||
| targetTree.addListener (this); | |||
| } | |||
| template <typename Type> | |||
| inline Value CachedValue<Type>::getPropertyAsValue() | |||
| { | |||
| return targetTree.getPropertyAsValue (targetProperty, undoManager); | |||
| } | |||
| template <typename Type> | |||
| inline bool CachedValue<Type>::isUsingDefault() const | |||
| { | |||
| return ! targetTree.hasProperty (targetProperty); | |||
| } | |||
| template <typename Type> | |||
| inline CachedValue<Type>& CachedValue<Type>::operator= (const Type& newValue) | |||
| { | |||
| setValue (newValue, undoManager); | |||
| return *this; | |||
| } | |||
| template <typename Type> | |||
| inline void CachedValue<Type>::setValue (const Type& newValue, UndoManager* undoManagerToUse) | |||
| { | |||
| if (cachedValue != newValue || isUsingDefault()) | |||
| { | |||
| cachedValue = newValue; | |||
| targetTree.setProperty (targetProperty, VariantConverter<Type>::toVar (newValue), undoManagerToUse); | |||
| } | |||
| } | |||
| template <typename Type> | |||
| inline void CachedValue<Type>::resetToDefault() | |||
| { | |||
| resetToDefault (undoManager); | |||
| } | |||
| template <typename Type> | |||
| inline void CachedValue<Type>::resetToDefault (UndoManager* undoManagerToUse) | |||
| { | |||
| targetTree.removeProperty (targetProperty, undoManagerToUse); | |||
| forceUpdateOfCachedValue(); | |||
| } | |||
| template <typename Type> | |||
| inline void CachedValue<Type>::referTo (ValueTree& v, const Identifier& i, UndoManager* um) | |||
| { | |||
| referToWithDefault (v, i, um, Type()); | |||
| } | |||
| template <typename Type> | |||
| inline void CachedValue<Type>::referTo (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultVal) | |||
| { | |||
| referToWithDefault (v, i, um, defaultVal); | |||
| } | |||
| template <typename Type> | |||
| inline void CachedValue<Type>::forceUpdateOfCachedValue() | |||
| { | |||
| cachedValue = getTypedValue(); | |||
| } | |||
| template <typename Type> | |||
| inline void CachedValue<Type>::referToWithDefault (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultVal) | |||
| { | |||
| targetTree.removeListener (this); | |||
| targetTree = v; | |||
| targetProperty = i; | |||
| undoManager = um; | |||
| defaultValue = defaultVal; | |||
| cachedValue = getTypedValue(); | |||
| targetTree.addListener (this); | |||
| } | |||
| template <typename Type> | |||
| inline Type CachedValue<Type>::getTypedValue() const | |||
| { | |||
| if (const var* property = targetTree.getPropertyPointer (targetProperty)) | |||
| return VariantConverter<Type>::fromVar (*property); | |||
| return defaultValue; | |||
| } | |||
| template <typename Type> | |||
| inline void CachedValue<Type>::valueTreePropertyChanged (ValueTree& changedTree, const Identifier& changedProperty) | |||
| { | |||
| if (changedProperty == targetProperty && targetTree == changedTree) | |||
| forceUpdateOfCachedValue(); | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,242 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| Value::ValueSource::ValueSource() | |||
| { | |||
| } | |||
| Value::ValueSource::~ValueSource() | |||
| { | |||
| cancelPendingUpdate(); | |||
| } | |||
| void Value::ValueSource::handleAsyncUpdate() | |||
| { | |||
| sendChangeMessage (true); | |||
| } | |||
| void Value::ValueSource::sendChangeMessage (const bool synchronous) | |||
| { | |||
| const int numListeners = valuesWithListeners.size(); | |||
| if (numListeners > 0) | |||
| { | |||
| if (synchronous) | |||
| { | |||
| const ReferenceCountedObjectPtr<ValueSource> localRef (this); | |||
| cancelPendingUpdate(); | |||
| for (int i = numListeners; --i >= 0;) | |||
| if (Value* const v = valuesWithListeners[i]) | |||
| v->callListeners(); | |||
| } | |||
| else | |||
| { | |||
| triggerAsyncUpdate(); | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| class SimpleValueSource : public Value::ValueSource | |||
| { | |||
| public: | |||
| SimpleValueSource() | |||
| { | |||
| } | |||
| SimpleValueSource (const var& initialValue) | |||
| : value (initialValue) | |||
| { | |||
| } | |||
| var getValue() const override | |||
| { | |||
| return value; | |||
| } | |||
| void setValue (const var& newValue) override | |||
| { | |||
| if (! newValue.equalsWithSameType (value)) | |||
| { | |||
| value = newValue; | |||
| sendChangeMessage (false); | |||
| } | |||
| } | |||
| private: | |||
| var value; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimpleValueSource) | |||
| }; | |||
| //============================================================================== | |||
| Value::Value() : value (new SimpleValueSource()) | |||
| { | |||
| } | |||
| Value::Value (ValueSource* const v) : value (v) | |||
| { | |||
| jassert (v != nullptr); | |||
| } | |||
| Value::Value (const var& initialValue) : value (new SimpleValueSource (initialValue)) | |||
| { | |||
| } | |||
| Value::Value (const Value& other) : value (other.value) | |||
| { | |||
| } | |||
| Value::Value (Value&& other) noexcept | |||
| { | |||
| // moving a Value with listeners will lose those listeners, which | |||
| // probably isn't what you wanted to happen! | |||
| jassert (other.listeners.size() == 0); | |||
| other.removeFromListenerList(); | |||
| value = static_cast<ReferenceCountedObjectPtr<ValueSource>&&> (other.value); | |||
| } | |||
| Value& Value::operator= (Value&& other) noexcept | |||
| { | |||
| // moving a Value with listeners will lose those listeners, which | |||
| // probably isn't what you wanted to happen! | |||
| jassert (other.listeners.size() == 0); | |||
| other.removeFromListenerList(); | |||
| value = static_cast<ReferenceCountedObjectPtr<ValueSource>&&> (other.value); | |||
| return *this; | |||
| } | |||
| Value::~Value() | |||
| { | |||
| removeFromListenerList(); | |||
| } | |||
| void Value::removeFromListenerList() | |||
| { | |||
| if (listeners.size() > 0 && value != nullptr) // may be nullptr after a move operation | |||
| value->valuesWithListeners.removeValue (this); | |||
| } | |||
| //============================================================================== | |||
| var Value::getValue() const | |||
| { | |||
| return value->getValue(); | |||
| } | |||
| Value::operator var() const | |||
| { | |||
| return value->getValue(); | |||
| } | |||
| void Value::setValue (const var& newValue) | |||
| { | |||
| value->setValue (newValue); | |||
| } | |||
| String Value::toString() const | |||
| { | |||
| return value->getValue().toString(); | |||
| } | |||
| Value& Value::operator= (const var& newValue) | |||
| { | |||
| value->setValue (newValue); | |||
| return *this; | |||
| } | |||
| void Value::referTo (const Value& valueToReferTo) | |||
| { | |||
| if (valueToReferTo.value != value) | |||
| { | |||
| if (listeners.size() > 0) | |||
| { | |||
| value->valuesWithListeners.removeValue (this); | |||
| valueToReferTo.value->valuesWithListeners.add (this); | |||
| } | |||
| value = valueToReferTo.value; | |||
| callListeners(); | |||
| } | |||
| } | |||
| bool Value::refersToSameSourceAs (const Value& other) const | |||
| { | |||
| return value == other.value; | |||
| } | |||
| bool Value::operator== (const Value& other) const | |||
| { | |||
| return value == other.value || value->getValue() == other.getValue(); | |||
| } | |||
| bool Value::operator!= (const Value& other) const | |||
| { | |||
| return value != other.value && value->getValue() != other.getValue(); | |||
| } | |||
| //============================================================================== | |||
| void Value::addListener (ValueListener* const listener) | |||
| { | |||
| if (listener != nullptr) | |||
| { | |||
| if (listeners.size() == 0) | |||
| value->valuesWithListeners.add (this); | |||
| listeners.add (listener); | |||
| } | |||
| } | |||
| void Value::removeListener (ValueListener* const listener) | |||
| { | |||
| listeners.remove (listener); | |||
| if (listeners.size() == 0) | |||
| value->valuesWithListeners.removeValue (this); | |||
| } | |||
| void Value::callListeners() | |||
| { | |||
| if (listeners.size() > 0) | |||
| { | |||
| Value v (*this); // (create a copy in case this gets deleted by a callback) | |||
| listeners.call (&ValueListener::valueChanged, v); | |||
| } | |||
| } | |||
| OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const Value& value) | |||
| { | |||
| return stream << value.toString(); | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,243 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Represents a shared variant value. | |||
| A Value object contains a reference to a var object, and can get and set its value. | |||
| Listeners can be attached to be told when the value is changed. | |||
| The Value class is a wrapper around a shared, reference-counted underlying data | |||
| object - this means that multiple Value objects can all refer to the same piece of | |||
| data, allowing all of them to be notified when any of them changes it. | |||
| When you create a Value with its default constructor, it acts as a wrapper around a | |||
| simple var object, but by creating a Value that refers to a custom subclass of ValueSource, | |||
| you can map the Value onto any kind of underlying data. | |||
| Important note! The Value class is not thread-safe! If you're accessing one from | |||
| multiple threads, then you'll need to use your own synchronisation around any code | |||
| that accesses it. | |||
| */ | |||
| class JUCE_API Value | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates an empty Value, containing a void var. */ | |||
| Value(); | |||
| /** Creates a Value that refers to the same value as another one. | |||
| Note that this doesn't make a copy of the other value - both this and the other | |||
| Value will share the same underlying value, so that when either one alters it, both | |||
| will see it change. | |||
| */ | |||
| Value (const Value& other); | |||
| /** Creates a Value that is set to the specified value. */ | |||
| explicit Value (const var& initialValue); | |||
| /** Move constructor */ | |||
| Value (Value&&) noexcept; | |||
| /** Destructor. */ | |||
| ~Value(); | |||
| //============================================================================== | |||
| /** Returns the current value. */ | |||
| var getValue() const; | |||
| /** Returns the current value. */ | |||
| operator var() const; | |||
| /** Returns the value as a string. | |||
| This is a shortcut for "myValue.getValue().toString()". | |||
| */ | |||
| String toString() const; | |||
| /** Sets the current value. | |||
| You can also use operator= to set the value. | |||
| If there are any listeners registered, they will be notified of the | |||
| change asynchronously. | |||
| */ | |||
| void setValue (const var& newValue); | |||
| /** Sets the current value. | |||
| This is the same as calling setValue(). | |||
| If there are any listeners registered, they will be notified of the | |||
| change asynchronously. | |||
| */ | |||
| Value& operator= (const var& newValue); | |||
| /** Move assignment operator */ | |||
| Value& operator= (Value&&) noexcept; | |||
| /** Makes this object refer to the same underlying ValueSource as another one. | |||
| Once this object has been connected to another one, changing either one | |||
| will update the other. | |||
| Existing listeners will still be registered after you call this method, and | |||
| they'll continue to receive messages when the new value changes. | |||
| */ | |||
| void referTo (const Value& valueToReferTo); | |||
| /** Returns true if this value and the other one are references to the same value. | |||
| */ | |||
| bool refersToSameSourceAs (const Value& other) const; | |||
| /** Compares two values. | |||
| This is a compare-by-value comparison, so is effectively the same as | |||
| saying (this->getValue() == other.getValue()). | |||
| */ | |||
| bool operator== (const Value& other) const; | |||
| /** Compares two values. | |||
| This is a compare-by-value comparison, so is effectively the same as | |||
| saying (this->getValue() != other.getValue()). | |||
| */ | |||
| bool operator!= (const Value& other) const; | |||
| //============================================================================== | |||
| /** Receives callbacks when a Value object changes. | |||
| @see Value::addListener | |||
| */ | |||
| class JUCE_API Listener | |||
| { | |||
| public: | |||
| Listener() {} | |||
| virtual ~Listener() {} | |||
| /** Called when a Value object is changed. | |||
| Note that the Value object passed as a parameter may not be exactly the same | |||
| object that you registered the listener with - it might be a copy that refers | |||
| to the same underlying ValueSource. To find out, you can call Value::refersToSameSourceAs(). | |||
| */ | |||
| virtual void valueChanged (Value& value) = 0; | |||
| }; | |||
| /** Adds a listener to receive callbacks when the value changes. | |||
| The listener is added to this specific Value object, and not to the shared | |||
| object that it refers to. When this object is deleted, all the listeners will | |||
| be lost, even if other references to the same Value still exist. So when you're | |||
| adding a listener, make sure that you add it to a Value instance that will last | |||
| for as long as you need the listener. In general, you'd never want to add a listener | |||
| to a local stack-based Value, but more likely to one that's a member variable. | |||
| @see removeListener | |||
| */ | |||
| void addListener (Listener* listener); | |||
| /** Removes a listener that was previously added with addListener(). */ | |||
| void removeListener (Listener* listener); | |||
| //============================================================================== | |||
| /** | |||
| Used internally by the Value class as the base class for its shared value objects. | |||
| The Value class is essentially a reference-counted pointer to a shared instance | |||
| of a ValueSource object. If you're feeling adventurous, you can create your own custom | |||
| ValueSource classes to allow Value objects to represent your own custom data items. | |||
| */ | |||
| class JUCE_API ValueSource : public ReferenceCountedObject, | |||
| private AsyncUpdater | |||
| { | |||
| public: | |||
| ValueSource(); | |||
| virtual ~ValueSource(); | |||
| /** Returns the current value of this object. */ | |||
| virtual var getValue() const = 0; | |||
| /** Changes the current value. | |||
| This must also trigger a change message if the value actually changes. | |||
| */ | |||
| virtual void setValue (const var& newValue) = 0; | |||
| /** Delivers a change message to all the listeners that are registered with | |||
| this value. | |||
| If dispatchSynchronously is true, the method will call all the listeners | |||
| before returning; otherwise it'll dispatch a message and make the call later. | |||
| */ | |||
| void sendChangeMessage (bool dispatchSynchronously); | |||
| protected: | |||
| //============================================================================== | |||
| friend class Value; | |||
| SortedSet<Value*> valuesWithListeners; | |||
| private: | |||
| void handleAsyncUpdate() override; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueSource) | |||
| }; | |||
| //============================================================================== | |||
| /** Creates a Value object that uses this valueSource object as its underlying data. */ | |||
| explicit Value (ValueSource* valueSource); | |||
| /** Returns the ValueSource that this value is referring to. */ | |||
| ValueSource& getValueSource() noexcept { return *value; } | |||
| private: | |||
| //============================================================================== | |||
| friend class ValueSource; | |||
| ReferenceCountedObjectPtr<ValueSource> value; | |||
| ListenerList<Listener> listeners; | |||
| void callListeners(); | |||
| void removeFromListenerList(); | |||
| // This is disallowed to avoid confusion about whether it should | |||
| // do a by-value or by-reference copy. | |||
| Value& operator= (const Value&) JUCE_DELETED_FUNCTION; | |||
| // This declaration prevents accidental construction from an integer of 0, | |||
| // which is possible in some compilers via an implicit cast to a pointer. | |||
| explicit Value (void*) JUCE_DELETED_FUNCTION; | |||
| }; | |||
| /** Writes a Value to an OutputStream as a UTF8 string. */ | |||
| OutputStream& JUCE_CALLTYPE operator<< (OutputStream&, const Value&); | |||
| /** This typedef is just for compatibility with old code - newer code should use the Value::Listener class directly. */ | |||
| typedef Value::Listener ValueListener; | |||
| } // namespace juce | |||
| @@ -0,0 +1,578 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| A powerful tree structure that can be used to hold free-form data, and which can | |||
| handle its own undo and redo behaviour. | |||
| A ValueTree contains a list of named properties as var objects, and also holds | |||
| any number of sub-trees. | |||
| Create ValueTree objects on the stack, and don't be afraid to copy them around, as | |||
| they're simply a lightweight reference to a shared data container. Creating a copy | |||
| of another ValueTree simply creates a new reference to the same underlying object - to | |||
| make a separate, deep copy of a tree you should explicitly call createCopy(). | |||
| Each ValueTree has a type name, in much the same way as an XmlElement has a tag name, | |||
| and much of the structure of a ValueTree is similar to an XmlElement tree. | |||
| You can convert a ValueTree to and from an XmlElement, and as long as the XML doesn't | |||
| contain text elements, the conversion works well and makes a good serialisation | |||
| format. They can also be serialised to a binary format, which is very fast and compact. | |||
| All the methods that change data take an optional UndoManager, which will be used | |||
| to track any changes to the object. For this to work, you have to be careful to | |||
| consistently always use the same UndoManager for all operations to any node inside | |||
| the tree. | |||
| A ValueTree can only be a child of one parent at a time, so if you're moving one from | |||
| one tree to another, be careful to always remove it first, before adding it. This | |||
| could also mess up your undo/redo chain, so be wary! In a debug build you should hit | |||
| assertions if you try to do anything dangerous, but there are still plenty of ways it | |||
| could go wrong. | |||
| Note that although the children in a tree have a fixed order, the properties are not | |||
| guaranteed to be stored in any particular order, so don't expect that a property's index | |||
| will correspond to the order in which the property was added, or that it will remain | |||
| constant when other properties are added or removed. | |||
| Listeners can be added to a ValueTree to be told when properies change and when | |||
| nodes are added or removed. | |||
| @see var, XmlElement | |||
| */ | |||
| class JUCE_API ValueTree | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates an empty, invalid ValueTree. | |||
| A ValueTree that is created with this constructor can't actually be used for anything, | |||
| it's just a default 'null' ValueTree that can be returned to indicate some sort of failure. | |||
| To create a real one, use the constructor that takes a string. | |||
| */ | |||
| ValueTree() noexcept; | |||
| /** Creates an empty ValueTree with the given type name. | |||
| Like an XmlElement, each ValueTree node has a type, which you can access with | |||
| getType() and hasType(). | |||
| */ | |||
| explicit ValueTree (const Identifier& type); | |||
| /** Creates a reference to another ValueTree. */ | |||
| ValueTree (const ValueTree&) noexcept; | |||
| /** Makes this object reference another node. */ | |||
| ValueTree& operator= (const ValueTree&); | |||
| /** Move constructor */ | |||
| ValueTree (ValueTree&&) noexcept; | |||
| /** Destructor. */ | |||
| ~ValueTree(); | |||
| /** Returns true if both this and the other tree node refer to the same underlying structure. | |||
| Note that this isn't a value comparison - two independently-created trees which | |||
| contain identical data are not considered equal. | |||
| */ | |||
| bool operator== (const ValueTree&) const noexcept; | |||
| /** Returns true if this and the other node refer to different underlying structures. | |||
| Note that this isn't a value comparison - two independently-created trees which | |||
| contain identical data are not considered equal. | |||
| */ | |||
| bool operator!= (const ValueTree&) const noexcept; | |||
| /** Performs a deep comparison between the properties and children of two trees. | |||
| If all the properties and children of the two trees are the same (recursively), this | |||
| returns true. | |||
| The normal operator==() only checks whether two trees refer to the same shared data | |||
| structure, so use this method if you need to do a proper value comparison. | |||
| */ | |||
| bool isEquivalentTo (const ValueTree&) const; | |||
| //============================================================================== | |||
| /** Returns true if this node refers to some valid data. | |||
| It's hard to create an invalid node, but you might get one returned, e.g. by an out-of-range | |||
| call to getChild(). | |||
| */ | |||
| bool isValid() const noexcept { return object != nullptr; } | |||
| /** Returns a deep copy of this tree and all its sub-nodes. */ | |||
| ValueTree createCopy() const; | |||
| //============================================================================== | |||
| /** Returns the type of this node. | |||
| The type is specified when the ValueTree is created. | |||
| @see hasType | |||
| */ | |||
| Identifier getType() const noexcept; | |||
| /** Returns true if the node has this type. | |||
| The comparison is case-sensitive. | |||
| */ | |||
| bool hasType (const Identifier& typeName) const noexcept; | |||
| //============================================================================== | |||
| /** Returns the value of a named property. | |||
| If no such property has been set, this will return a void variant. | |||
| You can also use operator[] to get a property. | |||
| @see var, setProperty, getPropertyPointer, hasProperty | |||
| */ | |||
| const var& getProperty (const Identifier& name) const noexcept; | |||
| /** Returns the value of a named property, or the value of defaultReturnValue | |||
| if the property doesn't exist. | |||
| You can also use operator[] and getProperty to get a property. | |||
| @see var, getProperty, getPropertyPointer, setProperty, hasProperty | |||
| */ | |||
| var getProperty (const Identifier& name, const var& defaultReturnValue) const; | |||
| /** Returns a pointer to the value of a named property, or nullptr if the property | |||
| doesn't exist. | |||
| @see var, getProperty, setProperty, hasProperty | |||
| */ | |||
| const var* getPropertyPointer (const Identifier& name) const noexcept; | |||
| /** Returns the value of a named property. | |||
| If no such property has been set, this will return a void variant. This is the same as | |||
| calling getProperty(). | |||
| @see getProperty | |||
| */ | |||
| const var& operator[] (const Identifier& name) const noexcept; | |||
| /** Changes a named property of the node. | |||
| The name identifier must not be an empty string. | |||
| If the undoManager parameter is non-null, its UndoManager::perform() method will be used, | |||
| so that this change can be undone. | |||
| @see var, getProperty, removeProperty | |||
| @returns a reference to the value tree, so that you can daisy-chain calls to this method. | |||
| */ | |||
| ValueTree& setProperty (const Identifier& name, const var& newValue, UndoManager* undoManager); | |||
| /** Returns true if the node contains a named property. */ | |||
| bool hasProperty (const Identifier& name) const noexcept; | |||
| /** Removes a property from the node. | |||
| If the undoManager parameter is non-null, its UndoManager::perform() method will be used, | |||
| so that this change can be undone. | |||
| */ | |||
| void removeProperty (const Identifier& name, UndoManager* undoManager); | |||
| /** Removes all properties from the node. | |||
| If the undoManager parameter is non-null, its UndoManager::perform() method will be used, | |||
| so that this change can be undone. | |||
| */ | |||
| void removeAllProperties (UndoManager* undoManager); | |||
| /** Returns the total number of properties that the node contains. | |||
| @see getProperty. | |||
| */ | |||
| int getNumProperties() const noexcept; | |||
| /** Returns the identifier of the property with a given index. | |||
| Note that properties are not guaranteed to be stored in any particular order, so don't | |||
| expect that the index will correspond to the order in which the property was added, or | |||
| that it will remain constant when other properties are added or removed. | |||
| @see getNumProperties | |||
| */ | |||
| Identifier getPropertyName (int index) const noexcept; | |||
| /** Returns a Value object that can be used to control and respond to one of the tree's properties. | |||
| The Value object will maintain a reference to this tree, and will use the undo manager when | |||
| it needs to change the value. Attaching a Value::Listener to the value object will provide | |||
| callbacks whenever the property changes. | |||
| */ | |||
| Value getPropertyAsValue (const Identifier& name, UndoManager* undoManager); | |||
| /** Overwrites all the properties in this tree with the properties of the source tree. | |||
| Any properties that already exist will be updated; and new ones will be added, and | |||
| any that are not present in the source tree will be removed. | |||
| */ | |||
| void copyPropertiesFrom (const ValueTree& source, UndoManager* undoManager); | |||
| //============================================================================== | |||
| /** Returns the number of child nodes belonging to this one. | |||
| @see getChild | |||
| */ | |||
| int getNumChildren() const noexcept; | |||
| /** Returns one of this node's child nodes. | |||
| If the index is out of range, it'll return an invalid node. (See isValid() to find out | |||
| whether a node is valid). | |||
| */ | |||
| ValueTree getChild (int index) const; | |||
| /** Returns the first child node with the specified type name. | |||
| If no such node is found, it'll return an invalid node. (See isValid() to find out | |||
| whether a node is valid). | |||
| @see getOrCreateChildWithName | |||
| */ | |||
| ValueTree getChildWithName (const Identifier& type) const; | |||
| /** Returns the first child node with the specified type name, creating and adding | |||
| a child with this name if there wasn't already one there. | |||
| The only time this will return an invalid object is when the object that you're calling | |||
| the method on is itself invalid. | |||
| @see getChildWithName | |||
| */ | |||
| ValueTree getOrCreateChildWithName (const Identifier& type, UndoManager* undoManager); | |||
| /** Looks for the first child node that has the specified property value. | |||
| This will scan the child nodes in order, until it finds one that has property that matches | |||
| the specified value. | |||
| If no such node is found, it'll return an invalid node. (See isValid() to find out | |||
| whether a node is valid). | |||
| */ | |||
| ValueTree getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const; | |||
| /** Adds a child to this node. | |||
| Make sure that the child is removed from any former parent node before calling this, or | |||
| you'll hit an assertion. | |||
| If the index is < 0 or greater than the current number of child nodes, the new node will | |||
| be added at the end of the list. | |||
| If the undoManager parameter is non-null, its UndoManager::perform() method will be used, | |||
| so that this change can be undone. | |||
| */ | |||
| void addChild (const ValueTree& child, int index, UndoManager* undoManager); | |||
| /** Removes the specified child from this node's child-list. | |||
| If the undoManager parameter is non-null, its UndoManager::perform() method will be used, | |||
| so that this change can be undone. | |||
| */ | |||
| void removeChild (const ValueTree& child, UndoManager* undoManager); | |||
| /** Removes a child from this node's child-list. | |||
| If the undoManager parameter is non-null, its UndoManager::perform() method will be used, | |||
| so that this change can be undone. | |||
| */ | |||
| void removeChild (int childIndex, UndoManager* undoManager); | |||
| /** Removes all child-nodes from this node. | |||
| If the undoManager parameter is non-null, its UndoManager::perform() method will be used, | |||
| so that this change can be undone. | |||
| */ | |||
| void removeAllChildren (UndoManager* undoManager); | |||
| /** Moves one of the children to a different index. | |||
| This will move the child to a specified index, shuffling along any intervening | |||
| items as required. So for example, if you have a list of { 0, 1, 2, 3, 4, 5 }, then | |||
| calling move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. | |||
| @param currentIndex the index of the item to be moved. If this isn't a | |||
| valid index, then nothing will be done | |||
| @param newIndex the index at which you'd like this item to end up. If this | |||
| is less than zero, the value will be moved to the end | |||
| of the list | |||
| @param undoManager the optional UndoManager to use to store this transaction | |||
| */ | |||
| void moveChild (int currentIndex, int newIndex, UndoManager* undoManager); | |||
| /** Returns true if this node is anywhere below the specified parent node. | |||
| This returns true if the node is a child-of-a-child, as well as a direct child. | |||
| */ | |||
| bool isAChildOf (const ValueTree& possibleParent) const noexcept; | |||
| /** Returns the index of a child item in this parent. | |||
| If the child isn't found, this returns -1. | |||
| */ | |||
| int indexOf (const ValueTree& child) const noexcept; | |||
| /** Returns the parent node that contains this one. | |||
| If the node has no parent, this will return an invalid node. (See isValid() to find out | |||
| whether a node is valid). | |||
| */ | |||
| ValueTree getParent() const noexcept; | |||
| /** Recusrively finds the highest-level parent node that contains this one. | |||
| If the node has no parent, this will return itself. | |||
| */ | |||
| ValueTree getRoot() const noexcept; | |||
| /** Returns one of this node's siblings in its parent's child list. | |||
| The delta specifies how far to move through the list, so a value of 1 would return the node | |||
| that follows this one, -1 would return the node before it, 0 will return this node itself, etc. | |||
| If the requested position is beyond the range of available nodes, this will return an empty ValueTree(). | |||
| */ | |||
| ValueTree getSibling (int delta) const noexcept; | |||
| //============================================================================== | |||
| struct Iterator | |||
| { | |||
| Iterator (const ValueTree&, bool isEnd) noexcept; | |||
| Iterator& operator++() noexcept; | |||
| bool operator!= (const Iterator&) const noexcept; | |||
| ValueTree operator*() const; | |||
| private: | |||
| void* internal; | |||
| }; | |||
| /** Returns a start iterator for the children in this tree. */ | |||
| Iterator begin() const noexcept; | |||
| /** Returns an end iterator for the children in this tree. */ | |||
| Iterator end() const noexcept; | |||
| //============================================================================== | |||
| /** Creates an XmlElement that holds a complete image of this node and all its children. | |||
| If this node is invalid, this may return nullptr. Otherwise, the XML that is produced can | |||
| be used to recreate a similar node by calling fromXml(). | |||
| The caller must delete the object that is returned. | |||
| @see fromXml | |||
| */ | |||
| XmlElement* createXml() const; | |||
| /** Tries to recreate a node from its XML representation. | |||
| This isn't designed to cope with random XML data - for a sensible result, it should only | |||
| be fed XML that was created by the createXml() method. | |||
| */ | |||
| static ValueTree fromXml (const XmlElement& xml); | |||
| /** This returns a string containing an XML representation of the tree. | |||
| This is quite handy for debugging purposes, as it provides a quick way to view a tree. | |||
| */ | |||
| String toXmlString() const; | |||
| //============================================================================== | |||
| /** Stores this tree (and all its children) in a binary format. | |||
| Once written, the data can be read back with readFromStream(). | |||
| It's much faster to load/save your tree in binary form than as XML, but | |||
| obviously isn't human-readable. | |||
| */ | |||
| void writeToStream (OutputStream& output) const; | |||
| /** Reloads a tree from a stream that was written with writeToStream(). */ | |||
| static ValueTree readFromStream (InputStream& input); | |||
| /** Reloads a tree from a data block that was written with writeToStream(). */ | |||
| static ValueTree readFromData (const void* data, size_t numBytes); | |||
| /** Reloads a tree from a data block that was written with writeToStream() and | |||
| then zipped using GZIPCompressorOutputStream. | |||
| */ | |||
| static ValueTree readFromGZIPData (const void* data, size_t numBytes); | |||
| //============================================================================== | |||
| /** Listener class for events that happen to a ValueTree. | |||
| To get events from a ValueTree, make your class implement this interface, and use | |||
| ValueTree::addListener() and ValueTree::removeListener() to register it. | |||
| */ | |||
| class JUCE_API Listener | |||
| { | |||
| public: | |||
| /** Destructor. */ | |||
| virtual ~Listener() {} | |||
| /** This method is called when a property of this node (or of one of its sub-nodes) has | |||
| changed. | |||
| The tree parameter indicates which tree has had its property changed, and the property | |||
| parameter indicates the property. | |||
| Note that when you register a listener to a tree, it will receive this callback for | |||
| property changes in that tree, and also for any of its children, (recursively, at any depth). | |||
| If your tree has sub-trees but you only want to know about changes to the top level tree, | |||
| simply check the tree parameter in this callback to make sure it's the tree you're interested in. | |||
| */ | |||
| virtual void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged, | |||
| const Identifier& property) = 0; | |||
| /** This method is called when a child sub-tree is added. | |||
| Note that when you register a listener to a tree, it will receive this callback for | |||
| child changes in both that tree and any of its children, (recursively, at any depth). | |||
| If your tree has sub-trees but you only want to know about changes to the top level tree, | |||
| just check the parentTree parameter to make sure it's the one that you're interested in. | |||
| */ | |||
| virtual void valueTreeChildAdded (ValueTree& parentTree, | |||
| ValueTree& childWhichHasBeenAdded) = 0; | |||
| /** This method is called when a child sub-tree is removed. | |||
| Note that when you register a listener to a tree, it will receive this callback for | |||
| child changes in both that tree and any of its children, (recursively, at any depth). | |||
| If your tree has sub-trees but you only want to know about changes to the top level tree, | |||
| just check the parentTree parameter to make sure it's the one that you're interested in. | |||
| */ | |||
| virtual void valueTreeChildRemoved (ValueTree& parentTree, | |||
| ValueTree& childWhichHasBeenRemoved, | |||
| int indexFromWhichChildWasRemoved) = 0; | |||
| /** This method is called when a tree's children have been re-shuffled. | |||
| Note that when you register a listener to a tree, it will receive this callback for | |||
| child changes in both that tree and any of its children, (recursively, at any depth). | |||
| If your tree has sub-trees but you only want to know about changes to the top level tree, | |||
| just check the parameter to make sure it's the tree that you're interested in. | |||
| */ | |||
| virtual void valueTreeChildOrderChanged (ValueTree& parentTreeWhoseChildrenHaveMoved, | |||
| int oldIndex, int newIndex) = 0; | |||
| /** This method is called when a tree has been added or removed from a parent node. | |||
| This callback happens when the tree to which the listener was registered is added or | |||
| removed from a parent. Unlike the other callbacks, it applies only to the tree to which | |||
| the listener is registered, and not to any of its children. | |||
| */ | |||
| virtual void valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged) = 0; | |||
| /** This method is called when a tree is made to point to a different internal shared object. | |||
| When operator= is used to make a ValueTree refer to a different object, this callback | |||
| will be made. | |||
| */ | |||
| virtual void valueTreeRedirected (ValueTree& treeWhichHasBeenChanged); | |||
| }; | |||
| /** Adds a listener to receive callbacks when this node is changed. | |||
| The listener is added to this specific ValueTree object, and not to the shared | |||
| object that it refers to. When this object is deleted, all the listeners will | |||
| be lost, even if other references to the same ValueTree still exist. And if you | |||
| use the operator= to make this refer to a different ValueTree, any listeners will | |||
| begin listening to changes to the new tree instead of the old one. | |||
| When you're adding a listener, make sure that you add it to a ValueTree instance that | |||
| will last for as long as you need the listener. In general, you'd never want to add a | |||
| listener to a local stack-based ValueTree, and would usually add one to a member variable. | |||
| @see removeListener | |||
| */ | |||
| void addListener (Listener* listener); | |||
| /** Removes a listener that was previously added with addListener(). */ | |||
| void removeListener (Listener* listener); | |||
| /** Changes a named property of the node, but will not notify a specified listener of the change. | |||
| @see setProperty | |||
| */ | |||
| ValueTree& setPropertyExcludingListener (Listener* listenerToExclude, | |||
| const Identifier& name, const var& newValue, | |||
| UndoManager* undoManager); | |||
| /** Causes a property-change callback to be triggered for the specified property, | |||
| calling any listeners that are registered. | |||
| */ | |||
| void sendPropertyChangeMessage (const Identifier& property); | |||
| //============================================================================== | |||
| /** This method uses a comparator object to sort the tree's children into order. | |||
| The object provided must have a method of the form: | |||
| @code | |||
| int compareElements (const ValueTree& first, const ValueTree& second); | |||
| @endcode | |||
| ..and this method must return: | |||
| - a value of < 0 if the first comes before the second | |||
| - a value of 0 if the two objects are equivalent | |||
| - a value of > 0 if the second comes before the first | |||
| To improve performance, the compareElements() method can be declared as static or const. | |||
| @param comparator the comparator to use for comparing elements. | |||
| @param undoManager optional UndoManager for storing the changes | |||
| @param retainOrderOfEquivalentItems if this is true, then items which the comparator says are | |||
| equivalent will be kept in the order in which they currently appear in the array. | |||
| This is slower to perform, but may be important in some cases. If it's false, a | |||
| faster algorithm is used, but equivalent elements may be rearranged. | |||
| */ | |||
| template <typename ElementComparator> | |||
| void sort (ElementComparator& comparator, UndoManager* undoManager, bool retainOrderOfEquivalentItems) | |||
| { | |||
| if (object != nullptr) | |||
| { | |||
| OwnedArray<ValueTree> sortedList; | |||
| createListOfChildren (sortedList); | |||
| ComparatorAdapter<ElementComparator> adapter (comparator); | |||
| sortedList.sort (adapter, retainOrderOfEquivalentItems); | |||
| reorderChildren (sortedList, undoManager); | |||
| } | |||
| } | |||
| #if JUCE_ALLOW_STATIC_NULL_VARIABLES | |||
| /** An invalid ValueTree that can be used if you need to return one as an error condition, etc. | |||
| This invalid object is equivalent to ValueTree created with its default constructor, but | |||
| you should always prefer to avoid it and use ValueTree() or {} instead. | |||
| */ | |||
| static const ValueTree invalid; | |||
| #endif | |||
| /** Returns the total number of references to the shared underlying data structure that this | |||
| ValueTree is using. | |||
| */ | |||
| int getReferenceCount() const noexcept; | |||
| private: | |||
| //============================================================================== | |||
| JUCE_PUBLIC_IN_DLL_BUILD (class SharedObject) | |||
| friend class SharedObject; | |||
| ReferenceCountedObjectPtr<SharedObject> object; | |||
| ListenerList<Listener> listeners; | |||
| template <typename ElementComparator> | |||
| struct ComparatorAdapter | |||
| { | |||
| ComparatorAdapter (ElementComparator& comp) noexcept : comparator (comp) {} | |||
| int compareElements (const ValueTree* const first, const ValueTree* const second) | |||
| { | |||
| return comparator.compareElements (*first, *second); | |||
| } | |||
| private: | |||
| ElementComparator& comparator; | |||
| JUCE_DECLARE_NON_COPYABLE (ComparatorAdapter) | |||
| }; | |||
| void createListOfChildren (OwnedArray<ValueTree>&) const; | |||
| void reorderChildren (const OwnedArray<ValueTree>&, UndoManager*); | |||
| explicit ValueTree (SharedObject*) noexcept; | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,242 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| namespace ValueTreeSynchroniserHelpers | |||
| { | |||
| enum ChangeType | |||
| { | |||
| propertyChanged = 1, | |||
| fullSync = 2, | |||
| childAdded = 3, | |||
| childRemoved = 4, | |||
| childMoved = 5, | |||
| propertyRemoved = 6 | |||
| }; | |||
| static void getValueTreePath (ValueTree v, const ValueTree& topLevelTree, Array<int>& path) | |||
| { | |||
| while (v != topLevelTree) | |||
| { | |||
| ValueTree parent (v.getParent()); | |||
| if (! parent.isValid()) | |||
| break; | |||
| path.add (parent.indexOf (v)); | |||
| v = parent; | |||
| } | |||
| } | |||
| static void writeHeader (MemoryOutputStream& stream, ChangeType type) | |||
| { | |||
| stream.writeByte ((char) type); | |||
| } | |||
| static void writeHeader (ValueTreeSynchroniser& target, MemoryOutputStream& stream, | |||
| ChangeType type, ValueTree v) | |||
| { | |||
| writeHeader (stream, type); | |||
| Array<int> path; | |||
| getValueTreePath (v, target.getRoot(), path); | |||
| stream.writeCompressedInt (path.size()); | |||
| for (int i = path.size(); --i >= 0;) | |||
| stream.writeCompressedInt (path.getUnchecked(i)); | |||
| } | |||
| static ValueTree readSubTreeLocation (MemoryInputStream& input, ValueTree v) | |||
| { | |||
| const int numLevels = input.readCompressedInt(); | |||
| if (! isPositiveAndBelow (numLevels, 65536)) // sanity-check | |||
| return {}; | |||
| for (int i = numLevels; --i >= 0;) | |||
| { | |||
| const int index = input.readCompressedInt(); | |||
| if (! isPositiveAndBelow (index, v.getNumChildren())) | |||
| return {}; | |||
| v = v.getChild (index); | |||
| } | |||
| return v; | |||
| } | |||
| } | |||
| ValueTreeSynchroniser::ValueTreeSynchroniser (const ValueTree& tree) : valueTree (tree) | |||
| { | |||
| valueTree.addListener (this); | |||
| } | |||
| ValueTreeSynchroniser::~ValueTreeSynchroniser() | |||
| { | |||
| valueTree.removeListener (this); | |||
| } | |||
| void ValueTreeSynchroniser::sendFullSyncCallback() | |||
| { | |||
| MemoryOutputStream m; | |||
| writeHeader (m, ValueTreeSynchroniserHelpers::fullSync); | |||
| valueTree.writeToStream (m); | |||
| stateChanged (m.getData(), m.getDataSize()); | |||
| } | |||
| void ValueTreeSynchroniser::valueTreePropertyChanged (ValueTree& vt, const Identifier& property) | |||
| { | |||
| MemoryOutputStream m; | |||
| if (auto* value = vt.getPropertyPointer (property)) | |||
| { | |||
| ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::propertyChanged, vt); | |||
| m.writeString (property.toString()); | |||
| value->writeToStream (m); | |||
| } | |||
| else | |||
| { | |||
| ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::propertyRemoved, vt); | |||
| m.writeString (property.toString()); | |||
| } | |||
| stateChanged (m.getData(), m.getDataSize()); | |||
| } | |||
| void ValueTreeSynchroniser::valueTreeChildAdded (ValueTree& parentTree, ValueTree& childTree) | |||
| { | |||
| const int index = parentTree.indexOf (childTree); | |||
| jassert (index >= 0); | |||
| MemoryOutputStream m; | |||
| ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::childAdded, parentTree); | |||
| m.writeCompressedInt (index); | |||
| childTree.writeToStream (m); | |||
| stateChanged (m.getData(), m.getDataSize()); | |||
| } | |||
| void ValueTreeSynchroniser::valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int oldIndex) | |||
| { | |||
| MemoryOutputStream m; | |||
| ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::childRemoved, parentTree); | |||
| m.writeCompressedInt (oldIndex); | |||
| stateChanged (m.getData(), m.getDataSize()); | |||
| } | |||
| void ValueTreeSynchroniser::valueTreeChildOrderChanged (ValueTree& parent, int oldIndex, int newIndex) | |||
| { | |||
| MemoryOutputStream m; | |||
| ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::childMoved, parent); | |||
| m.writeCompressedInt (oldIndex); | |||
| m.writeCompressedInt (newIndex); | |||
| stateChanged (m.getData(), m.getDataSize()); | |||
| } | |||
| void ValueTreeSynchroniser::valueTreeParentChanged (ValueTree&) {} // (No action needed here) | |||
| bool ValueTreeSynchroniser::applyChange (ValueTree& root, const void* data, size_t dataSize, UndoManager* undoManager) | |||
| { | |||
| MemoryInputStream input (data, dataSize, false); | |||
| const ValueTreeSynchroniserHelpers::ChangeType type = (ValueTreeSynchroniserHelpers::ChangeType) input.readByte(); | |||
| if (type == ValueTreeSynchroniserHelpers::fullSync) | |||
| { | |||
| root = ValueTree::readFromStream (input); | |||
| return true; | |||
| } | |||
| ValueTree v (ValueTreeSynchroniserHelpers::readSubTreeLocation (input, root)); | |||
| if (! v.isValid()) | |||
| return false; | |||
| switch (type) | |||
| { | |||
| case ValueTreeSynchroniserHelpers::propertyChanged: | |||
| { | |||
| Identifier property (input.readString()); | |||
| v.setProperty (property, var::readFromStream (input), undoManager); | |||
| return true; | |||
| } | |||
| case ValueTreeSynchroniserHelpers::propertyRemoved: | |||
| { | |||
| Identifier property (input.readString()); | |||
| v.removeProperty (property, undoManager); | |||
| return true; | |||
| } | |||
| case ValueTreeSynchroniserHelpers::childAdded: | |||
| { | |||
| const int index = input.readCompressedInt(); | |||
| v.addChild (ValueTree::readFromStream (input), index, undoManager); | |||
| return true; | |||
| } | |||
| case ValueTreeSynchroniserHelpers::childRemoved: | |||
| { | |||
| const int index = input.readCompressedInt(); | |||
| if (isPositiveAndBelow (index, v.getNumChildren())) | |||
| { | |||
| v.removeChild (index, undoManager); | |||
| return true; | |||
| } | |||
| jassertfalse; // Either received some corrupt data, or the trees have drifted out of sync | |||
| break; | |||
| } | |||
| case ValueTreeSynchroniserHelpers::childMoved: | |||
| { | |||
| const int oldIndex = input.readCompressedInt(); | |||
| const int newIndex = input.readCompressedInt(); | |||
| if (isPositiveAndBelow (oldIndex, v.getNumChildren()) | |||
| && isPositiveAndBelow (newIndex, v.getNumChildren())) | |||
| { | |||
| v.moveChild (oldIndex, newIndex, undoManager); | |||
| return true; | |||
| } | |||
| jassertfalse; // Either received some corrupt data, or the trees have drifted out of sync | |||
| break; | |||
| } | |||
| default: | |||
| jassertfalse; // Seem to have received some corrupt data? | |||
| break; | |||
| } | |||
| return false; | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,97 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| This class can be used to watch for all changes to the state of a ValueTree, | |||
| and to convert them to a transmittable binary encoding. | |||
| The purpose of this class is to allow two or more ValueTrees to be remotely | |||
| synchronised by transmitting encoded changes over some kind of transport | |||
| mechanism. | |||
| To use it, you'll need to implement a subclass of ValueTreeSynchroniser | |||
| and implement the stateChanged() method to transmit the encoded change (maybe | |||
| via a network or other means) to a remote destination, where it can be | |||
| applied to a target tree. | |||
| */ | |||
| class JUCE_API ValueTreeSynchroniser : private ValueTree::Listener | |||
| { | |||
| public: | |||
| /** Creates a ValueTreeSynchroniser that watches the given tree. | |||
| After creating an instance of this class and somehow attaching it to | |||
| a target tree, you probably want to call sendFullSyncCallback() to | |||
| get them into a common starting state. | |||
| */ | |||
| ValueTreeSynchroniser (const ValueTree& tree); | |||
| /** Destructor. */ | |||
| virtual ~ValueTreeSynchroniser(); | |||
| /** This callback happens when the ValueTree changes and the given state-change message | |||
| needs to be applied to any other trees that need to stay in sync with it. | |||
| The data is an opaque blob of binary that you should transmit to wherever your | |||
| target tree lives, and use applyChange() to apply this to the target tree. | |||
| */ | |||
| virtual void stateChanged (const void* encodedChange, size_t encodedChangeSize) = 0; | |||
| /** Forces the sending of a full state message, which may be large, as it | |||
| encodes the entire ValueTree. | |||
| This will internally invoke stateChanged() with the encoded version of the state. | |||
| */ | |||
| void sendFullSyncCallback(); | |||
| /** Applies an encoded change to the given destination tree. | |||
| When you implement a receiver for changes that were sent by the stateChanged() | |||
| message, this is the function that you'll need to call to apply them to the | |||
| target tree that you want to be synced. | |||
| */ | |||
| static bool applyChange (ValueTree& target, | |||
| const void* encodedChangeData, size_t encodedChangeDataSize, | |||
| UndoManager* undoManager); | |||
| /** Returns the root ValueTree that is being observed. */ | |||
| const ValueTree& getRoot() noexcept { return valueTree; } | |||
| private: | |||
| ValueTree valueTree; | |||
| void valueTreePropertyChanged (ValueTree&, const Identifier&) override; | |||
| void valueTreeChildAdded (ValueTree&, ValueTree&) override; | |||
| void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override; | |||
| void valueTreeChildOrderChanged (ValueTree&, int, int) override; | |||
| void valueTreeParentChanged (ValueTree&) override; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueTreeSynchroniser) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,123 @@ | |||
| #!/usr/bin/make -f | |||
| # Makefile for juce_events # | |||
| # ------------------------ # | |||
| # Created by falkTX | |||
| # | |||
| CWD=../.. | |||
| MODULENAME=juce_events | |||
| include ../Makefile.mk | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| BUILD_CXX_FLAGS += $(JUCE_EVENTS_FLAGS) -I.. | |||
| ifeq ($(WIN32),true) | |||
| BUILD_CXX_FLAGS += -Wno-missing-field-initializers | |||
| endif | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| ifeq ($(MACOS),true) | |||
| OBJS = $(OBJDIR)/$(MODULENAME).mm.o | |||
| OBJS_posix32 = $(OBJDIR)/$(MODULENAME).mm.posix32.o | |||
| OBJS_posix64 = $(OBJDIR)/$(MODULENAME).mm.posix64.o | |||
| else | |||
| OBJS = $(OBJDIR)/$(MODULENAME).cpp.o | |||
| OBJS_posix32 = $(OBJDIR)/$(MODULENAME).cpp.posix32.o | |||
| OBJS_posix64 = $(OBJDIR)/$(MODULENAME).cpp.posix64.o | |||
| endif | |||
| OBJS_win32 = $(OBJDIR)/$(MODULENAME).cpp.win32.o | |||
| OBJS_win64 = $(OBJDIR)/$(MODULENAME).cpp.win64.o | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| all: $(MODULEDIR)/$(MODULENAME).a | |||
| posix32: $(MODULEDIR)/$(MODULENAME).posix32.a | |||
| posix64: $(MODULEDIR)/$(MODULENAME).posix64.a | |||
| win32: $(MODULEDIR)/$(MODULENAME).win32.a | |||
| win64: $(MODULEDIR)/$(MODULENAME).win64.a | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| clean: | |||
| rm -f $(OBJDIR)/*.o $(MODULEDIR)/$(MODULENAME)*.a | |||
| debug: | |||
| $(MAKE) DEBUG=true | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| $(MODULEDIR)/$(MODULENAME).a: $(OBJS) | |||
| -@mkdir -p $(MODULEDIR) | |||
| @echo "Creating $(MODULENAME).a" | |||
| @rm -f $@ | |||
| @$(AR) crs $@ $^ | |||
| $(MODULEDIR)/$(MODULENAME).posix32.a: $(OBJS_posix32) | |||
| -@mkdir -p $(MODULEDIR) | |||
| @echo "Creating $(MODULENAME).posix32.a" | |||
| @rm -f $@ | |||
| @$(AR) crs $@ $^ | |||
| $(MODULEDIR)/$(MODULENAME).posix64.a: $(OBJS_posix64) | |||
| -@mkdir -p $(MODULEDIR) | |||
| @echo "Creating $(MODULENAME).posix64.a" | |||
| @rm -f $@ | |||
| @$(AR) crs $@ $^ | |||
| $(MODULEDIR)/$(MODULENAME).win32.a: $(OBJS_win32) | |||
| -@mkdir -p $(MODULEDIR) | |||
| @echo "Creating $(MODULENAME).win32.a" | |||
| @rm -f $@ | |||
| @$(AR) crs $@ $^ | |||
| $(MODULEDIR)/$(MODULENAME).win64.a: $(OBJS_win64) | |||
| -@mkdir -p $(MODULEDIR) | |||
| @echo "Creating $(MODULENAME).win64.a" | |||
| @rm -f $@ | |||
| @$(AR) crs $@ $^ | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| $(OBJDIR)/$(MODULENAME).cpp.o: $(MODULENAME).cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $<" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | |||
| $(OBJDIR)/$(MODULENAME).cpp.%32.o: $(MODULENAME).cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $< (32bit)" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ | |||
| $(OBJDIR)/$(MODULENAME).cpp.%64.o: $(MODULENAME).cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $< (64bit)" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| $(OBJDIR)/$(MODULENAME).mm.o: $(MODULENAME).cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $<" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ | |||
| $(OBJDIR)/$(MODULENAME).mm.%32.o: $(MODULENAME).cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $< (32bit)" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -ObjC++ -c -o $@ | |||
| $(OBJDIR)/$(MODULENAME).mm.%64.o: $(MODULENAME).cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $< (64bit)" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -ObjC++ -c -o $@ | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| -include $(OBJS:%.o=%.d) | |||
| -include $(OBJS_posix32:%.o=%.d) | |||
| -include $(OBJS_posix64:%.o=%.d) | |||
| -include $(OBJS_win32:%.o=%.d) | |||
| -include $(OBJS_win64:%.o=%.d) | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| @@ -0,0 +1,92 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| class ActionBroadcaster::ActionMessage : public MessageManager::MessageBase | |||
| { | |||
| public: | |||
| ActionMessage (const ActionBroadcaster* ab, | |||
| const String& messageText, ActionListener* l) noexcept | |||
| : broadcaster (const_cast<ActionBroadcaster*> (ab)), | |||
| message (messageText), | |||
| listener (l) | |||
| {} | |||
| void messageCallback() override | |||
| { | |||
| if (const ActionBroadcaster* const b = broadcaster) | |||
| if (b->actionListeners.contains (listener)) | |||
| listener->actionListenerCallback (message); | |||
| } | |||
| private: | |||
| WeakReference<ActionBroadcaster> broadcaster; | |||
| const String message; | |||
| ActionListener* const listener; | |||
| JUCE_DECLARE_NON_COPYABLE (ActionMessage) | |||
| }; | |||
| //============================================================================== | |||
| ActionBroadcaster::ActionBroadcaster() | |||
| { | |||
| // are you trying to create this object before or after juce has been intialised?? | |||
| jassert (MessageManager::getInstanceWithoutCreating() != nullptr); | |||
| } | |||
| ActionBroadcaster::~ActionBroadcaster() | |||
| { | |||
| // all event-based objects must be deleted BEFORE juce is shut down! | |||
| jassert (MessageManager::getInstanceWithoutCreating() != nullptr); | |||
| } | |||
| void ActionBroadcaster::addActionListener (ActionListener* const listener) | |||
| { | |||
| const ScopedLock sl (actionListenerLock); | |||
| if (listener != nullptr) | |||
| actionListeners.add (listener); | |||
| } | |||
| void ActionBroadcaster::removeActionListener (ActionListener* const listener) | |||
| { | |||
| const ScopedLock sl (actionListenerLock); | |||
| actionListeners.removeValue (listener); | |||
| } | |||
| void ActionBroadcaster::removeAllActionListeners() | |||
| { | |||
| const ScopedLock sl (actionListenerLock); | |||
| actionListeners.clear(); | |||
| } | |||
| void ActionBroadcaster::sendActionMessage (const String& message) const | |||
| { | |||
| const ScopedLock sl (actionListenerLock); | |||
| for (int i = actionListeners.size(); --i >= 0;) | |||
| (new ActionMessage (this, message, actionListeners.getUnchecked(i)))->post(); | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,77 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** Manages a list of ActionListeners, and can send them messages. | |||
| To quickly add methods to your class that can add/remove action | |||
| listeners and broadcast to them, you can derive from this. | |||
| @see ActionListener, ChangeListener | |||
| */ | |||
| class JUCE_API ActionBroadcaster | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates an ActionBroadcaster. */ | |||
| ActionBroadcaster(); | |||
| /** Destructor. */ | |||
| virtual ~ActionBroadcaster(); | |||
| //============================================================================== | |||
| /** Adds a listener to the list. | |||
| Trying to add a listener that's already on the list will have no effect. | |||
| */ | |||
| void addActionListener (ActionListener* listener); | |||
| /** Removes a listener from the list. | |||
| If the listener isn't on the list, this won't have any effect. | |||
| */ | |||
| void removeActionListener (ActionListener* listener); | |||
| /** Removes all listeners from the list. */ | |||
| void removeAllActionListeners(); | |||
| //============================================================================== | |||
| /** Broadcasts a message to all the registered listeners. | |||
| @see ActionListener::actionListenerCallback | |||
| */ | |||
| void sendActionMessage (const String& message) const; | |||
| private: | |||
| //============================================================================== | |||
| class ActionMessage; | |||
| friend class ActionMessage; | |||
| SortedSet<ActionListener*> actionListeners; | |||
| CriticalSection actionListenerLock; | |||
| JUCE_DECLARE_WEAK_REFERENCEABLE (ActionBroadcaster) | |||
| JUCE_DECLARE_NON_COPYABLE (ActionBroadcaster) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,46 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Interface class for delivery of events that are sent by an ActionBroadcaster. | |||
| @see ActionBroadcaster, ChangeListener | |||
| */ | |||
| class JUCE_API ActionListener | |||
| { | |||
| public: | |||
| /** Destructor. */ | |||
| virtual ~ActionListener() {} | |||
| /** Overridden by your subclass to receive the callback. | |||
| @param message the string that was specified when the event was triggered | |||
| by a call to ActionBroadcaster::sendActionMessage() | |||
| */ | |||
| virtual void actionListenerCallback (const String& message) = 0; | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,93 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| class AsyncUpdater::AsyncUpdaterMessage : public CallbackMessage | |||
| { | |||
| public: | |||
| AsyncUpdaterMessage (AsyncUpdater& au) : owner (au) {} | |||
| void messageCallback() override | |||
| { | |||
| if (shouldDeliver.compareAndSetBool (0, 1)) | |||
| owner.handleAsyncUpdate(); | |||
| } | |||
| AsyncUpdater& owner; | |||
| Atomic<int> shouldDeliver; | |||
| JUCE_DECLARE_NON_COPYABLE (AsyncUpdaterMessage) | |||
| }; | |||
| //============================================================================== | |||
| AsyncUpdater::AsyncUpdater() | |||
| { | |||
| activeMessage = new AsyncUpdaterMessage (*this); | |||
| } | |||
| AsyncUpdater::~AsyncUpdater() | |||
| { | |||
| // You're deleting this object with a background thread while there's an update | |||
| // pending on the main event thread - that's pretty dodgy threading, as the callback could | |||
| // happen after this destructor has finished. You should either use a MessageManagerLock while | |||
| // deleting this object, or find some other way to avoid such a race condition. | |||
| jassert ((! isUpdatePending()) | |||
| || MessageManager::getInstanceWithoutCreating() == nullptr | |||
| || MessageManager::getInstanceWithoutCreating()->currentThreadHasLockedMessageManager()); | |||
| activeMessage->shouldDeliver.set (0); | |||
| } | |||
| void AsyncUpdater::triggerAsyncUpdate() | |||
| { | |||
| // If you're calling this before (or after) the MessageManager is | |||
| // running, then you're not going to get any callbacks! | |||
| jassert (MessageManager::getInstanceWithoutCreating() != nullptr); | |||
| if (activeMessage->shouldDeliver.compareAndSetBool (1, 0)) | |||
| if (! activeMessage->post()) | |||
| cancelPendingUpdate(); // if the message queue fails, this avoids getting | |||
| // trapped waiting for the message to arrive | |||
| } | |||
| void AsyncUpdater::cancelPendingUpdate() noexcept | |||
| { | |||
| activeMessage->shouldDeliver.set (0); | |||
| } | |||
| void AsyncUpdater::handleUpdateNowIfNeeded() | |||
| { | |||
| // This can only be called by the event thread. | |||
| jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); | |||
| if (activeMessage->shouldDeliver.exchange (0) != 0) | |||
| handleAsyncUpdate(); | |||
| } | |||
| bool AsyncUpdater::isUpdatePending() const noexcept | |||
| { | |||
| return activeMessage->shouldDeliver.value != 0; | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,108 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Has a callback method that is triggered asynchronously. | |||
| This object allows an asynchronous callback function to be triggered, for | |||
| tasks such as coalescing multiple updates into a single callback later on. | |||
| Basically, one or more calls to the triggerAsyncUpdate() will result in the | |||
| message thread calling handleAsyncUpdate() as soon as it can. | |||
| */ | |||
| class JUCE_API AsyncUpdater | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates an AsyncUpdater object. */ | |||
| AsyncUpdater(); | |||
| /** Destructor. | |||
| If there are any pending callbacks when the object is deleted, these are lost. | |||
| */ | |||
| virtual ~AsyncUpdater(); | |||
| //============================================================================== | |||
| /** Causes the callback to be triggered at a later time. | |||
| This method returns immediately, after which a callback to the | |||
| handleAsyncUpdate() method will be made by the message thread as | |||
| soon as possible. | |||
| If an update callback is already pending but hasn't happened yet, calling | |||
| this method will have no effect. | |||
| It's thread-safe to call this method from any thread, BUT beware of calling | |||
| it from a real-time (e.g. audio) thread, because it involves posting a message | |||
| to the system queue, which means it may block (and in general will do on | |||
| most OSes). | |||
| */ | |||
| void triggerAsyncUpdate(); | |||
| /** This will stop any pending updates from happening. | |||
| If called after triggerAsyncUpdate() and before the handleAsyncUpdate() | |||
| callback happens, this will cancel the handleAsyncUpdate() callback. | |||
| Note that this method simply cancels the next callback - if a callback is already | |||
| in progress on a different thread, this won't block until the callback finishes, so | |||
| there's no guarantee that the callback isn't still running when the method returns. | |||
| */ | |||
| void cancelPendingUpdate() noexcept; | |||
| /** If an update has been triggered and is pending, this will invoke it | |||
| synchronously. | |||
| Use this as a kind of "flush" operation - if an update is pending, the | |||
| handleAsyncUpdate() method will be called immediately; if no update is | |||
| pending, then nothing will be done. | |||
| Because this may invoke the callback, this method must only be called on | |||
| the main event thread. | |||
| */ | |||
| void handleUpdateNowIfNeeded(); | |||
| /** Returns true if there's an update callback in the pipeline. */ | |||
| bool isUpdatePending() const noexcept; | |||
| //============================================================================== | |||
| /** Called back to do whatever your class needs to do. | |||
| This method is called by the message thread at the next convenient time | |||
| after the triggerAsyncUpdate() method has been called. | |||
| */ | |||
| virtual void handleAsyncUpdate() = 0; | |||
| private: | |||
| //============================================================================== | |||
| class AsyncUpdaterMessage; | |||
| friend class ReferenceCountedObjectPtr<AsyncUpdaterMessage>; | |||
| ReferenceCountedObjectPtr<AsyncUpdaterMessage> activeMessage; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AsyncUpdater) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,99 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| ChangeBroadcaster::ChangeBroadcaster() noexcept | |||
| { | |||
| broadcastCallback.owner = this; | |||
| } | |||
| ChangeBroadcaster::~ChangeBroadcaster() | |||
| { | |||
| } | |||
| void ChangeBroadcaster::addChangeListener (ChangeListener* const listener) | |||
| { | |||
| // Listeners can only be safely added when the event thread is locked | |||
| // You can use a MessageManagerLock if you need to call this from another thread. | |||
| jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); | |||
| changeListeners.add (listener); | |||
| } | |||
| void ChangeBroadcaster::removeChangeListener (ChangeListener* const listener) | |||
| { | |||
| // Listeners can only be safely removed when the event thread is locked | |||
| // You can use a MessageManagerLock if you need to call this from another thread. | |||
| jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); | |||
| changeListeners.remove (listener); | |||
| } | |||
| void ChangeBroadcaster::removeAllChangeListeners() | |||
| { | |||
| // Listeners can only be safely removed when the event thread is locked | |||
| // You can use a MessageManagerLock if you need to call this from another thread. | |||
| jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); | |||
| changeListeners.clear(); | |||
| } | |||
| void ChangeBroadcaster::sendChangeMessage() | |||
| { | |||
| if (changeListeners.size() > 0) | |||
| broadcastCallback.triggerAsyncUpdate(); | |||
| } | |||
| void ChangeBroadcaster::sendSynchronousChangeMessage() | |||
| { | |||
| // This can only be called by the event thread. | |||
| jassert (MessageManager::getInstance()->isThisTheMessageThread()); | |||
| broadcastCallback.cancelPendingUpdate(); | |||
| callListeners(); | |||
| } | |||
| void ChangeBroadcaster::dispatchPendingMessages() | |||
| { | |||
| broadcastCallback.handleUpdateNowIfNeeded(); | |||
| } | |||
| void ChangeBroadcaster::callListeners() | |||
| { | |||
| changeListeners.call (&ChangeListener::changeListenerCallback, this); | |||
| } | |||
| //============================================================================== | |||
| ChangeBroadcaster::ChangeBroadcasterCallback::ChangeBroadcasterCallback() | |||
| : owner (nullptr) | |||
| { | |||
| } | |||
| void ChangeBroadcaster::ChangeBroadcasterCallback::handleAsyncUpdate() | |||
| { | |||
| jassert (owner != nullptr); | |||
| owner->callListeners(); | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,101 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Holds a list of ChangeListeners, and sends messages to them when instructed. | |||
| @see ChangeListener | |||
| */ | |||
| class JUCE_API ChangeBroadcaster | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates an ChangeBroadcaster. */ | |||
| ChangeBroadcaster() noexcept; | |||
| /** Destructor. */ | |||
| virtual ~ChangeBroadcaster(); | |||
| //============================================================================== | |||
| /** Registers a listener to receive change callbacks from this broadcaster. | |||
| Trying to add a listener that's already on the list will have no effect. | |||
| */ | |||
| void addChangeListener (ChangeListener* listener); | |||
| /** Unregisters a listener from the list. | |||
| If the listener isn't on the list, this won't have any effect. | |||
| */ | |||
| void removeChangeListener (ChangeListener* listener); | |||
| /** Removes all listeners from the list. */ | |||
| void removeAllChangeListeners(); | |||
| //============================================================================== | |||
| /** Causes an asynchronous change message to be sent to all the registered listeners. | |||
| The message will be delivered asynchronously by the main message thread, so this | |||
| method will return immediately. To call the listeners synchronously use | |||
| sendSynchronousChangeMessage(). | |||
| */ | |||
| void sendChangeMessage(); | |||
| /** Sends a synchronous change message to all the registered listeners. | |||
| This will immediately call all the listeners that are registered. For thread-safety | |||
| reasons, you must only call this method on the main message thread. | |||
| @see dispatchPendingMessages | |||
| */ | |||
| void sendSynchronousChangeMessage(); | |||
| /** If a change message has been sent but not yet dispatched, this will call | |||
| sendSynchronousChangeMessage() to make the callback immediately. | |||
| For thread-safety reasons, you must only call this method on the main message thread. | |||
| */ | |||
| void dispatchPendingMessages(); | |||
| private: | |||
| //============================================================================== | |||
| class ChangeBroadcasterCallback : public AsyncUpdater | |||
| { | |||
| public: | |||
| ChangeBroadcasterCallback(); | |||
| void handleAsyncUpdate() override; | |||
| ChangeBroadcaster* owner; | |||
| }; | |||
| friend class ChangeBroadcasterCallback; | |||
| ChangeBroadcasterCallback broadcastCallback; | |||
| ListenerList <ChangeListener> changeListeners; | |||
| void callListeners(); | |||
| JUCE_DECLARE_NON_COPYABLE (ChangeBroadcaster) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,61 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| class ChangeBroadcaster; | |||
| //============================================================================== | |||
| /** | |||
| Receives change event callbacks that are sent out by a ChangeBroadcaster. | |||
| A ChangeBroadcaster keeps a set of listeners to which it broadcasts a message when | |||
| the ChangeBroadcaster::sendChangeMessage() method is called. A subclass of | |||
| ChangeListener is used to receive these callbacks. | |||
| Note that the major difference between an ActionListener and a ChangeListener | |||
| is that for a ChangeListener, multiple changes will be coalesced into fewer | |||
| callbacks, but ActionListeners perform one callback for every event posted. | |||
| @see ChangeBroadcaster, ActionListener | |||
| */ | |||
| class JUCE_API ChangeListener | |||
| { | |||
| public: | |||
| /** Destructor. */ | |||
| virtual ~ChangeListener() {} | |||
| /** Your subclass should implement this method to receive the callback. | |||
| @param source the ChangeBroadcaster that triggered the callback. | |||
| */ | |||
| virtual void changeListenerCallback (ChangeBroadcaster* source) = 0; | |||
| //============================================================================== | |||
| #if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||
| // This method's signature has changed to take a ChangeBroadcaster parameter - please update your code! | |||
| private: virtual int changeListenerCallback (void*) { return 0; } | |||
| #endif | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,267 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| enum { magicMastSlaveConnectionHeader = 0x712baf04 }; | |||
| static const char* startMessage = "__ipc_st"; | |||
| static const char* killMessage = "__ipc_k_"; | |||
| static const char* pingMessage = "__ipc_p_"; | |||
| enum { specialMessageSize = 8, defaultTimeoutMs = 8000 }; | |||
| static String getCommandLinePrefix (const String& commandLineUniqueID) | |||
| { | |||
| return "--" + commandLineUniqueID + ":"; | |||
| } | |||
| //============================================================================== | |||
| // This thread sends and receives ping messages every second, so that it | |||
| // can find out if the other process has stopped running. | |||
| struct ChildProcessPingThread : public Thread, | |||
| private AsyncUpdater | |||
| { | |||
| ChildProcessPingThread (int timeout) : Thread ("IPC ping"), timeoutMs (timeout) | |||
| { | |||
| pingReceived(); | |||
| } | |||
| static bool isPingMessage (const MemoryBlock& m) noexcept | |||
| { | |||
| return memcmp (m.getData(), pingMessage, specialMessageSize) == 0; | |||
| } | |||
| void pingReceived() noexcept { countdown = timeoutMs / 1000 + 1; } | |||
| void triggerConnectionLostMessage() { triggerAsyncUpdate(); } | |||
| virtual bool sendPingMessage (const MemoryBlock&) = 0; | |||
| virtual void pingFailed() = 0; | |||
| int timeoutMs; | |||
| private: | |||
| Atomic<int> countdown; | |||
| void handleAsyncUpdate() override { pingFailed(); } | |||
| void run() override | |||
| { | |||
| while (! threadShouldExit()) | |||
| { | |||
| if (--countdown <= 0 || ! sendPingMessage (MemoryBlock (pingMessage, specialMessageSize))) | |||
| { | |||
| triggerConnectionLostMessage(); | |||
| break; | |||
| } | |||
| wait (1000); | |||
| } | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessPingThread) | |||
| }; | |||
| //============================================================================== | |||
| struct ChildProcessMaster::Connection : public InterprocessConnection, | |||
| private ChildProcessPingThread | |||
| { | |||
| Connection (ChildProcessMaster& m, const String& pipeName, int timeout) | |||
| : InterprocessConnection (false, magicMastSlaveConnectionHeader), | |||
| ChildProcessPingThread (timeout), | |||
| owner (m) | |||
| { | |||
| if (createPipe (pipeName, timeoutMs)) | |||
| startThread (4); | |||
| } | |||
| ~Connection() | |||
| { | |||
| stopThread (10000); | |||
| } | |||
| private: | |||
| void connectionMade() override {} | |||
| void connectionLost() override { owner.handleConnectionLost(); } | |||
| bool sendPingMessage (const MemoryBlock& m) override { return owner.sendMessageToSlave (m); } | |||
| void pingFailed() override { connectionLost(); } | |||
| void messageReceived (const MemoryBlock& m) override | |||
| { | |||
| pingReceived(); | |||
| if (m.getSize() != specialMessageSize || ! isPingMessage (m)) | |||
| owner.handleMessageFromSlave (m); | |||
| } | |||
| ChildProcessMaster& owner; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Connection) | |||
| }; | |||
| //============================================================================== | |||
| ChildProcessMaster::ChildProcessMaster() {} | |||
| ChildProcessMaster::~ChildProcessMaster() | |||
| { | |||
| if (connection != nullptr) | |||
| { | |||
| sendMessageToSlave (MemoryBlock (killMessage, specialMessageSize)); | |||
| connection->disconnect(); | |||
| connection = nullptr; | |||
| } | |||
| } | |||
| void ChildProcessMaster::handleConnectionLost() {} | |||
| bool ChildProcessMaster::sendMessageToSlave (const MemoryBlock& mb) | |||
| { | |||
| if (connection != nullptr) | |||
| return connection->sendMessage (mb); | |||
| jassertfalse; // this can only be used when the connection is active! | |||
| return false; | |||
| } | |||
| bool ChildProcessMaster::launchSlaveProcess (const File& executable, const String& commandLineUniqueID, int timeoutMs, int streamFlags) | |||
| { | |||
| connection = nullptr; | |||
| jassert (childProcess.kill()); | |||
| const String pipeName ("p" + String::toHexString (Random().nextInt64())); | |||
| StringArray args; | |||
| args.add (executable.getFullPathName()); | |||
| args.add (getCommandLinePrefix (commandLineUniqueID) + pipeName); | |||
| if (childProcess.start (args, streamFlags)) | |||
| { | |||
| connection = new Connection (*this, pipeName, timeoutMs <= 0 ? defaultTimeoutMs : timeoutMs); | |||
| if (connection->isConnected()) | |||
| { | |||
| sendMessageToSlave (MemoryBlock (startMessage, specialMessageSize)); | |||
| return true; | |||
| } | |||
| connection = nullptr; | |||
| } | |||
| return false; | |||
| } | |||
| //============================================================================== | |||
| struct ChildProcessSlave::Connection : public InterprocessConnection, | |||
| private ChildProcessPingThread | |||
| { | |||
| Connection (ChildProcessSlave& p, const String& pipeName, int timeout) | |||
| : InterprocessConnection (false, magicMastSlaveConnectionHeader), | |||
| ChildProcessPingThread (timeout), | |||
| owner (p) | |||
| { | |||
| connectToPipe (pipeName, timeoutMs); | |||
| startThread (4); | |||
| } | |||
| ~Connection() | |||
| { | |||
| stopThread (10000); | |||
| } | |||
| private: | |||
| ChildProcessSlave& owner; | |||
| void connectionMade() override {} | |||
| void connectionLost() override { owner.handleConnectionLost(); } | |||
| bool sendPingMessage (const MemoryBlock& m) override { return owner.sendMessageToMaster (m); } | |||
| void pingFailed() override { connectionLost(); } | |||
| void messageReceived (const MemoryBlock& m) override | |||
| { | |||
| pingReceived(); | |||
| if (m.getSize() == specialMessageSize) | |||
| { | |||
| if (isPingMessage (m)) | |||
| return; | |||
| if (memcmp (m.getData(), killMessage, specialMessageSize) == 0) | |||
| { | |||
| triggerConnectionLostMessage(); | |||
| return; | |||
| } | |||
| if (memcmp (m.getData(), startMessage, specialMessageSize) == 0) | |||
| { | |||
| owner.handleConnectionMade(); | |||
| return; | |||
| } | |||
| } | |||
| owner.handleMessageFromMaster (m); | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Connection) | |||
| }; | |||
| //============================================================================== | |||
| ChildProcessSlave::ChildProcessSlave() {} | |||
| ChildProcessSlave::~ChildProcessSlave() {} | |||
| void ChildProcessSlave::handleConnectionMade() {} | |||
| void ChildProcessSlave::handleConnectionLost() {} | |||
| bool ChildProcessSlave::sendMessageToMaster (const MemoryBlock& mb) | |||
| { | |||
| if (connection != nullptr) | |||
| return connection->sendMessage (mb); | |||
| jassertfalse; // this can only be used when the connection is active! | |||
| return false; | |||
| } | |||
| bool ChildProcessSlave::initialiseFromCommandLine (const String& commandLine, | |||
| const String& commandLineUniqueID, | |||
| int timeoutMs) | |||
| { | |||
| String prefix (getCommandLinePrefix (commandLineUniqueID)); | |||
| if (commandLine.trim().startsWith (prefix)) | |||
| { | |||
| String pipeName (commandLine.fromFirstOccurrenceOf (prefix, false, false) | |||
| .upToFirstOccurrenceOf (" ", false, false).trim()); | |||
| if (pipeName.isNotEmpty()) | |||
| { | |||
| connection = new Connection (*this, pipeName, timeoutMs <= 0 ? defaultTimeoutMs : timeoutMs); | |||
| if (! connection->isConnected()) | |||
| connection = nullptr; | |||
| } | |||
| } | |||
| return connection != nullptr; | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,189 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Acts as the slave end of a master/slave pair of connected processes. | |||
| The ChildProcessSlave and ChildProcessMaster classes make it easy for an app | |||
| to spawn a child process, and to manage a 2-way messaging connection to control it. | |||
| To use the system, you need to create subclasses of both ChildProcessSlave and | |||
| ChildProcessMaster. To instantiate the ChildProcessSlave object, you must | |||
| add some code to your main() or JUCEApplication::initialise() function that | |||
| calls the initialiseFromCommandLine() method to check the app's command-line | |||
| parameters to see whether it's being launched as a child process. If this returns | |||
| true then the slave process can be allowed to run, and its handleMessageFromMaster() | |||
| method will be called whenever a message arrives. | |||
| The juce demo app has a good example of this class in action. | |||
| @see ChildProcessMaster, InterprocessConnection, ChildProcess | |||
| */ | |||
| class JUCE_API ChildProcessSlave | |||
| { | |||
| public: | |||
| /** Creates a non-connected slave process. | |||
| Use initialiseFromCommandLine to connect to a master process. | |||
| */ | |||
| ChildProcessSlave(); | |||
| /** Destructor. */ | |||
| virtual ~ChildProcessSlave(); | |||
| /** This checks some command-line parameters to see whether they were generated by | |||
| ChildProcessMaster::launchSlaveProcess(), and if so, connects to that master process. | |||
| In an exe that can be used as a child process, you should add some code to your | |||
| main() or JUCEApplication::initialise() that calls this method. | |||
| The commandLineUniqueID should be a short alphanumeric identifier (no spaces!) | |||
| that matches the string passed to ChildProcessMaster::launchSlaveProcess(). | |||
| The timeoutMs parameter lets you specify how long the child process is allowed | |||
| to run without receiving a ping from the master before the master is considered to | |||
| have died, and handleConnectionLost() will be called. Passing <= 0 for this timeout | |||
| makes it use a default value. | |||
| Returns true if the command-line matches and the connection is made successfully. | |||
| */ | |||
| bool initialiseFromCommandLine (const String& commandLine, | |||
| const String& commandLineUniqueID, | |||
| int timeoutMs = 0); | |||
| //============================================================================== | |||
| /** This will be called to deliver messages from the master process. | |||
| The call will probably be made on a background thread, so be careful with your | |||
| thread-safety! You may want to respond by sending back a message with | |||
| sendMessageToMaster() | |||
| */ | |||
| virtual void handleMessageFromMaster (const MemoryBlock&) = 0; | |||
| /** This will be called when the master process finishes connecting to this slave. | |||
| The call will probably be made on a background thread, so be careful with your thread-safety! | |||
| */ | |||
| virtual void handleConnectionMade(); | |||
| /** This will be called when the connection to the master process is lost. | |||
| The call may be made from any thread (including the message thread). | |||
| Typically, if your process only exists to act as a slave, you should probably exit | |||
| when this happens. | |||
| */ | |||
| virtual void handleConnectionLost(); | |||
| /** Tries to send a message to the master process. | |||
| This returns true if the message was sent, but doesn't check that it actually gets | |||
| delivered at the other end. If successful, the data will emerge in a call to your | |||
| ChildProcessMaster::handleMessageFromSlave(). | |||
| */ | |||
| bool sendMessageToMaster (const MemoryBlock&); | |||
| private: | |||
| struct Connection; | |||
| friend struct Connection; | |||
| friend struct ContainerDeletePolicy<Connection>; | |||
| ScopedPointer<Connection> connection; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessSlave) | |||
| }; | |||
| //============================================================================== | |||
| /** | |||
| Acts as the master in a master/slave pair of connected processes. | |||
| The ChildProcessSlave and ChildProcessMaster classes make it easy for an app | |||
| to spawn a child process, and to manage a 2-way messaging connection to control it. | |||
| To use the system, you need to create subclasses of both ChildProcessSlave and | |||
| ChildProcessMaster. When you want your master process to launch the slave, you | |||
| just call launchSlaveProcess(), and it'll attempt to launch the executable that | |||
| you specify (which may be the same exe), and assuming it has been set-up to | |||
| correctly parse the command-line parameters (see ChildProcessSlave) then a | |||
| two-way connection will be created. | |||
| The juce demo app has a good example of this class in action. | |||
| @see ChildProcessSlave, InterprocessConnection, ChildProcess | |||
| */ | |||
| class JUCE_API ChildProcessMaster | |||
| { | |||
| public: | |||
| /** Creates an uninitialised master process object. | |||
| Use launchSlaveProcess to launch and connect to a child process. | |||
| */ | |||
| ChildProcessMaster(); | |||
| /** Destructor. */ | |||
| virtual ~ChildProcessMaster(); | |||
| /** Attempts to launch and connect to a slave process. | |||
| This will start the given executable, passing it a special command-line | |||
| parameter based around the commandLineUniqueID string, which must be a | |||
| short alphanumeric string (no spaces!) that identifies your app. The exe | |||
| that gets launched must respond by calling ChildProcessSlave::initialiseFromCommandLine() | |||
| in its startup code, and must use a matching ID to commandLineUniqueID. | |||
| The timeoutMs parameter lets you specify how long the child process is allowed | |||
| to go without sending a ping before it is considered to have died and | |||
| handleConnectionLost() will be called. Passing <= 0 for this timeout makes | |||
| it use a default value. | |||
| If this all works, the method returns true, and you can begin sending and | |||
| receiving messages with the slave process. | |||
| */ | |||
| bool launchSlaveProcess (const File& executableToLaunch, | |||
| const String& commandLineUniqueID, | |||
| int timeoutMs = 0, | |||
| int streamFlags = ChildProcess::wantStdOut | ChildProcess::wantStdErr); | |||
| /** This will be called to deliver a message from the slave process. | |||
| The call will probably be made on a background thread, so be careful with your thread-safety! | |||
| */ | |||
| virtual void handleMessageFromSlave (const MemoryBlock&) = 0; | |||
| /** This will be called when the slave process dies or is somehow disconnected. | |||
| The call will probably be made on a background thread, so be careful with your thread-safety! | |||
| */ | |||
| virtual void handleConnectionLost(); | |||
| /** Attempts to send a message to the slave process. | |||
| This returns true if the message was dispatched, but doesn't check that it actually | |||
| gets delivered at the other end. If successful, the data will emerge in a call to | |||
| your ChildProcessSlave::handleMessageFromMaster(). | |||
| */ | |||
| bool sendMessageToSlave (const MemoryBlock&); | |||
| private: | |||
| ChildProcess childProcess; | |||
| struct Connection; | |||
| friend struct Connection; | |||
| friend struct ContainerDeletePolicy<Connection>; | |||
| ScopedPointer<Connection> connection; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessMaster) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,362 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| struct InterprocessConnection::ConnectionThread : public Thread | |||
| { | |||
| ConnectionThread (InterprocessConnection& c) : Thread ("JUCE IPC"), owner (c) {} | |||
| void run() override { owner.runThread(); } | |||
| InterprocessConnection& owner; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectionThread) | |||
| }; | |||
| //============================================================================== | |||
| InterprocessConnection::InterprocessConnection (bool callbacksOnMessageThread, uint32 magicMessageHeaderNumber) | |||
| : useMessageThread (callbacksOnMessageThread), | |||
| magicMessageHeader (magicMessageHeaderNumber) | |||
| { | |||
| thread = new ConnectionThread (*this); | |||
| } | |||
| InterprocessConnection::~InterprocessConnection() | |||
| { | |||
| callbackConnectionState = false; | |||
| disconnect(); | |||
| masterReference.clear(); | |||
| thread = nullptr; | |||
| } | |||
| //============================================================================== | |||
| bool InterprocessConnection::connectToSocket (const String& hostName, | |||
| const int portNumber, | |||
| const int timeOutMillisecs) | |||
| { | |||
| disconnect(); | |||
| const ScopedLock sl (pipeAndSocketLock); | |||
| socket = new StreamingSocket(); | |||
| if (socket->connect (hostName, portNumber, timeOutMillisecs)) | |||
| { | |||
| connectionMadeInt(); | |||
| thread->startThread(); | |||
| return true; | |||
| } | |||
| socket = nullptr; | |||
| return false; | |||
| } | |||
| bool InterprocessConnection::connectToPipe (const String& pipeName, const int timeoutMs) | |||
| { | |||
| disconnect(); | |||
| ScopedPointer<NamedPipe> newPipe (new NamedPipe()); | |||
| if (newPipe->openExisting (pipeName)) | |||
| { | |||
| const ScopedLock sl (pipeAndSocketLock); | |||
| pipeReceiveMessageTimeout = timeoutMs; | |||
| initialiseWithPipe (newPipe.release()); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| bool InterprocessConnection::createPipe (const String& pipeName, const int timeoutMs, bool mustNotExist) | |||
| { | |||
| disconnect(); | |||
| ScopedPointer<NamedPipe> newPipe (new NamedPipe()); | |||
| if (newPipe->createNewPipe (pipeName, mustNotExist)) | |||
| { | |||
| const ScopedLock sl (pipeAndSocketLock); | |||
| pipeReceiveMessageTimeout = timeoutMs; | |||
| initialiseWithPipe (newPipe.release()); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| void InterprocessConnection::disconnect() | |||
| { | |||
| thread->signalThreadShouldExit(); | |||
| { | |||
| const ScopedLock sl (pipeAndSocketLock); | |||
| if (socket != nullptr) socket->close(); | |||
| if (pipe != nullptr) pipe->close(); | |||
| } | |||
| thread->stopThread (4000); | |||
| deletePipeAndSocket(); | |||
| connectionLostInt(); | |||
| } | |||
| void InterprocessConnection::deletePipeAndSocket() | |||
| { | |||
| const ScopedLock sl (pipeAndSocketLock); | |||
| socket = nullptr; | |||
| pipe = nullptr; | |||
| } | |||
| bool InterprocessConnection::isConnected() const | |||
| { | |||
| const ScopedLock sl (pipeAndSocketLock); | |||
| return ((socket != nullptr && socket->isConnected()) | |||
| || (pipe != nullptr && pipe->isOpen())) | |||
| && thread->isThreadRunning(); | |||
| } | |||
| String InterprocessConnection::getConnectedHostName() const | |||
| { | |||
| { | |||
| const ScopedLock sl (pipeAndSocketLock); | |||
| if (pipe == nullptr && socket == nullptr) | |||
| return {}; | |||
| if (socket != nullptr && ! socket->isLocal()) | |||
| return socket->getHostName(); | |||
| } | |||
| return IPAddress::local().toString(); | |||
| } | |||
| //============================================================================== | |||
| bool InterprocessConnection::sendMessage (const MemoryBlock& message) | |||
| { | |||
| uint32 messageHeader[2] = { ByteOrder::swapIfBigEndian (magicMessageHeader), | |||
| ByteOrder::swapIfBigEndian ((uint32) message.getSize()) }; | |||
| MemoryBlock messageData (sizeof (messageHeader) + message.getSize()); | |||
| messageData.copyFrom (messageHeader, 0, sizeof (messageHeader)); | |||
| messageData.copyFrom (message.getData(), sizeof (messageHeader), message.getSize()); | |||
| return writeData (messageData.getData(), (int) messageData.getSize()) == (int) messageData.getSize(); | |||
| } | |||
| int InterprocessConnection::writeData (void* data, int dataSize) | |||
| { | |||
| const ScopedLock sl (pipeAndSocketLock); | |||
| if (socket != nullptr) | |||
| return socket->write (data, dataSize); | |||
| if (pipe != nullptr) | |||
| return pipe->write (data, dataSize, pipeReceiveMessageTimeout); | |||
| return 0; | |||
| } | |||
| //============================================================================== | |||
| void InterprocessConnection::initialiseWithSocket (StreamingSocket* newSocket) | |||
| { | |||
| jassert (socket == nullptr && pipe == nullptr); | |||
| socket = newSocket; | |||
| connectionMadeInt(); | |||
| thread->startThread(); | |||
| } | |||
| void InterprocessConnection::initialiseWithPipe (NamedPipe* newPipe) | |||
| { | |||
| jassert (socket == nullptr && pipe == nullptr); | |||
| pipe = newPipe; | |||
| connectionMadeInt(); | |||
| thread->startThread(); | |||
| } | |||
| //============================================================================== | |||
| struct ConnectionStateMessage : public MessageManager::MessageBase | |||
| { | |||
| ConnectionStateMessage (InterprocessConnection* ipc, bool connected) noexcept | |||
| : owner (ipc), connectionMade (connected) | |||
| {} | |||
| void messageCallback() override | |||
| { | |||
| if (auto* ipc = owner.get()) | |||
| { | |||
| if (connectionMade) | |||
| ipc->connectionMade(); | |||
| else | |||
| ipc->connectionLost(); | |||
| } | |||
| } | |||
| WeakReference<InterprocessConnection> owner; | |||
| bool connectionMade; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectionStateMessage) | |||
| }; | |||
| void InterprocessConnection::connectionMadeInt() | |||
| { | |||
| if (! callbackConnectionState) | |||
| { | |||
| callbackConnectionState = true; | |||
| if (useMessageThread) | |||
| (new ConnectionStateMessage (this, true))->post(); | |||
| else | |||
| connectionMade(); | |||
| } | |||
| } | |||
| void InterprocessConnection::connectionLostInt() | |||
| { | |||
| if (callbackConnectionState) | |||
| { | |||
| callbackConnectionState = false; | |||
| if (useMessageThread) | |||
| (new ConnectionStateMessage (this, false))->post(); | |||
| else | |||
| connectionLost(); | |||
| } | |||
| } | |||
| struct DataDeliveryMessage : public Message | |||
| { | |||
| DataDeliveryMessage (InterprocessConnection* ipc, const MemoryBlock& d) | |||
| : owner (ipc), data (d) | |||
| {} | |||
| void messageCallback() override | |||
| { | |||
| if (auto* ipc = owner.get()) | |||
| ipc->messageReceived (data); | |||
| } | |||
| WeakReference<InterprocessConnection> owner; | |||
| MemoryBlock data; | |||
| }; | |||
| void InterprocessConnection::deliverDataInt (const MemoryBlock& data) | |||
| { | |||
| jassert (callbackConnectionState); | |||
| if (useMessageThread) | |||
| (new DataDeliveryMessage (this, data))->post(); | |||
| else | |||
| messageReceived (data); | |||
| } | |||
| //============================================================================== | |||
| bool InterprocessConnection::readNextMessageInt() | |||
| { | |||
| uint32 messageHeader[2]; | |||
| const int bytes = socket != nullptr ? socket->read (messageHeader, sizeof (messageHeader), true) | |||
| : pipe ->read (messageHeader, sizeof (messageHeader), -1); | |||
| if (bytes == sizeof (messageHeader) | |||
| && ByteOrder::swapIfBigEndian (messageHeader[0]) == magicMessageHeader) | |||
| { | |||
| int bytesInMessage = (int) ByteOrder::swapIfBigEndian (messageHeader[1]); | |||
| if (bytesInMessage > 0) | |||
| { | |||
| MemoryBlock messageData ((size_t) bytesInMessage, true); | |||
| int bytesRead = 0; | |||
| while (bytesInMessage > 0) | |||
| { | |||
| if (thread->threadShouldExit()) | |||
| return false; | |||
| const int numThisTime = jmin (bytesInMessage, 65536); | |||
| void* const data = addBytesToPointer (messageData.getData(), bytesRead); | |||
| const int bytesIn = socket != nullptr ? socket->read (data, numThisTime, true) | |||
| : pipe ->read (data, numThisTime, -1); | |||
| if (bytesIn <= 0) | |||
| break; | |||
| bytesRead += bytesIn; | |||
| bytesInMessage -= bytesIn; | |||
| } | |||
| if (bytesRead >= 0) | |||
| deliverDataInt (messageData); | |||
| } | |||
| } | |||
| else if (bytes < 0) | |||
| { | |||
| if (socket != nullptr) | |||
| deletePipeAndSocket(); | |||
| connectionLostInt(); | |||
| return false; | |||
| } | |||
| return true; | |||
| } | |||
| void InterprocessConnection::runThread() | |||
| { | |||
| while (! thread->threadShouldExit()) | |||
| { | |||
| if (socket != nullptr) | |||
| { | |||
| auto ready = socket->waitUntilReady (true, 0); | |||
| if (ready < 0) | |||
| { | |||
| deletePipeAndSocket(); | |||
| connectionLostInt(); | |||
| break; | |||
| } | |||
| if (ready == 0) | |||
| { | |||
| thread->wait (1); | |||
| continue; | |||
| } | |||
| } | |||
| else if (pipe != nullptr) | |||
| { | |||
| if (! pipe->isOpen()) | |||
| { | |||
| deletePipeAndSocket(); | |||
| connectionLostInt(); | |||
| break; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| break; | |||
| } | |||
| if (thread->threadShouldExit() || ! readNextMessageInt()) | |||
| break; | |||
| } | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,207 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| class InterprocessConnectionServer; | |||
| class MemoryBlock; | |||
| //============================================================================== | |||
| /** | |||
| Manages a simple two-way messaging connection to another process, using either | |||
| a socket or a named pipe as the transport medium. | |||
| To connect to a waiting socket or an open pipe, use the connectToSocket() or | |||
| connectToPipe() methods. If this succeeds, messages can be sent to the other end, | |||
| and incoming messages will result in a callback via the messageReceived() | |||
| method. | |||
| To open a pipe and wait for another client to connect to it, use the createPipe() | |||
| method. | |||
| To act as a socket server and create connections for one or more client, see the | |||
| InterprocessConnectionServer class. | |||
| @see InterprocessConnectionServer, Socket, NamedPipe | |||
| */ | |||
| class JUCE_API InterprocessConnection | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a connection. | |||
| Connections are created manually, connecting them with the connectToSocket() | |||
| or connectToPipe() methods, or they are created automatically by a InterprocessConnectionServer | |||
| when a client wants to connect. | |||
| @param callbacksOnMessageThread if true, callbacks to the connectionMade(), | |||
| connectionLost() and messageReceived() methods will | |||
| always be made using the message thread; if false, | |||
| these will be called immediately on the connection's | |||
| own thread. | |||
| @param magicMessageHeaderNumber a magic number to use in the header to check the | |||
| validity of the data blocks being sent and received. This | |||
| can be any number, but the sender and receiver must obviously | |||
| use matching values or they won't recognise each other. | |||
| */ | |||
| InterprocessConnection (bool callbacksOnMessageThread = true, | |||
| uint32 magicMessageHeaderNumber = 0xf2b49e2c); | |||
| /** Destructor. */ | |||
| virtual ~InterprocessConnection(); | |||
| //============================================================================== | |||
| /** Tries to connect this object to a socket. | |||
| For this to work, the machine on the other end needs to have a InterprocessConnectionServer | |||
| object waiting to receive client connections on this port number. | |||
| @param hostName the host computer, either a network address or name | |||
| @param portNumber the socket port number to try to connect to | |||
| @param timeOutMillisecs how long to keep trying before giving up | |||
| @returns true if the connection is established successfully | |||
| @see Socket | |||
| */ | |||
| bool connectToSocket (const String& hostName, | |||
| int portNumber, | |||
| int timeOutMillisecs); | |||
| /** Tries to connect the object to an existing named pipe. | |||
| For this to work, another process on the same computer must already have opened | |||
| an InterprocessConnection object and used createPipe() to create a pipe for this | |||
| to connect to. | |||
| @param pipeName the name to use for the pipe - this should be unique to your app | |||
| @param pipeReceiveMessageTimeoutMs a timeout length to be used when reading or writing | |||
| to the pipe, or -1 for an infinite timeout. | |||
| @returns true if it connects successfully. | |||
| @see createPipe, NamedPipe | |||
| */ | |||
| bool connectToPipe (const String& pipeName, int pipeReceiveMessageTimeoutMs); | |||
| /** Tries to create a new pipe for other processes to connect to. | |||
| This creates a pipe with the given name, so that other processes can use | |||
| connectToPipe() to connect to the other end. | |||
| @param pipeName the name to use for the pipe - this should be unique to your app | |||
| @param pipeReceiveMessageTimeoutMs a timeout length to be used when reading or writing | |||
| to the pipe, or -1 for an infinite timeout | |||
| @param mustNotExist if set to true, the method will fail if the pipe already exists | |||
| @returns true if the pipe was created, or false if it fails (e.g. if another process is | |||
| already using using the pipe) | |||
| */ | |||
| bool createPipe (const String& pipeName, int pipeReceiveMessageTimeoutMs, bool mustNotExist = false); | |||
| /** Disconnects and closes any currently-open sockets or pipes. */ | |||
| void disconnect(); | |||
| /** True if a socket or pipe is currently active. */ | |||
| bool isConnected() const; | |||
| /** Returns the socket that this connection is using (or nullptr if it uses a pipe). */ | |||
| StreamingSocket* getSocket() const noexcept { return socket; } | |||
| /** Returns the pipe that this connection is using (or nullptr if it uses a socket). */ | |||
| NamedPipe* getPipe() const noexcept { return pipe; } | |||
| /** Returns the name of the machine at the other end of this connection. | |||
| This may return an empty string if the name is unknown. | |||
| */ | |||
| String getConnectedHostName() const; | |||
| //============================================================================== | |||
| /** Tries to send a message to the other end of this connection. | |||
| This will fail if it's not connected, or if there's some kind of write error. If | |||
| it succeeds, the connection object at the other end will receive the message by | |||
| a callback to its messageReceived() method. | |||
| @see messageReceived | |||
| */ | |||
| bool sendMessage (const MemoryBlock& message); | |||
| //============================================================================== | |||
| /** Called when the connection is first connected. | |||
| If the connection was created with the callbacksOnMessageThread flag set, then | |||
| this will be called on the message thread; otherwise it will be called on a server | |||
| thread. | |||
| */ | |||
| virtual void connectionMade() = 0; | |||
| /** Called when the connection is broken. | |||
| If the connection was created with the callbacksOnMessageThread flag set, then | |||
| this will be called on the message thread; otherwise it will be called on a server | |||
| thread. | |||
| */ | |||
| virtual void connectionLost() = 0; | |||
| /** Called when a message arrives. | |||
| When the object at the other end of this connection sends us a message with sendMessage(), | |||
| this callback is used to deliver it to us. | |||
| If the connection was created with the callbacksOnMessageThread flag set, then | |||
| this will be called on the message thread; otherwise it will be called on a server | |||
| thread. | |||
| @see sendMessage | |||
| */ | |||
| virtual void messageReceived (const MemoryBlock& message) = 0; | |||
| private: | |||
| //============================================================================== | |||
| CriticalSection pipeAndSocketLock; | |||
| ScopedPointer<StreamingSocket> socket; | |||
| ScopedPointer<NamedPipe> pipe; | |||
| bool callbackConnectionState = false; | |||
| const bool useMessageThread; | |||
| const uint32 magicMessageHeader; | |||
| int pipeReceiveMessageTimeout = -1; | |||
| friend class InterprocessConnectionServer; | |||
| void initialiseWithSocket (StreamingSocket*); | |||
| void initialiseWithPipe (NamedPipe*); | |||
| void deletePipeAndSocket(); | |||
| void connectionMadeInt(); | |||
| void connectionLostInt(); | |||
| void deliverDataInt (const MemoryBlock&); | |||
| bool readNextMessageInt(); | |||
| struct ConnectionThread; | |||
| friend struct ConnectionThread; | |||
| friend struct ContainerDeletePolicy<ConnectionThread>; | |||
| ScopedPointer<ConnectionThread> thread; | |||
| void runThread(); | |||
| int writeData (void*, int); | |||
| JUCE_DECLARE_WEAK_REFERENCEABLE (InterprocessConnection) | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InterprocessConnection) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,81 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| InterprocessConnectionServer::InterprocessConnectionServer() | |||
| : Thread ("Juce IPC server") | |||
| { | |||
| } | |||
| InterprocessConnectionServer::~InterprocessConnectionServer() | |||
| { | |||
| stop(); | |||
| } | |||
| //============================================================================== | |||
| bool InterprocessConnectionServer::beginWaitingForSocket (const int portNumber, const String& bindAddress) | |||
| { | |||
| stop(); | |||
| socket = new StreamingSocket(); | |||
| if (socket->createListener (portNumber, bindAddress)) | |||
| { | |||
| startThread(); | |||
| return true; | |||
| } | |||
| socket = nullptr; | |||
| return false; | |||
| } | |||
| void InterprocessConnectionServer::stop() | |||
| { | |||
| signalThreadShouldExit(); | |||
| if (socket != nullptr) | |||
| socket->close(); | |||
| stopThread (4000); | |||
| socket = nullptr; | |||
| } | |||
| int InterprocessConnectionServer::getBoundPort() const noexcept | |||
| { | |||
| return (socket == nullptr) ? -1 : socket->getBoundPort(); | |||
| } | |||
| void InterprocessConnectionServer::run() | |||
| { | |||
| while ((! threadShouldExit()) && socket != nullptr) | |||
| { | |||
| ScopedPointer<StreamingSocket> clientSocket (socket->waitForNextConnection()); | |||
| if (clientSocket != nullptr) | |||
| if (InterprocessConnection* newConnection = createConnectionObject()) | |||
| newConnection->initialiseWithSocket (clientSocket.release()); | |||
| } | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,104 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| An object that waits for client sockets to connect to a port on this host, and | |||
| creates InterprocessConnection objects for each one. | |||
| To use this, create a class derived from it which implements the createConnectionObject() | |||
| method, so that it creates suitable connection objects for each client that tries | |||
| to connect. | |||
| @see InterprocessConnection | |||
| */ | |||
| class JUCE_API InterprocessConnectionServer : private Thread | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates an uninitialised server object. | |||
| */ | |||
| InterprocessConnectionServer(); | |||
| /** Destructor. */ | |||
| ~InterprocessConnectionServer(); | |||
| //============================================================================== | |||
| /** Starts an internal thread which listens on the given port number. | |||
| While this is running, in another process tries to connect with the | |||
| InterprocessConnection::connectToSocket() method, this object will call | |||
| createConnectionObject() to create a connection to that client. | |||
| Use stop() to stop the thread running. | |||
| @param portNumber The port on which the server will receive | |||
| connections | |||
| @param bindAddress The address on which the server will listen | |||
| for connections. An empty string indicates | |||
| that it should listen on all addresses | |||
| assigned to this machine. | |||
| @see createConnectionObject, stop | |||
| */ | |||
| bool beginWaitingForSocket (int portNumber, const String& bindAddress = String()); | |||
| /** Terminates the listener thread, if it's active. | |||
| @see beginWaitingForSocket | |||
| */ | |||
| void stop(); | |||
| /** Returns the local port number to which this server is currently bound. | |||
| This is useful if you need to know to which port the OS has actually bound your | |||
| socket when calling beginWaitingForSocket with a port number of zero. | |||
| Returns -1 if the function fails. | |||
| */ | |||
| int getBoundPort() const noexcept; | |||
| protected: | |||
| /** Creates a suitable connection object for a client process that wants to | |||
| connect to this one. | |||
| This will be called by the listener thread when a client process tries | |||
| to connect, and must return a new InterprocessConnection object that will | |||
| then run as this end of the connection. | |||
| @see InterprocessConnection | |||
| */ | |||
| virtual InterprocessConnection* createConnectionObject() = 0; | |||
| private: | |||
| //============================================================================== | |||
| ScopedPointer<StreamingSocket> socket; | |||
| void run() override; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InterprocessConnectionServer) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,102 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| #ifdef JUCE_EVENTS_H_INCLUDED | |||
| /* When you add this cpp file to your project, you mustn't include it in a file where you've | |||
| already included any other headers - just put it inside a file on its own, possibly with your config | |||
| flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix | |||
| header files that the compiler may be using. | |||
| */ | |||
| #error "Incorrect use of JUCE cpp file" | |||
| #endif | |||
| #define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 | |||
| #define JUCE_CORE_INCLUDE_JNI_HELPERS 1 | |||
| #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | |||
| #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | |||
| #define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1 | |||
| #if JUCE_USE_WINRT_MIDI | |||
| #define JUCE_EVENTS_INCLUDE_WINRT_WRAPPER 1 | |||
| #endif | |||
| #include "juce_events.h" | |||
| //============================================================================== | |||
| #if JUCE_MAC | |||
| #import <IOKit/IOKitLib.h> | |||
| #import <IOKit/IOCFPlugIn.h> | |||
| #import <IOKit/hid/IOHIDLib.h> | |||
| #import <IOKit/hid/IOHIDKeys.h> | |||
| #import <IOKit/pwr_mgt/IOPMLib.h> | |||
| #elif JUCE_LINUX | |||
| #include <unistd.h> | |||
| #endif | |||
| //============================================================================== | |||
| #include "messages/juce_ApplicationBase.cpp" | |||
| #include "messages/juce_DeletedAtShutdown.cpp" | |||
| #include "messages/juce_MessageListener.cpp" | |||
| #include "messages/juce_MessageManager.cpp" | |||
| #include "broadcasters/juce_ActionBroadcaster.cpp" | |||
| #include "broadcasters/juce_AsyncUpdater.cpp" | |||
| #include "broadcasters/juce_ChangeBroadcaster.cpp" | |||
| #include "timers/juce_MultiTimer.cpp" | |||
| #include "timers/juce_Timer.cpp" | |||
| #include "interprocess/juce_InterprocessConnection.cpp" | |||
| #include "interprocess/juce_InterprocessConnectionServer.cpp" | |||
| #include "interprocess/juce_ConnectedChildProcess.cpp" | |||
| //============================================================================== | |||
| #if JUCE_MAC || JUCE_IOS | |||
| #include "native/juce_osx_MessageQueue.h" | |||
| #if JUCE_CLANG | |||
| #pragma clang diagnostic push | |||
| #pragma clang diagnostic ignored "-Wundeclared-selector" | |||
| #endif | |||
| #if JUCE_MAC | |||
| #include "native/juce_mac_MessageManager.mm" | |||
| #else | |||
| #include "native/juce_ios_MessageManager.mm" | |||
| #endif | |||
| #if JUCE_CLANG | |||
| #pragma clang diagnostic pop | |||
| #endif | |||
| #elif JUCE_WINDOWS | |||
| #include "native/juce_win32_Messaging.cpp" | |||
| #if JUCE_EVENTS_INCLUDE_WINRT_WRAPPER | |||
| #include "native/juce_win32_WinRTWrapper.cpp" | |||
| #endif | |||
| #elif JUCE_LINUX | |||
| #include "native/juce_linux_Messaging.cpp" | |||
| #elif JUCE_ANDROID | |||
| #include "native/juce_android_Messaging.cpp" | |||
| #endif | |||
| @@ -0,0 +1,96 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| /******************************************************************************* | |||
| The block below describes the properties of this module, and is read by | |||
| the Projucer to automatically generate project code that uses it. | |||
| For details about the syntax and how to create or use a module, see the | |||
| JUCE Module Format.txt file. | |||
| BEGIN_JUCE_MODULE_DECLARATION | |||
| ID: juce_events | |||
| vendor: juce | |||
| version: 5.1.2 | |||
| name: JUCE message and event handling classes | |||
| description: Classes for running an application's main event loop and sending/receiving messages, timers, etc. | |||
| website: http://www.juce.com/juce | |||
| license: ISC | |||
| dependencies: juce_core | |||
| END_JUCE_MODULE_DECLARATION | |||
| *******************************************************************************/ | |||
| #pragma once | |||
| #define JUCE_EVENTS_H_INCLUDED | |||
| #include <juce_core/juce_core.h> | |||
| //============================================================================== | |||
| /** Config: JUCE_EXECUTE_APP_SUSPEND_ON_IOS_BACKGROUND_TASK | |||
| Will execute your application's suspend method on an iOS background task, giving | |||
| you extra time to save your applications state. | |||
| */ | |||
| #ifndef JUCE_EXECUTE_APP_SUSPEND_ON_BACKGROUND_TASK | |||
| #define JUCE_EXECUTE_APP_SUSPEND_ON_BACKGROUND_TASK 0 | |||
| #endif | |||
| #if JUCE_EVENTS_INCLUDE_WINRT_WRAPPER && JUCE_WINDOWS | |||
| #include <hstring.h> | |||
| #endif | |||
| #include "messages/juce_MessageManager.h" | |||
| #include "messages/juce_Message.h" | |||
| #include "messages/juce_MessageListener.h" | |||
| #include "messages/juce_CallbackMessage.h" | |||
| #include "messages/juce_DeletedAtShutdown.h" | |||
| #include "messages/juce_NotificationType.h" | |||
| #include "messages/juce_ApplicationBase.h" | |||
| #include "messages/juce_Initialisation.h" | |||
| #include "messages/juce_MountedVolumeListChangeDetector.h" | |||
| #include "broadcasters/juce_ActionBroadcaster.h" | |||
| #include "broadcasters/juce_ActionListener.h" | |||
| #include "broadcasters/juce_AsyncUpdater.h" | |||
| #include "broadcasters/juce_ChangeListener.h" | |||
| #include "broadcasters/juce_ChangeBroadcaster.h" | |||
| #include "timers/juce_Timer.h" | |||
| #include "timers/juce_MultiTimer.h" | |||
| #include "interprocess/juce_InterprocessConnection.h" | |||
| #include "interprocess/juce_InterprocessConnectionServer.h" | |||
| #include "interprocess/juce_ConnectedChildProcess.h" | |||
| #if JUCE_LINUX | |||
| #include "native/juce_linux_EventLoop.h" | |||
| #endif | |||
| #if JUCE_WINDOWS | |||
| #if JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW | |||
| #include "native/juce_win32_HiddenMessageWindow.h" | |||
| #endif | |||
| #if JUCE_EVENTS_INCLUDE_WINRT_WRAPPER | |||
| #include "native/juce_win32_WinRTWrapper.h" | |||
| #endif | |||
| #endif | |||
| @@ -0,0 +1,333 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| JUCEApplicationBase::CreateInstanceFunction JUCEApplicationBase::createInstance = 0; | |||
| JUCEApplicationBase* JUCEApplicationBase::appInstance = nullptr; | |||
| #if JUCE_IOS | |||
| void* JUCEApplicationBase::iOSCustomDelegate = nullptr; | |||
| #endif | |||
| JUCEApplicationBase::JUCEApplicationBase() | |||
| : appReturnValue (0), | |||
| stillInitialising (true) | |||
| { | |||
| jassert (isStandaloneApp() && appInstance == nullptr); | |||
| appInstance = this; | |||
| } | |||
| JUCEApplicationBase::~JUCEApplicationBase() | |||
| { | |||
| jassert (appInstance == this); | |||
| appInstance = nullptr; | |||
| } | |||
| void JUCEApplicationBase::setApplicationReturnValue (const int newReturnValue) noexcept | |||
| { | |||
| appReturnValue = newReturnValue; | |||
| } | |||
| // This is called on the Mac and iOS where the OS doesn't allow the stack to unwind on shutdown.. | |||
| void JUCEApplicationBase::appWillTerminateByForce() | |||
| { | |||
| JUCE_AUTORELEASEPOOL | |||
| { | |||
| { | |||
| const ScopedPointer<JUCEApplicationBase> app (appInstance); | |||
| if (app != nullptr) | |||
| app->shutdownApp(); | |||
| } | |||
| DeletedAtShutdown::deleteAll(); | |||
| MessageManager::deleteInstance(); | |||
| } | |||
| } | |||
| void JUCEApplicationBase::quit() | |||
| { | |||
| MessageManager::getInstance()->stopDispatchLoop(); | |||
| } | |||
| void JUCEApplicationBase::sendUnhandledException (const std::exception* const e, | |||
| const char* const sourceFile, | |||
| const int lineNumber) | |||
| { | |||
| if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) | |||
| { | |||
| // If you hit this assertion then the __FILE__ macro is providing a | |||
| // relative path instead of an absolute path. On Windows this will be | |||
| // a path relative to the build directory rather than the currently | |||
| // running application. To fix this you must compile with the /FC flag. | |||
| jassert (File::isAbsolutePath (sourceFile)); | |||
| app->unhandledException (e, sourceFile, lineNumber); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| #if ! (JUCE_IOS || JUCE_ANDROID) | |||
| #define JUCE_HANDLE_MULTIPLE_INSTANCES 1 | |||
| #endif | |||
| #if JUCE_HANDLE_MULTIPLE_INSTANCES | |||
| struct JUCEApplicationBase::MultipleInstanceHandler : public ActionListener | |||
| { | |||
| public: | |||
| MultipleInstanceHandler (const String& appName) | |||
| : appLock ("juceAppLock_" + appName) | |||
| { | |||
| } | |||
| bool sendCommandLineToPreexistingInstance() | |||
| { | |||
| if (appLock.enter (0)) | |||
| return false; | |||
| JUCEApplicationBase* const app = JUCEApplicationBase::getInstance(); | |||
| jassert (app != nullptr); | |||
| MessageManager::broadcastMessage (app->getApplicationName() | |||
| + "/" + app->getCommandLineParameters()); | |||
| return true; | |||
| } | |||
| void actionListenerCallback (const String& message) override | |||
| { | |||
| if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) | |||
| { | |||
| const String appName (app->getApplicationName()); | |||
| if (message.startsWith (appName + "/")) | |||
| app->anotherInstanceStarted (message.substring (appName.length() + 1)); | |||
| } | |||
| } | |||
| private: | |||
| InterProcessLock appLock; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultipleInstanceHandler) | |||
| }; | |||
| bool JUCEApplicationBase::sendCommandLineToPreexistingInstance() | |||
| { | |||
| jassert (multipleInstanceHandler == nullptr); // this must only be called once! | |||
| multipleInstanceHandler = new MultipleInstanceHandler (getApplicationName()); | |||
| return multipleInstanceHandler->sendCommandLineToPreexistingInstance(); | |||
| } | |||
| #else | |||
| struct JUCEApplicationBase::MultipleInstanceHandler {}; | |||
| #endif | |||
| //============================================================================== | |||
| #if JUCE_ANDROID | |||
| StringArray JUCEApplicationBase::getCommandLineParameterArray() { return {}; } | |||
| String JUCEApplicationBase::getCommandLineParameters() { return {}; } | |||
| #else | |||
| #if JUCE_WINDOWS && ! defined (_CONSOLE) | |||
| String JUCE_CALLTYPE JUCEApplicationBase::getCommandLineParameters() | |||
| { | |||
| return CharacterFunctions::findEndOfToken (CharPointer_UTF16 (GetCommandLineW()), | |||
| CharPointer_UTF16 (L" "), | |||
| CharPointer_UTF16 (L"\"")).findEndOfWhitespace(); | |||
| } | |||
| StringArray JUCE_CALLTYPE JUCEApplicationBase::getCommandLineParameterArray() | |||
| { | |||
| StringArray s; | |||
| int argc = 0; | |||
| if (LPWSTR* const argv = CommandLineToArgvW (GetCommandLineW(), &argc)) | |||
| { | |||
| s = StringArray (argv + 1, argc - 1); | |||
| LocalFree (argv); | |||
| } | |||
| return s; | |||
| } | |||
| #else | |||
| #if JUCE_IOS | |||
| extern int juce_iOSMain (int argc, const char* argv[], void* classPtr); | |||
| #endif | |||
| #if JUCE_MAC | |||
| extern void initialiseNSApplication(); | |||
| #endif | |||
| #if JUCE_LINUX && JUCE_MODULE_AVAILABLE_juce_gui_extra && (! defined(JUCE_WEB_BROWSER) || JUCE_WEB_BROWSER) | |||
| extern int juce_gtkWebkitMain (int argc, const char* argv[]); | |||
| #endif | |||
| #if JUCE_WINDOWS | |||
| const char* const* juce_argv = nullptr; | |||
| int juce_argc = 0; | |||
| #else | |||
| extern const char* const* juce_argv; // declared in juce_core | |||
| extern int juce_argc; | |||
| #endif | |||
| String JUCEApplicationBase::getCommandLineParameters() | |||
| { | |||
| String argString; | |||
| for (int i = 1; i < juce_argc; ++i) | |||
| { | |||
| String arg (juce_argv[i]); | |||
| if (arg.containsChar (' ') && ! arg.isQuotedString()) | |||
| arg = arg.quoted ('"'); | |||
| argString << arg << ' '; | |||
| } | |||
| return argString.trim(); | |||
| } | |||
| StringArray JUCEApplicationBase::getCommandLineParameterArray() | |||
| { | |||
| return StringArray (juce_argv + 1, juce_argc - 1); | |||
| } | |||
| int JUCEApplicationBase::main (int argc, const char* argv[]) | |||
| { | |||
| JUCE_AUTORELEASEPOOL | |||
| { | |||
| juce_argc = argc; | |||
| juce_argv = argv; | |||
| #if JUCE_MAC | |||
| initialiseNSApplication(); | |||
| #endif | |||
| #if JUCE_LINUX && JUCE_MODULE_AVAILABLE_juce_gui_extra && (! defined(JUCE_WEB_BROWSER) || JUCE_WEB_BROWSER) | |||
| if (argc >= 2 && String (argv[1]) == "--juce-gtkwebkitfork-child") | |||
| return juce_gtkWebkitMain (argc, argv); | |||
| #endif | |||
| #if JUCE_IOS | |||
| return juce_iOSMain (argc, argv, iOSCustomDelegate); | |||
| #else | |||
| return JUCEApplicationBase::main(); | |||
| #endif | |||
| } | |||
| } | |||
| #endif | |||
| //============================================================================== | |||
| int JUCEApplicationBase::main() | |||
| { | |||
| ScopedJuceInitialiser_GUI libraryInitialiser; | |||
| jassert (createInstance != nullptr); | |||
| const ScopedPointer<JUCEApplicationBase> app (createInstance()); | |||
| jassert (app != nullptr); | |||
| if (! app->initialiseApp()) | |||
| return app->shutdownApp(); | |||
| JUCE_TRY | |||
| { | |||
| // loop until a quit message is received.. | |||
| MessageManager::getInstance()->runDispatchLoop(); | |||
| } | |||
| JUCE_CATCH_EXCEPTION | |||
| return app->shutdownApp(); | |||
| } | |||
| #endif | |||
| //============================================================================== | |||
| bool JUCEApplicationBase::initialiseApp() | |||
| { | |||
| #if JUCE_HANDLE_MULTIPLE_INSTANCES | |||
| if ((! moreThanOneInstanceAllowed()) && sendCommandLineToPreexistingInstance()) | |||
| { | |||
| DBG ("Another instance is running - quitting..."); | |||
| return false; | |||
| } | |||
| #endif | |||
| #if JUCE_WINDOWS && JUCE_STANDALONE_APPLICATION && (! defined (_CONSOLE)) && (! JUCE_MINGW) | |||
| if (AttachConsole (ATTACH_PARENT_PROCESS) != 0) | |||
| { | |||
| // if we've launched a GUI app from cmd.exe or PowerShell, we need this to enable printf etc. | |||
| // However, only reassign stdout, stderr, stdin if they have not been already opened by | |||
| // a redirect or similar. | |||
| FILE* ignore; | |||
| if (_fileno(stdout) < 0) freopen_s (&ignore, "CONOUT$", "w", stdout); | |||
| if (_fileno(stderr) < 0) freopen_s (&ignore, "CONOUT$", "w", stderr); | |||
| if (_fileno(stdin) < 0) freopen_s (&ignore, "CONIN$", "r", stdin); | |||
| } | |||
| #endif | |||
| // let the app do its setting-up.. | |||
| initialise (getCommandLineParameters()); | |||
| stillInitialising = false; | |||
| if (MessageManager::getInstance()->hasStopMessageBeenSent()) | |||
| return false; | |||
| #if JUCE_HANDLE_MULTIPLE_INSTANCES | |||
| if (multipleInstanceHandler != nullptr) | |||
| MessageManager::getInstance()->registerBroadcastListener (multipleInstanceHandler); | |||
| #endif | |||
| return true; | |||
| } | |||
| int JUCEApplicationBase::shutdownApp() | |||
| { | |||
| jassert (JUCEApplicationBase::getInstance() == this); | |||
| #if JUCE_HANDLE_MULTIPLE_INSTANCES | |||
| if (multipleInstanceHandler != nullptr) | |||
| MessageManager::getInstance()->deregisterBroadcastListener (multipleInstanceHandler); | |||
| #endif | |||
| JUCE_TRY | |||
| { | |||
| // give the app a chance to clean up.. | |||
| shutdown(); | |||
| } | |||
| JUCE_CATCH_EXCEPTION | |||
| multipleInstanceHandler = nullptr; | |||
| return getApplicationReturnValue(); | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,312 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Abstract base class for application classes. | |||
| Note that in the juce_gui_basics module, there's a utility class JUCEApplication | |||
| which derives from JUCEApplicationBase, and takes care of a few chores. Most | |||
| of the time you'll want to derive your class from JUCEApplication rather than | |||
| using JUCEApplicationBase directly, but if you're not using the juce_gui_basics | |||
| module then you might need to go straight to this base class. | |||
| Any application that wants to run an event loop must declare a subclass of | |||
| JUCEApplicationBase, and implement its various pure virtual methods. | |||
| It then needs to use the START_JUCE_APPLICATION macro somewhere in a CPP file | |||
| to declare an instance of this class and generate suitable platform-specific | |||
| boilerplate code to launch the app. | |||
| e.g. @code | |||
| class MyJUCEApp : public JUCEApplication | |||
| { | |||
| public: | |||
| MyJUCEApp() {} | |||
| ~MyJUCEApp() {} | |||
| void initialise (const String& commandLine) override | |||
| { | |||
| myMainWindow = new MyApplicationWindow(); | |||
| myMainWindow->setBounds (100, 100, 400, 500); | |||
| myMainWindow->setVisible (true); | |||
| } | |||
| void shutdown() override | |||
| { | |||
| myMainWindow = nullptr; | |||
| } | |||
| const String getApplicationName() override | |||
| { | |||
| return "Super JUCE-o-matic"; | |||
| } | |||
| const String getApplicationVersion() override | |||
| { | |||
| return "1.0"; | |||
| } | |||
| private: | |||
| ScopedPointer<MyApplicationWindow> myMainWindow; | |||
| }; | |||
| // this generates boilerplate code to launch our app class: | |||
| START_JUCE_APPLICATION (MyJUCEApp) | |||
| @endcode | |||
| @see JUCEApplication, START_JUCE_APPLICATION | |||
| */ | |||
| class JUCE_API JUCEApplicationBase | |||
| { | |||
| protected: | |||
| //============================================================================== | |||
| JUCEApplicationBase(); | |||
| public: | |||
| /** Destructor. */ | |||
| virtual ~JUCEApplicationBase(); | |||
| //============================================================================== | |||
| /** Returns the global instance of the application object that's running. */ | |||
| static JUCEApplicationBase* getInstance() noexcept { return appInstance; } | |||
| //============================================================================== | |||
| /** Returns the application's name. */ | |||
| virtual const String getApplicationName() = 0; | |||
| /** Returns the application's version number. */ | |||
| virtual const String getApplicationVersion() = 0; | |||
| /** Checks whether multiple instances of the app are allowed. | |||
| If you application class returns true for this, more than one instance is | |||
| permitted to run (except on the Mac where this isn't possible). | |||
| If it's false, the second instance won't start, but it you will still get a | |||
| callback to anotherInstanceStarted() to tell you about this - which | |||
| gives you a chance to react to what the user was trying to do. | |||
| */ | |||
| virtual bool moreThanOneInstanceAllowed() = 0; | |||
| /** Called when the application starts. | |||
| This will be called once to let the application do whatever initialisation | |||
| it needs, create its windows, etc. | |||
| After the method returns, the normal event-dispatch loop will be run, | |||
| until the quit() method is called, at which point the shutdown() | |||
| method will be called to let the application clear up anything it needs | |||
| to delete. | |||
| If during the initialise() method, the application decides not to start-up | |||
| after all, it can just call the quit() method and the event loop won't be run. | |||
| @param commandLineParameters the line passed in does not include the name of | |||
| the executable, just the parameter list. To get the | |||
| parameters as an array, you can call | |||
| JUCEApplication::getCommandLineParameters() | |||
| @see shutdown, quit | |||
| */ | |||
| virtual void initialise (const String& commandLineParameters) = 0; | |||
| /* Called to allow the application to clear up before exiting. | |||
| After JUCEApplication::quit() has been called, the event-dispatch loop will | |||
| terminate, and this method will get called to allow the app to sort itself | |||
| out. | |||
| Be careful that nothing happens in this method that might rely on messages | |||
| being sent, or any kind of window activity, because the message loop is no | |||
| longer running at this point. | |||
| @see DeletedAtShutdown | |||
| */ | |||
| virtual void shutdown() = 0; | |||
| /** Indicates that the user has tried to start up another instance of the app. | |||
| This will get called even if moreThanOneInstanceAllowed() is false. | |||
| */ | |||
| virtual void anotherInstanceStarted (const String& commandLine) = 0; | |||
| /** Called when the operating system is trying to close the application. | |||
| The default implementation of this method is to call quit(), but it may | |||
| be overloaded to ignore the request or do some other special behaviour | |||
| instead. For example, you might want to offer the user the chance to save | |||
| their changes before quitting, and give them the chance to cancel. | |||
| If you want to send a quit signal to your app, this is the correct method | |||
| to call, because it means that requests that come from the system get handled | |||
| in the same way as those from your own application code. So e.g. you'd | |||
| call this method from a "quit" item on a menu bar. | |||
| */ | |||
| virtual void systemRequestedQuit() = 0; | |||
| /** This method is called when the application is being put into background mode | |||
| by the operating system. | |||
| */ | |||
| virtual void suspended() = 0; | |||
| /** This method is called when the application is being woken from background mode | |||
| by the operating system. | |||
| */ | |||
| virtual void resumed() = 0; | |||
| /** If any unhandled exceptions make it through to the message dispatch loop, this | |||
| callback will be triggered, in case you want to log them or do some other | |||
| type of error-handling. | |||
| If the type of exception is derived from the std::exception class, the pointer | |||
| passed-in will be valid. If the exception is of unknown type, this pointer | |||
| will be null. | |||
| */ | |||
| virtual void unhandledException (const std::exception*, | |||
| const String& sourceFilename, | |||
| int lineNumber) = 0; | |||
| //============================================================================== | |||
| /** Override this method to be informed when the back button is pressed on a device. | |||
| This is currently only implemented on Android devices. | |||
| */ | |||
| virtual void backButtonPressed() { } | |||
| //============================================================================== | |||
| /** Signals that the main message loop should stop and the application should terminate. | |||
| This isn't synchronous, it just posts a quit message to the main queue, and | |||
| when this message arrives, the message loop will stop, the shutdown() method | |||
| will be called, and the app will exit. | |||
| Note that this will cause an unconditional quit to happen, so if you need an | |||
| extra level before this, e.g. to give the user the chance to save their work | |||
| and maybe cancel the quit, you'll need to handle this in the systemRequestedQuit() | |||
| method - see that method's help for more info. | |||
| @see MessageManager | |||
| */ | |||
| static void quit(); | |||
| //============================================================================== | |||
| /** Returns the application's command line parameters as a set of strings. | |||
| @see getCommandLineParameters | |||
| */ | |||
| static StringArray JUCE_CALLTYPE getCommandLineParameterArray(); | |||
| /** Returns the application's command line parameters as a single string. | |||
| @see getCommandLineParameterArray | |||
| */ | |||
| static String JUCE_CALLTYPE getCommandLineParameters(); | |||
| //============================================================================== | |||
| /** Sets the value that should be returned as the application's exit code when the | |||
| app quits. | |||
| This is the value that's returned by the main() function. Normally you'd leave this | |||
| as 0 unless you want to indicate an error code. | |||
| @see getApplicationReturnValue | |||
| */ | |||
| void setApplicationReturnValue (int newReturnValue) noexcept; | |||
| /** Returns the value that has been set as the application's exit code. | |||
| @see setApplicationReturnValue | |||
| */ | |||
| int getApplicationReturnValue() const noexcept { return appReturnValue; } | |||
| //============================================================================== | |||
| /** Returns true if this executable is running as an app (as opposed to being a plugin | |||
| or other kind of shared library. */ | |||
| static bool isStandaloneApp() noexcept { return createInstance != nullptr; } | |||
| /** Returns true if the application hasn't yet completed its initialise() method | |||
| and entered the main event loop. | |||
| This is handy for things like splash screens to know when the app's up-and-running | |||
| properly. | |||
| */ | |||
| bool isInitialising() const noexcept { return stillInitialising; } | |||
| //============================================================================== | |||
| #ifndef DOXYGEN | |||
| // The following methods are for internal use only... | |||
| static int main(); | |||
| static int main (int argc, const char* argv[]); | |||
| static void appWillTerminateByForce(); | |||
| typedef JUCEApplicationBase* (*CreateInstanceFunction)(); | |||
| static CreateInstanceFunction createInstance; | |||
| #if JUCE_IOS | |||
| static void* iOSCustomDelegate; | |||
| #endif | |||
| virtual bool initialiseApp(); | |||
| int shutdownApp(); | |||
| static void JUCE_CALLTYPE sendUnhandledException (const std::exception*, const char* sourceFile, int lineNumber); | |||
| bool sendCommandLineToPreexistingInstance(); | |||
| #endif | |||
| private: | |||
| //============================================================================== | |||
| static JUCEApplicationBase* appInstance; | |||
| int appReturnValue; | |||
| bool stillInitialising; | |||
| struct MultipleInstanceHandler; | |||
| friend struct MultipleInstanceHandler; | |||
| friend struct ContainerDeletePolicy<MultipleInstanceHandler>; | |||
| ScopedPointer<MultipleInstanceHandler> multipleInstanceHandler; | |||
| JUCE_DECLARE_NON_COPYABLE (JUCEApplicationBase) | |||
| }; | |||
| //============================================================================== | |||
| #if JUCE_CATCH_UNHANDLED_EXCEPTIONS || defined (DOXYGEN) | |||
| /** The JUCE_TRY/JUCE_CATCH_EXCEPTION wrappers can be used to pass any uncaught exceptions to | |||
| the JUCEApplicationBase::sendUnhandledException() method. | |||
| This functionality can be enabled with the JUCE_CATCH_UNHANDLED_EXCEPTIONS macro. | |||
| */ | |||
| #define JUCE_TRY try | |||
| /** The JUCE_TRY/JUCE_CATCH_EXCEPTION wrappers can be used to pass any uncaught exceptions to | |||
| the JUCEApplicationBase::sendUnhandledException() method. | |||
| This functionality can be enabled with the JUCE_CATCH_UNHANDLED_EXCEPTIONS macro. | |||
| */ | |||
| #define JUCE_CATCH_EXCEPTION \ | |||
| catch (const std::exception& e) { juce::JUCEApplicationBase::sendUnhandledException (&e, __FILE__, __LINE__); } \ | |||
| catch (...) { juce::JUCEApplicationBase::sendUnhandledException (nullptr, __FILE__, __LINE__); } | |||
| #else | |||
| #define JUCE_TRY | |||
| #define JUCE_CATCH_EXCEPTION | |||
| #endif | |||
| } // namespace juce | |||
| @@ -0,0 +1,72 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| A message that invokes a callback method when it gets delivered. | |||
| You can use this class to fire off actions that you want to be performed later | |||
| on the message thread. | |||
| To use it, create a subclass of CallbackMessage which implements the messageCallback() | |||
| method, then call post() to dispatch it. The event thread will then invoke your | |||
| messageCallback() method later on, and will automatically delete the message object | |||
| afterwards. | |||
| Always create a new instance of a CallbackMessage on the heap, as it will be | |||
| deleted automatically after the message has been delivered. | |||
| Note that this class was essential back in the days before C++11, but in modern | |||
| times you may prefer to use MessageManager::callAsync() with a lambda. | |||
| @see MessageManager::callAsync, MessageListener, ActionListener, ChangeListener | |||
| */ | |||
| class JUCE_API CallbackMessage : public MessageManager::MessageBase | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| CallbackMessage() noexcept {} | |||
| /** Destructor. */ | |||
| ~CallbackMessage() {} | |||
| //============================================================================== | |||
| /** Called when the message is delivered. | |||
| You should implement this method and make it do whatever action you want | |||
| to perform. | |||
| Note that like all other messages, this object will be deleted immediately | |||
| after this method has been invoked. | |||
| */ | |||
| virtual void messageCallback() = 0; | |||
| private: | |||
| // Avoid the leak-detector because for plugins, the host can unload our DLL with undelivered | |||
| // messages still in the system event queue. These aren't harmful, but can cause annoying assertions. | |||
| JUCE_DECLARE_NON_COPYABLE (CallbackMessage) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,94 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| static SpinLock deletedAtShutdownLock; // use a spin lock because it can be statically initialised | |||
| static Array<DeletedAtShutdown*>& getDeletedAtShutdownObjects() | |||
| { | |||
| static Array<DeletedAtShutdown*> objects; | |||
| return objects; | |||
| } | |||
| DeletedAtShutdown::DeletedAtShutdown() | |||
| { | |||
| const SpinLock::ScopedLockType sl (deletedAtShutdownLock); | |||
| getDeletedAtShutdownObjects().add (this); | |||
| } | |||
| DeletedAtShutdown::~DeletedAtShutdown() | |||
| { | |||
| const SpinLock::ScopedLockType sl (deletedAtShutdownLock); | |||
| getDeletedAtShutdownObjects().removeFirstMatchingValue (this); | |||
| } | |||
| #if JUCE_MSVC | |||
| // Disable unreachable code warning, in case the compiler manages to figure out that | |||
| // you have no classes of DeletedAtShutdown that could throw an exception in their destructor. | |||
| #pragma warning (push) | |||
| #pragma warning (disable: 4702) | |||
| #endif | |||
| void DeletedAtShutdown::deleteAll() | |||
| { | |||
| // make a local copy of the array, so it can't get into a loop if something | |||
| // creates another DeletedAtShutdown object during its destructor. | |||
| Array<DeletedAtShutdown*> localCopy; | |||
| { | |||
| const SpinLock::ScopedLockType sl (deletedAtShutdownLock); | |||
| localCopy = getDeletedAtShutdownObjects(); | |||
| } | |||
| for (int i = localCopy.size(); --i >= 0;) | |||
| { | |||
| JUCE_TRY | |||
| { | |||
| auto* deletee = localCopy.getUnchecked(i); | |||
| // double-check that it's not already been deleted during another object's destructor. | |||
| { | |||
| const SpinLock::ScopedLockType sl (deletedAtShutdownLock); | |||
| if (! getDeletedAtShutdownObjects().contains (deletee)) | |||
| deletee = nullptr; | |||
| } | |||
| delete deletee; | |||
| } | |||
| JUCE_CATCH_EXCEPTION | |||
| } | |||
| // if this fails, then it's likely that some new DeletedAtShutdown objects were | |||
| // created while executing the destructors of the other ones. | |||
| jassert (getDeletedAtShutdownObjects().isEmpty()); | |||
| getDeletedAtShutdownObjects().clear(); // just to make sure the array doesn't have any memory still allocated | |||
| } | |||
| #if JUCE_MSVC | |||
| #pragma warning (pop) | |||
| #endif | |||
| } // namespace juce | |||
| @@ -0,0 +1,63 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Classes derived from this will be automatically deleted when the application exits. | |||
| After JUCEApplicationBase::shutdown() has been called, any objects derived from | |||
| DeletedAtShutdown which are still in existence will be deleted in the reverse | |||
| order to that in which they were created. | |||
| So if you've got a singleton and don't want to have to explicitly delete it, just | |||
| inherit from this and it'll be taken care of. | |||
| */ | |||
| class JUCE_API DeletedAtShutdown | |||
| { | |||
| protected: | |||
| /** Creates a DeletedAtShutdown object. */ | |||
| DeletedAtShutdown(); | |||
| /** Destructor. | |||
| It's ok to delete these objects explicitly - it's only the ones left | |||
| dangling at the end that will be deleted automatically. | |||
| */ | |||
| virtual ~DeletedAtShutdown(); | |||
| public: | |||
| /** Deletes all extant objects. | |||
| This shouldn't be used by applications, as it's called automatically | |||
| in the shutdown code of the JUCEApplicationBase class. | |||
| */ | |||
| static void deleteAll(); | |||
| private: | |||
| JUCE_DECLARE_NON_COPYABLE (DeletedAtShutdown) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,200 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** Initialises Juce's GUI classes. | |||
| If you're embedding Juce into an application that uses its own event-loop rather | |||
| than using the START_JUCE_APPLICATION macro, call this function before making any | |||
| Juce calls, to make sure things are initialised correctly. | |||
| Note that if you're creating a Juce DLL for Windows, you may also need to call the | |||
| Process::setCurrentModuleInstanceHandle() method. | |||
| @see shutdownJuce_GUI() | |||
| */ | |||
| JUCE_API void JUCE_CALLTYPE initialiseJuce_GUI(); | |||
| /** Clears up any static data being used by Juce's GUI classes. | |||
| If you're embedding Juce into an application that uses its own event-loop rather | |||
| than using the START_JUCE_APPLICATION macro, call this function in your shutdown | |||
| code to clean up any juce objects that might be lying around. | |||
| @see initialiseJuce_GUI() | |||
| */ | |||
| JUCE_API void JUCE_CALLTYPE shutdownJuce_GUI(); | |||
| //============================================================================== | |||
| /** A utility object that helps you initialise and shutdown Juce correctly | |||
| using an RAII pattern. | |||
| When the first instance of this class is created, it calls initialiseJuce_GUI(), | |||
| and when the last instance is deleted, it calls shutdownJuce_GUI(), so that you | |||
| can easily be sure that as long as at least one instance of the class exists, the | |||
| library will be initialised. | |||
| This class is particularly handy to use at the beginning of a console app's | |||
| main() function, because it'll take care of shutting down whenever you return | |||
| from the main() call. | |||
| Be careful with your threading though - to be safe, you should always make sure | |||
| that these objects are created and deleted on the message thread. | |||
| */ | |||
| class JUCE_API ScopedJuceInitialiser_GUI | |||
| { | |||
| public: | |||
| /** The constructor simply calls initialiseJuce_GUI(). */ | |||
| ScopedJuceInitialiser_GUI(); | |||
| /** The destructor simply calls shutdownJuce_GUI(). */ | |||
| ~ScopedJuceInitialiser_GUI(); | |||
| }; | |||
| //============================================================================== | |||
| /** | |||
| To start a JUCE app, use this macro: START_JUCE_APPLICATION (AppSubClass) where | |||
| AppSubClass is the name of a class derived from JUCEApplication or JUCEApplicationBase. | |||
| See the JUCEApplication and JUCEApplicationBase class documentation for more details. | |||
| */ | |||
| #ifdef DOXYGEN | |||
| #define START_JUCE_APPLICATION(AppClass) | |||
| #else | |||
| #if JUCE_WINDOWS && ! defined (_CONSOLE) | |||
| #define JUCE_MAIN_FUNCTION int __stdcall WinMain (struct HINSTANCE__*, struct HINSTANCE__*, char*, int) | |||
| #define JUCE_MAIN_FUNCTION_ARGS | |||
| #else | |||
| #define JUCE_MAIN_FUNCTION int main (int argc, char* argv[]) | |||
| #define JUCE_MAIN_FUNCTION_ARGS argc, (const char**) argv | |||
| #endif | |||
| #if JUCE_IOS | |||
| #define JUCE_CREATE_APPLICATION_DEFINE(AppClass) \ | |||
| juce::JUCEApplicationBase* juce_CreateApplication() { return new AppClass(); } \ | |||
| void* juce_GetIOSCustomDelegateClass() { return nullptr; } | |||
| #define JUCE_CREATE_APPLICATION_DEFINE_CUSTOM_DELEGATE(AppClass, DelegateClass) \ | |||
| juce::JUCEApplicationBase* juce_CreateApplication() { return new AppClass(); } \ | |||
| void* juce_GetIOSCustomDelegateClass() { return [DelegateClass class]; } | |||
| #define JUCE_MAIN_FUNCTION_DEFINITION \ | |||
| extern "C" JUCE_MAIN_FUNCTION \ | |||
| { \ | |||
| juce::JUCEApplicationBase::createInstance = &juce_CreateApplication; \ | |||
| juce::JUCEApplicationBase::iOSCustomDelegate = juce_GetIOSCustomDelegateClass(); \ | |||
| return juce::JUCEApplicationBase::main (JUCE_MAIN_FUNCTION_ARGS); \ | |||
| } | |||
| #elif JUCE_ANDROID | |||
| #define JUCE_CREATE_APPLICATION_DEFINE(AppClass) \ | |||
| juce::JUCEApplicationBase* juce_CreateApplication() { return new AppClass(); } | |||
| #define JUCE_MAIN_FUNCTION_DEFINITION | |||
| #else | |||
| #define JUCE_CREATE_APPLICATION_DEFINE(AppClass) \ | |||
| juce::JUCEApplicationBase* juce_CreateApplication() { return new AppClass(); } | |||
| #define JUCE_MAIN_FUNCTION_DEFINITION \ | |||
| extern "C" JUCE_MAIN_FUNCTION \ | |||
| { \ | |||
| juce::JUCEApplicationBase::createInstance = &juce_CreateApplication; \ | |||
| return juce::JUCEApplicationBase::main (JUCE_MAIN_FUNCTION_ARGS); \ | |||
| } | |||
| #endif | |||
| #if JucePlugin_Build_Standalone | |||
| #if JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP | |||
| #define START_JUCE_APPLICATION(AppClass) JUCE_CREATE_APPLICATION_DEFINE(AppClass) | |||
| #if JUCE_IOS | |||
| #define START_JUCE_APPLICATION_WITH_CUSTOM_DELEGATE(AppClass, DelegateClass) JUCE_CREATE_APPLICATION_DEFINE_CUSTOM_DELEGATE(AppClass, DelegateClass) | |||
| #endif | |||
| #else | |||
| #define START_JUCE_APPLICATION(AppClass) static_assert(false, "You are trying to use START_JUCE_APPLICATION in an audio plug-in. Define JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1 if you want to use a custom standalone target app."); | |||
| #if JUCE_IOS | |||
| #define START_JUCE_APPLICATION_WITH_CUSTOM_DELEGATE(AppClass, DelegateClass) static_assert(false, "You are trying to use START_JUCE_APPLICATION in an audio plug-in. Define JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1 if you want to use a custom standalone target app."); | |||
| #endif | |||
| #endif | |||
| #else | |||
| #define START_JUCE_APPLICATION(AppClass) \ | |||
| JUCE_CREATE_APPLICATION_DEFINE(AppClass) \ | |||
| JUCE_MAIN_FUNCTION_DEFINITION | |||
| #if JUCE_IOS | |||
| /** | |||
| You can instruct JUCE to use a custom iOS app delegate class instaed of JUCE's default | |||
| app delegate. For JUCE to work you must pass all messages to JUCE's internal app delegate. | |||
| Below is an example of minimal forwarding custom delegate. Note that you are at your own | |||
| risk if you decide to use your own delegate an subtle, hard to debug bugs may occur. | |||
| @interface MyCustomDelegate : NSObject <UIApplicationDelegate> { NSObject<UIApplicationDelegate>* juceDelegate; } @end | |||
| @implementation MyCustomDelegate | |||
| -(id) init | |||
| { | |||
| self = [super init]; | |||
| juceDelegate = reinterpret_cast<NSObject<UIApplicationDelegate>*> ([[NSClassFromString (@"JuceAppStartupDelegate") alloc] init]); | |||
| return self; | |||
| } | |||
| -(void)dealloc | |||
| { | |||
| [juceDelegate release]; | |||
| [super dealloc]; | |||
| } | |||
| - (void)forwardInvocation:(NSInvocation *)anInvocation | |||
| { | |||
| if (juceDelegate != nullptr && [juceDelegate respondsToSelector:[anInvocation selector]]) | |||
| [anInvocation invokeWithTarget:juceDelegate]; | |||
| else | |||
| [super forwardInvocation:anInvocation]; | |||
| } | |||
| -(BOOL)respondsToSelector:(SEL)aSelector | |||
| { | |||
| if (juceDelegate != nullptr && [juceDelegate respondsToSelector:aSelector]) | |||
| return YES; | |||
| return [super respondsToSelector:aSelector]; | |||
| } | |||
| @end | |||
| */ | |||
| #define START_JUCE_APPLICATION_WITH_CUSTOM_DELEGATE(AppClass, DelegateClass) \ | |||
| JUCE_CREATE_APPLICATION_DEFINE_CUSTOM_DELEGATE(AppClass, DelegateClass) \ | |||
| JUCE_MAIN_FUNCTION_DEFINITION | |||
| #endif | |||
| #endif | |||
| #endif | |||
| } // namespace juce | |||
| @@ -0,0 +1,62 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| class MessageListener; | |||
| //============================================================================== | |||
| /** The base class for objects that can be sent to a MessageListener. | |||
| If you want to send a message that carries some kind of custom data, just | |||
| create a subclass of Message with some appropriate member variables to hold | |||
| your data. | |||
| Always create a new instance of a Message object on the heap, as it will be | |||
| deleted automatically after the message has been delivered. | |||
| @see MessageListener, MessageManager, ActionListener, ChangeListener | |||
| */ | |||
| class JUCE_API Message : public MessageManager::MessageBase | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates an uninitialised message. */ | |||
| Message() noexcept; | |||
| ~Message(); | |||
| typedef ReferenceCountedObjectPtr<Message> Ptr; | |||
| //============================================================================== | |||
| private: | |||
| friend class MessageListener; | |||
| WeakReference<MessageListener> recipient; | |||
| void messageCallback() override; | |||
| // Avoid the leak-detector because for plugins, the host can unload our DLL with undelivered | |||
| // messages still in the system event queue. These aren't harmful, but can cause annoying assertions. | |||
| JUCE_DECLARE_NON_COPYABLE (Message) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,52 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| Message::Message() noexcept {} | |||
| Message::~Message() {} | |||
| void Message::messageCallback() | |||
| { | |||
| if (MessageListener* const r = recipient) | |||
| r->handleMessage (*this); | |||
| } | |||
| MessageListener::MessageListener() noexcept | |||
| { | |||
| // Are you trying to create a messagelistener before or after juce has been intialised?? | |||
| jassert (MessageManager::getInstanceWithoutCreating() != nullptr); | |||
| } | |||
| MessageListener::~MessageListener() | |||
| { | |||
| masterReference.clear(); | |||
| } | |||
| void MessageListener::postMessage (Message* const message) const | |||
| { | |||
| message->recipient = const_cast<MessageListener*> (this); | |||
| message->post(); | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,68 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| MessageListener subclasses can post and receive Message objects. | |||
| @see Message, MessageManager, ActionListener, ChangeListener | |||
| */ | |||
| class JUCE_API MessageListener | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| MessageListener() noexcept; | |||
| /** Destructor. */ | |||
| virtual ~MessageListener(); | |||
| //============================================================================== | |||
| /** This is the callback method that receives incoming messages. | |||
| This is called by the MessageManager from its dispatch loop. | |||
| @see postMessage | |||
| */ | |||
| virtual void handleMessage (const Message& message) = 0; | |||
| //============================================================================== | |||
| /** Sends a message to the message queue, for asynchronous delivery to this listener | |||
| later on. | |||
| This method can be called safely by any thread. | |||
| @param message the message object to send - this will be deleted | |||
| automatically by the message queue, so make sure it's | |||
| allocated on the heap, not the stack! | |||
| @see handleMessage | |||
| */ | |||
| void postMessage (Message* message) const; | |||
| private: | |||
| WeakReference<MessageListener>::Master masterReference; | |||
| friend class WeakReference<MessageListener>; | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,387 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| MessageManager::MessageManager() noexcept | |||
| : messageThreadId (Thread::getCurrentThreadId()) | |||
| { | |||
| if (JUCEApplicationBase::isStandaloneApp()) | |||
| Thread::setCurrentThreadName ("Juce Message Thread"); | |||
| } | |||
| MessageManager::~MessageManager() noexcept | |||
| { | |||
| broadcaster = nullptr; | |||
| doPlatformSpecificShutdown(); | |||
| jassert (instance == this); | |||
| instance = nullptr; // do this last in case this instance is still needed by doPlatformSpecificShutdown() | |||
| } | |||
| MessageManager* MessageManager::instance = nullptr; | |||
| MessageManager* MessageManager::getInstance() | |||
| { | |||
| if (instance == nullptr) | |||
| { | |||
| instance = new MessageManager(); | |||
| doPlatformSpecificInitialisation(); | |||
| } | |||
| return instance; | |||
| } | |||
| MessageManager* MessageManager::getInstanceWithoutCreating() noexcept | |||
| { | |||
| return instance; | |||
| } | |||
| void MessageManager::deleteInstance() | |||
| { | |||
| deleteAndZero (instance); | |||
| } | |||
| //============================================================================== | |||
| bool MessageManager::MessageBase::post() | |||
| { | |||
| auto* mm = MessageManager::instance; | |||
| if (mm == nullptr || mm->quitMessagePosted || ! postMessageToSystemQueue (this)) | |||
| { | |||
| Ptr deleter (this); // (this will delete messages that were just created with a 0 ref count) | |||
| return false; | |||
| } | |||
| return true; | |||
| } | |||
| //============================================================================== | |||
| #if JUCE_MODAL_LOOPS_PERMITTED && ! (JUCE_MAC || JUCE_IOS) | |||
| bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) | |||
| { | |||
| jassert (isThisTheMessageThread()); // must only be called by the message thread | |||
| const int64 endTime = Time::currentTimeMillis() + millisecondsToRunFor; | |||
| while (! quitMessageReceived) | |||
| { | |||
| JUCE_TRY | |||
| { | |||
| if (! dispatchNextMessageOnSystemQueue (millisecondsToRunFor >= 0)) | |||
| Thread::sleep (1); | |||
| } | |||
| JUCE_CATCH_EXCEPTION | |||
| if (millisecondsToRunFor >= 0 && Time::currentTimeMillis() >= endTime) | |||
| break; | |||
| } | |||
| return ! quitMessageReceived; | |||
| } | |||
| #endif | |||
| #if ! (JUCE_MAC || JUCE_IOS || JUCE_ANDROID) | |||
| class MessageManager::QuitMessage : public MessageManager::MessageBase | |||
| { | |||
| public: | |||
| QuitMessage() {} | |||
| void messageCallback() override | |||
| { | |||
| if (auto* mm = MessageManager::instance) | |||
| mm->quitMessageReceived = true; | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE (QuitMessage) | |||
| }; | |||
| void MessageManager::runDispatchLoop() | |||
| { | |||
| jassert (isThisTheMessageThread()); // must only be called by the message thread | |||
| while (! quitMessageReceived) | |||
| { | |||
| JUCE_TRY | |||
| { | |||
| if (! dispatchNextMessageOnSystemQueue (false)) | |||
| Thread::sleep (1); | |||
| } | |||
| JUCE_CATCH_EXCEPTION | |||
| } | |||
| } | |||
| void MessageManager::stopDispatchLoop() | |||
| { | |||
| (new QuitMessage())->post(); | |||
| quitMessagePosted = true; | |||
| } | |||
| #endif | |||
| //============================================================================== | |||
| class AsyncFunctionCallback : public MessageManager::MessageBase | |||
| { | |||
| public: | |||
| AsyncFunctionCallback (MessageCallbackFunction* const f, void* const param) | |||
| : func (f), parameter (param) | |||
| {} | |||
| void messageCallback() override | |||
| { | |||
| result = (*func) (parameter); | |||
| finished.signal(); | |||
| } | |||
| WaitableEvent finished; | |||
| void* volatile result = nullptr; | |||
| private: | |||
| MessageCallbackFunction* const func; | |||
| void* const parameter; | |||
| JUCE_DECLARE_NON_COPYABLE (AsyncFunctionCallback) | |||
| }; | |||
| void* MessageManager::callFunctionOnMessageThread (MessageCallbackFunction* const func, void* const parameter) | |||
| { | |||
| if (isThisTheMessageThread()) | |||
| return func (parameter); | |||
| // If this thread has the message manager locked, then this will deadlock! | |||
| jassert (! currentThreadHasLockedMessageManager()); | |||
| const ReferenceCountedObjectPtr<AsyncFunctionCallback> message (new AsyncFunctionCallback (func, parameter)); | |||
| if (message->post()) | |||
| { | |||
| message->finished.wait(); | |||
| return message->result; | |||
| } | |||
| jassertfalse; // the OS message queue failed to send the message! | |||
| return nullptr; | |||
| } | |||
| //============================================================================== | |||
| void MessageManager::deliverBroadcastMessage (const String& value) | |||
| { | |||
| if (broadcaster != nullptr) | |||
| broadcaster->sendActionMessage (value); | |||
| } | |||
| void MessageManager::registerBroadcastListener (ActionListener* const listener) | |||
| { | |||
| if (broadcaster == nullptr) | |||
| broadcaster = new ActionBroadcaster(); | |||
| broadcaster->addActionListener (listener); | |||
| } | |||
| void MessageManager::deregisterBroadcastListener (ActionListener* const listener) | |||
| { | |||
| if (broadcaster != nullptr) | |||
| broadcaster->removeActionListener (listener); | |||
| } | |||
| //============================================================================== | |||
| bool MessageManager::isThisTheMessageThread() const noexcept | |||
| { | |||
| return Thread::getCurrentThreadId() == messageThreadId; | |||
| } | |||
| void MessageManager::setCurrentThreadAsMessageThread() | |||
| { | |||
| const Thread::ThreadID thisThread = Thread::getCurrentThreadId(); | |||
| if (messageThreadId != thisThread) | |||
| { | |||
| messageThreadId = thisThread; | |||
| // This is needed on windows to make sure the message window is created by this thread | |||
| doPlatformSpecificShutdown(); | |||
| doPlatformSpecificInitialisation(); | |||
| } | |||
| } | |||
| bool MessageManager::currentThreadHasLockedMessageManager() const noexcept | |||
| { | |||
| const Thread::ThreadID thisThread = Thread::getCurrentThreadId(); | |||
| return thisThread == messageThreadId || thisThread == threadWithLock; | |||
| } | |||
| //============================================================================== | |||
| //============================================================================== | |||
| /* The only safe way to lock the message thread while another thread does | |||
| some work is by posting a special message, whose purpose is to tie up the event | |||
| loop until the other thread has finished its business. | |||
| Any other approach can get horribly deadlocked if the OS uses its own hidden locks which | |||
| get locked before making an event callback, because if the same OS lock gets indirectly | |||
| accessed from another thread inside a MM lock, you're screwed. (this is exactly what happens | |||
| in Cocoa). | |||
| */ | |||
| class MessageManagerLock::BlockingMessage : public MessageManager::MessageBase | |||
| { | |||
| public: | |||
| BlockingMessage() noexcept {} | |||
| void messageCallback() override | |||
| { | |||
| lockedEvent.signal(); | |||
| releaseEvent.wait(); | |||
| } | |||
| WaitableEvent lockedEvent, releaseEvent; | |||
| JUCE_DECLARE_NON_COPYABLE (BlockingMessage) | |||
| }; | |||
| //============================================================================== | |||
| MessageManagerLock::MessageManagerLock (Thread* const threadToCheck) | |||
| : blockingMessage(), checker (threadToCheck, nullptr), | |||
| locked (attemptLock (threadToCheck != nullptr ? &checker : nullptr)) | |||
| { | |||
| } | |||
| MessageManagerLock::MessageManagerLock (ThreadPoolJob* const jobToCheckForExitSignal) | |||
| : blockingMessage(), checker (nullptr, jobToCheckForExitSignal), | |||
| locked (attemptLock (jobToCheckForExitSignal != nullptr ? &checker : nullptr)) | |||
| { | |||
| } | |||
| MessageManagerLock::MessageManagerLock (BailOutChecker& bailOutChecker) | |||
| : blockingMessage(), checker (nullptr, nullptr), | |||
| locked (attemptLock (&bailOutChecker)) | |||
| { | |||
| } | |||
| bool MessageManagerLock::attemptLock (BailOutChecker* bailOutChecker) | |||
| { | |||
| auto* mm = MessageManager::instance; | |||
| if (mm == nullptr) | |||
| return false; | |||
| if (mm->currentThreadHasLockedMessageManager()) | |||
| return true; | |||
| if (bailOutChecker == nullptr) | |||
| { | |||
| mm->lockingLock.enter(); | |||
| } | |||
| else | |||
| { | |||
| while (! mm->lockingLock.tryEnter()) | |||
| { | |||
| if (bailOutChecker->shouldAbortAcquiringLock()) | |||
| return false; | |||
| Thread::yield(); | |||
| } | |||
| } | |||
| blockingMessage = new BlockingMessage(); | |||
| if (! blockingMessage->post()) | |||
| { | |||
| blockingMessage = nullptr; | |||
| return false; | |||
| } | |||
| while (! blockingMessage->lockedEvent.wait (20)) | |||
| { | |||
| if (bailOutChecker != nullptr && bailOutChecker->shouldAbortAcquiringLock()) | |||
| { | |||
| blockingMessage->releaseEvent.signal(); | |||
| blockingMessage = nullptr; | |||
| mm->lockingLock.exit(); | |||
| return false; | |||
| } | |||
| } | |||
| jassert (mm->threadWithLock == 0); | |||
| mm->threadWithLock = Thread::getCurrentThreadId(); | |||
| return true; | |||
| } | |||
| MessageManagerLock::~MessageManagerLock() noexcept | |||
| { | |||
| if (blockingMessage != nullptr) | |||
| { | |||
| auto* mm = MessageManager::instance; | |||
| jassert (mm == nullptr || mm->currentThreadHasLockedMessageManager()); | |||
| blockingMessage->releaseEvent.signal(); | |||
| blockingMessage = nullptr; | |||
| if (mm != nullptr) | |||
| { | |||
| mm->threadWithLock = 0; | |||
| mm->lockingLock.exit(); | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| MessageManagerLock::ThreadChecker::ThreadChecker (Thread* const threadToUse, | |||
| ThreadPoolJob* const threadJobToUse) | |||
| : threadToCheck (threadToUse), job (threadJobToUse) | |||
| { | |||
| } | |||
| bool MessageManagerLock::ThreadChecker::shouldAbortAcquiringLock() | |||
| { | |||
| return (threadToCheck != nullptr && threadToCheck->threadShouldExit()) | |||
| || (job != nullptr && job->shouldExit()); | |||
| } | |||
| //============================================================================== | |||
| JUCE_API void JUCE_CALLTYPE initialiseJuce_GUI(); | |||
| JUCE_API void JUCE_CALLTYPE initialiseJuce_GUI() | |||
| { | |||
| JUCE_AUTORELEASEPOOL | |||
| { | |||
| MessageManager::getInstance(); | |||
| } | |||
| } | |||
| JUCE_API void JUCE_CALLTYPE shutdownJuce_GUI(); | |||
| JUCE_API void JUCE_CALLTYPE shutdownJuce_GUI() | |||
| { | |||
| JUCE_AUTORELEASEPOOL | |||
| { | |||
| DeletedAtShutdown::deleteAll(); | |||
| MessageManager::deleteInstance(); | |||
| } | |||
| } | |||
| static int numScopedInitInstances = 0; | |||
| ScopedJuceInitialiser_GUI::ScopedJuceInitialiser_GUI() { if (numScopedInitInstances++ == 0) initialiseJuce_GUI(); } | |||
| ScopedJuceInitialiser_GUI::~ScopedJuceInitialiser_GUI() { if (--numScopedInitInstances == 0) shutdownJuce_GUI(); } | |||
| } // namespace juce | |||
| @@ -0,0 +1,381 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| class MessageManagerLock; | |||
| class ThreadPoolJob; | |||
| class ActionListener; | |||
| class ActionBroadcaster; | |||
| //============================================================================== | |||
| #if JUCE_MODULE_AVAILABLE_juce_opengl | |||
| class OpenGLContext; | |||
| #endif | |||
| //============================================================================== | |||
| /** See MessageManager::callFunctionOnMessageThread() for use of this function type. */ | |||
| typedef void* (MessageCallbackFunction) (void* userData); | |||
| //============================================================================== | |||
| /** | |||
| This class is in charge of the application's event-dispatch loop. | |||
| @see Message, CallbackMessage, MessageManagerLock, JUCEApplication, JUCEApplicationBase | |||
| */ | |||
| class JUCE_API MessageManager | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Returns the global instance of the MessageManager. */ | |||
| static MessageManager* getInstance(); | |||
| /** Returns the global instance of the MessageManager, or nullptr if it doesn't exist. */ | |||
| static MessageManager* getInstanceWithoutCreating() noexcept; | |||
| /** Deletes the global MessageManager instance. | |||
| Does nothing if no instance had been created. | |||
| */ | |||
| static void deleteInstance(); | |||
| //============================================================================== | |||
| /** Runs the event dispatch loop until a stop message is posted. | |||
| This method is only intended to be run by the application's startup routine, | |||
| as it blocks, and will only return after the stopDispatchLoop() method has been used. | |||
| @see stopDispatchLoop | |||
| */ | |||
| void runDispatchLoop(); | |||
| /** Sends a signal that the dispatch loop should terminate. | |||
| After this is called, the runDispatchLoop() or runDispatchLoopUntil() methods | |||
| will be interrupted and will return. | |||
| @see runDispatchLoop | |||
| */ | |||
| void stopDispatchLoop(); | |||
| /** Returns true if the stopDispatchLoop() method has been called. | |||
| */ | |||
| bool hasStopMessageBeenSent() const noexcept { return quitMessagePosted; } | |||
| #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN | |||
| /** Synchronously dispatches messages until a given time has elapsed. | |||
| Returns false if a quit message has been posted by a call to stopDispatchLoop(), | |||
| otherwise returns true. | |||
| */ | |||
| bool runDispatchLoopUntil (int millisecondsToRunFor); | |||
| #endif | |||
| //============================================================================== | |||
| /** Asynchronously invokes a function or C++11 lambda on the message thread. */ | |||
| template <typename FunctionType> | |||
| static void callAsync (FunctionType functionToCall) | |||
| { | |||
| new AsyncCallInvoker<FunctionType> (functionToCall); | |||
| } | |||
| /** Calls a function using the message-thread. | |||
| This can be used by any thread to cause this function to be called-back | |||
| by the message thread. If it's the message-thread that's calling this method, | |||
| then the function will just be called; if another thread is calling, a message | |||
| will be posted to the queue, and this method will block until that message | |||
| is delivered, the function is called, and the result is returned. | |||
| Be careful not to cause any deadlocks with this! It's easy to do - e.g. if the caller | |||
| thread has a critical section locked, which an unrelated message callback then tries to lock | |||
| before the message thread gets round to processing this callback. | |||
| @param callback the function to call - its signature must be @code | |||
| void* myCallbackFunction (void*) @endcode | |||
| @param userData a user-defined pointer that will be passed to the function that gets called | |||
| @returns the value that the callback function returns. | |||
| @see MessageManagerLock | |||
| */ | |||
| void* callFunctionOnMessageThread (MessageCallbackFunction* callback, void* userData); | |||
| /** Returns true if the caller-thread is the message thread. */ | |||
| bool isThisTheMessageThread() const noexcept; | |||
| /** Called to tell the manager that the current thread is the one that's running the dispatch loop. | |||
| (Best to ignore this method unless you really know what you're doing..) | |||
| @see getCurrentMessageThread | |||
| */ | |||
| void setCurrentThreadAsMessageThread(); | |||
| /** Returns the ID of the current message thread, as set by setCurrentThreadAsMessageThread(). | |||
| (Best to ignore this method unless you really know what you're doing..) | |||
| @see setCurrentThreadAsMessageThread | |||
| */ | |||
| Thread::ThreadID getCurrentMessageThread() const noexcept { return messageThreadId; } | |||
| /** Returns true if the caller thread has currently got the message manager locked. | |||
| see the MessageManagerLock class for more info about this. | |||
| This will be true if the caller is the message thread, because that automatically | |||
| gains a lock while a message is being dispatched. | |||
| */ | |||
| bool currentThreadHasLockedMessageManager() const noexcept; | |||
| //============================================================================== | |||
| /** Sends a message to all other JUCE applications that are running. | |||
| @param messageText the string that will be passed to the actionListenerCallback() | |||
| method of the broadcast listeners in the other app. | |||
| @see registerBroadcastListener, ActionListener | |||
| */ | |||
| static void broadcastMessage (const String& messageText); | |||
| /** Registers a listener to get told about broadcast messages. | |||
| The actionListenerCallback() callback's string parameter | |||
| is the message passed into broadcastMessage(). | |||
| @see broadcastMessage | |||
| */ | |||
| void registerBroadcastListener (ActionListener* listener); | |||
| /** Deregisters a broadcast listener. */ | |||
| void deregisterBroadcastListener (ActionListener* listener); | |||
| //============================================================================== | |||
| /** Internal class used as the base class for all message objects. | |||
| You shouldn't need to use this directly - see the CallbackMessage or Message | |||
| classes instead. | |||
| */ | |||
| class JUCE_API MessageBase : public ReferenceCountedObject | |||
| { | |||
| public: | |||
| MessageBase() noexcept {} | |||
| virtual ~MessageBase() {} | |||
| virtual void messageCallback() = 0; | |||
| bool post(); | |||
| typedef ReferenceCountedObjectPtr<MessageBase> Ptr; | |||
| JUCE_DECLARE_NON_COPYABLE (MessageBase) | |||
| }; | |||
| //============================================================================== | |||
| #ifndef DOXYGEN | |||
| // Internal methods - do not use! | |||
| void deliverBroadcastMessage (const String&); | |||
| ~MessageManager() noexcept; | |||
| static bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages); | |||
| #endif | |||
| private: | |||
| //============================================================================== | |||
| MessageManager() noexcept; | |||
| static MessageManager* instance; | |||
| friend class MessageBase; | |||
| class QuitMessage; | |||
| friend class QuitMessage; | |||
| friend class MessageManagerLock; | |||
| ScopedPointer<ActionBroadcaster> broadcaster; | |||
| bool quitMessagePosted = false, quitMessageReceived = false; | |||
| Thread::ThreadID messageThreadId; | |||
| Thread::ThreadID volatile threadWithLock = {}; | |||
| CriticalSection lockingLock; | |||
| static bool postMessageToSystemQueue (MessageBase*); | |||
| static void* exitModalLoopCallback (void*); | |||
| static void doPlatformSpecificInitialisation(); | |||
| static void doPlatformSpecificShutdown(); | |||
| template <typename FunctionType> | |||
| struct AsyncCallInvoker : public MessageBase | |||
| { | |||
| AsyncCallInvoker (FunctionType f) : callback (f) { post(); } | |||
| void messageCallback() override { callback(); } | |||
| FunctionType callback; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AsyncCallInvoker) | |||
| }; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessageManager) | |||
| }; | |||
| //============================================================================== | |||
| /** Used to make sure that the calling thread has exclusive access to the message loop. | |||
| Because it's not thread-safe to call any of the Component or other UI classes | |||
| from threads other than the message thread, one of these objects can be used to | |||
| lock the message loop and allow this to be done. The message thread will be | |||
| suspended for the lifetime of the MessageManagerLock object, so create one on | |||
| the stack like this: @code | |||
| void MyThread::run() | |||
| { | |||
| someData = 1234; | |||
| const MessageManagerLock mmLock; | |||
| // the event loop will now be locked so it's safe to make a few calls.. | |||
| myComponent->setBounds (newBounds); | |||
| myComponent->repaint(); | |||
| // ..the event loop will now be unlocked as the MessageManagerLock goes out of scope | |||
| } | |||
| @endcode | |||
| Obviously be careful not to create one of these and leave it lying around, or | |||
| your app will grind to a halt! | |||
| MessageManagerLocks are re-entrant, so can be safely nested if the current thread | |||
| already has the lock. | |||
| Another caveat is that using this in conjunction with other CriticalSections | |||
| can create lots of interesting ways of producing a deadlock! In particular, if | |||
| your message thread calls stopThread() for a thread that uses these locks, | |||
| you'll get an (occasional) deadlock.. | |||
| @see MessageManager, MessageManager::currentThreadHasLockedMessageManager | |||
| */ | |||
| class JUCE_API MessageManagerLock | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Tries to acquire a lock on the message manager. | |||
| The constructor attempts to gain a lock on the message loop, and the lock will be | |||
| kept for the lifetime of this object. | |||
| Optionally, you can pass a thread object here, and while waiting to obtain the lock, | |||
| this method will keep checking whether the thread has been given the | |||
| Thread::signalThreadShouldExit() signal. If this happens, then it will return | |||
| without gaining the lock. If you pass a thread, you must check whether the lock was | |||
| successful by calling lockWasGained(). If this is false, your thread is being told to | |||
| die, so you should take evasive action. | |||
| If you pass nullptr for the thread object, it will wait indefinitely for the lock - be | |||
| careful when doing this, because it's very easy to deadlock if your message thread | |||
| attempts to call stopThread() on a thread just as that thread attempts to get the | |||
| message lock. | |||
| If the calling thread already has the lock, nothing will be done, so it's safe and | |||
| quick to use these locks recursively. | |||
| E.g. | |||
| @code | |||
| void run() | |||
| { | |||
| ... | |||
| while (! threadShouldExit()) | |||
| { | |||
| MessageManagerLock mml (Thread::getCurrentThread()); | |||
| if (! mml.lockWasGained()) | |||
| return; // another thread is trying to kill us! | |||
| ..do some locked stuff here.. | |||
| } | |||
| ..and now the MM is now unlocked.. | |||
| } | |||
| @endcode | |||
| */ | |||
| MessageManagerLock (Thread* threadToCheckForExitSignal = nullptr); | |||
| //============================================================================== | |||
| /** This has the same behaviour as the other constructor, but takes a ThreadPoolJob | |||
| instead of a thread. | |||
| See the MessageManagerLock (Thread*) constructor for details on how this works. | |||
| */ | |||
| MessageManagerLock (ThreadPoolJob* jobToCheckForExitSignal); | |||
| //============================================================================== | |||
| struct BailOutChecker | |||
| { | |||
| virtual ~BailOutChecker() {} | |||
| /** Return true if acquiring the lock should be aborted. */ | |||
| virtual bool shouldAbortAcquiringLock() = 0; | |||
| }; | |||
| /** This is an abstraction of the other constructors. You can pass this constructor | |||
| a functor which is periodically checked if attempting the lock should be aborted. | |||
| See the MessageManagerLock (Thread*) constructor for details on how this works. | |||
| */ | |||
| MessageManagerLock (BailOutChecker&); | |||
| //============================================================================== | |||
| /** Releases the current thread's lock on the message manager. | |||
| Make sure this object is created and deleted by the same thread, | |||
| otherwise there are no guarantees what will happen! | |||
| */ | |||
| ~MessageManagerLock() noexcept; | |||
| //============================================================================== | |||
| /** Returns true if the lock was successfully acquired. | |||
| (See the constructor that takes a Thread for more info). | |||
| */ | |||
| bool lockWasGained() const noexcept { return locked; } | |||
| private: | |||
| class BlockingMessage; | |||
| friend class ReferenceCountedObjectPtr<BlockingMessage>; | |||
| ReferenceCountedObjectPtr<BlockingMessage> blockingMessage; | |||
| struct ThreadChecker : BailOutChecker | |||
| { | |||
| ThreadChecker (Thread* const, ThreadPoolJob* const); | |||
| // Required to supress VS2013 compiler warnings | |||
| ThreadChecker& operator= (const ThreadChecker&) = delete; | |||
| bool shouldAbortAcquiringLock() override; | |||
| Thread* const threadToCheck; | |||
| ThreadPoolJob* const job; | |||
| }; | |||
| //============================================================================== | |||
| ThreadChecker checker; | |||
| bool locked; | |||
| //============================================================================== | |||
| bool attemptLock (BailOutChecker*); | |||
| JUCE_DECLARE_NON_COPYABLE (MessageManagerLock) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,57 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| #if JUCE_MAC || JUCE_WINDOWS || defined (DOXYGEN) | |||
| //============================================================================== | |||
| /** | |||
| An instance of this class will provide callbacks when drives are | |||
| mounted or unmounted on the system. | |||
| Just inherit from this class and implement the pure virtual method | |||
| to get the callbacks, there's no need to do anything else. | |||
| @see File::findFileSystemRoots() | |||
| */ | |||
| class JUCE_API MountedVolumeListChangeDetector | |||
| { | |||
| public: | |||
| MountedVolumeListChangeDetector(); | |||
| virtual ~MountedVolumeListChangeDetector(); | |||
| /** This method is called when a volume is mounted or unmounted. */ | |||
| virtual void mountedVolumeListChanged() = 0; | |||
| private: | |||
| JUCE_PUBLIC_IN_DLL_BUILD (struct Pimpl) | |||
| friend struct ContainerDeletePolicy<Pimpl>; | |||
| ScopedPointer<Pimpl> pimpl; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MountedVolumeListChangeDetector) | |||
| }; | |||
| #endif | |||
| } // namespace juce | |||
| @@ -0,0 +1,39 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| These enums are used in various classes to indicate whether a notification | |||
| event should be sent out. | |||
| */ | |||
| enum NotificationType | |||
| { | |||
| dontSendNotification = 0, /**< No notification message should be sent. */ | |||
| sendNotification = 1, /**< Requests a notification message, either synchronous or not. */ | |||
| sendNotificationSync, /**< Requests a synchronous notification. */ | |||
| sendNotificationAsync, /**< Requests an asynchronous notification. */ | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,147 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||
| METHOD (constructor, "<init>", "()V") \ | |||
| METHOD (post, "post", "(Ljava/lang/Runnable;)Z") \ | |||
| DECLARE_JNI_CLASS (JNIHandler, "android/os/Handler"); | |||
| #undef JNI_CLASS_MEMBERS | |||
| //============================================================================== | |||
| namespace Android | |||
| { | |||
| class Runnable : public juce::AndroidInterfaceImplementer | |||
| { | |||
| public: | |||
| virtual void run() = 0; | |||
| private: | |||
| jobject invoke (jobject proxy, jobject method, jobjectArray args) override | |||
| { | |||
| auto* env = getEnv(); | |||
| auto methodName = juce::juceString ((jstring) env->CallObjectMethod (method, Method.getName)); | |||
| if (methodName == "run") | |||
| { | |||
| run(); | |||
| return nullptr; | |||
| } | |||
| // invoke base class | |||
| return AndroidInterfaceImplementer::invoke (proxy, method, args); | |||
| } | |||
| }; | |||
| struct Handler | |||
| { | |||
| juce_DeclareSingleton (Handler, false) | |||
| Handler() : nativeHandler (getEnv()->NewObject (JNIHandler, JNIHandler.constructor)) {} | |||
| bool post (Runnable* runnable) | |||
| { | |||
| return (getEnv()->CallBooleanMethod (nativeHandler.get(), JNIHandler.post, | |||
| CreateJavaInterface (runnable, "java/lang/Runnable").get()) != 0); | |||
| } | |||
| GlobalRef nativeHandler; | |||
| }; | |||
| juce_ImplementSingleton (Handler); | |||
| } | |||
| //============================================================================== | |||
| void MessageManager::doPlatformSpecificInitialisation() { Android::Handler::getInstance(); } | |||
| void MessageManager::doPlatformSpecificShutdown() {} | |||
| //============================================================================== | |||
| bool MessageManager::dispatchNextMessageOnSystemQueue (const bool) | |||
| { | |||
| Logger::outputDebugString ("*** Modal loops are not possible in Android!! Exiting..."); | |||
| exit (1); | |||
| return true; | |||
| } | |||
| //============================================================================== | |||
| struct AndroidMessageCallback : public Android::Runnable | |||
| { | |||
| AndroidMessageCallback (const MessageManager::MessageBase::Ptr& messageToDeliver) | |||
| : message (messageToDeliver) | |||
| {} | |||
| AndroidMessageCallback (MessageManager::MessageBase::Ptr && messageToDeliver) | |||
| : message (static_cast<MessageManager::MessageBase::Ptr&&> (messageToDeliver)) | |||
| {} | |||
| void run() override | |||
| { | |||
| JUCE_TRY | |||
| { | |||
| message->messageCallback(); | |||
| // delete the message already here as Java will only run the | |||
| // destructor of this runnable the next time the garbage | |||
| // collector kicks in. | |||
| message = nullptr; | |||
| } | |||
| JUCE_CATCH_EXCEPTION | |||
| } | |||
| MessageManager::MessageBase::Ptr message; | |||
| }; | |||
| bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) | |||
| { | |||
| return Android::Handler::getInstance()->post (new AndroidMessageCallback (message)); | |||
| } | |||
| //============================================================================== | |||
| void MessageManager::broadcastMessage (const String&) | |||
| { | |||
| } | |||
| void MessageManager::runDispatchLoop() | |||
| { | |||
| } | |||
| void MessageManager::stopDispatchLoop() | |||
| { | |||
| struct QuitCallback : public CallbackMessage | |||
| { | |||
| QuitCallback() {} | |||
| void messageCallback() override | |||
| { | |||
| android.activity.callVoidMethod (JuceAppActivity.finish); | |||
| } | |||
| }; | |||
| (new QuitCallback())->post(); | |||
| quitMessagePosted = true; | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,103 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| void MessageManager::runDispatchLoop() | |||
| { | |||
| jassert (isThisTheMessageThread()); // must only be called by the message thread | |||
| while (! quitMessagePosted) | |||
| { | |||
| JUCE_AUTORELEASEPOOL | |||
| { | |||
| [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode | |||
| beforeDate: [NSDate dateWithTimeIntervalSinceNow: 0.001]]; | |||
| } | |||
| } | |||
| } | |||
| void MessageManager::stopDispatchLoop() | |||
| { | |||
| if (! SystemStats::isRunningInAppExtensionSandbox()) | |||
| [[[UIApplication sharedApplication] delegate] applicationWillTerminate: [UIApplication sharedApplication]]; | |||
| exit (0); // iOS apps get no mercy.. | |||
| } | |||
| #if JUCE_MODAL_LOOPS_PERMITTED | |||
| bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) | |||
| { | |||
| JUCE_AUTORELEASEPOOL | |||
| { | |||
| jassert (isThisTheMessageThread()); // must only be called by the message thread | |||
| uint32 startTime = Time::getMillisecondCounter(); | |||
| NSDate* endDate = [NSDate dateWithTimeIntervalSinceNow: millisecondsToRunFor * 0.001]; | |||
| while (! quitMessagePosted) | |||
| { | |||
| JUCE_AUTORELEASEPOOL | |||
| { | |||
| [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode | |||
| beforeDate: endDate]; | |||
| if (millisecondsToRunFor >= 0 | |||
| && Time::getMillisecondCounter() >= startTime + (uint32) millisecondsToRunFor) | |||
| break; | |||
| } | |||
| } | |||
| return ! quitMessagePosted; | |||
| } | |||
| } | |||
| #endif | |||
| //============================================================================== | |||
| static ScopedPointer<MessageQueue> messageQueue; | |||
| void MessageManager::doPlatformSpecificInitialisation() | |||
| { | |||
| if (messageQueue == nullptr) | |||
| messageQueue = new MessageQueue(); | |||
| } | |||
| void MessageManager::doPlatformSpecificShutdown() | |||
| { | |||
| messageQueue = nullptr; | |||
| } | |||
| bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) | |||
| { | |||
| if (messageQueue != nullptr) | |||
| messageQueue->post (message); | |||
| return true; | |||
| } | |||
| void MessageManager::broadcastMessage (const String&) | |||
| { | |||
| // N/A on current iOS | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,55 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| namespace LinuxEventLoop | |||
| { | |||
| struct CallbackFunctionBase | |||
| { | |||
| virtual ~CallbackFunctionBase() {} | |||
| virtual bool operator()(int fd) = 0; | |||
| bool active = true; | |||
| }; | |||
| template <typename FdCallbackFunction> | |||
| struct CallbackFunction : public CallbackFunctionBase | |||
| { | |||
| FdCallbackFunction callback; | |||
| CallbackFunction (FdCallbackFunction c) : callback (c) {} | |||
| bool operator() (int fd) override { return callback (fd); } | |||
| }; | |||
| template <typename FdCallbackFunction> | |||
| void setWindowSystemFd (int fd, FdCallbackFunction readCallback) | |||
| { | |||
| setWindowSystemFdInternal (fd, new CallbackFunction<FdCallbackFunction> (readCallback)); | |||
| } | |||
| void removeWindowSystemFd() noexcept; | |||
| void setWindowSystemFdInternal (int fd, CallbackFunctionBase* readCallback) noexcept; | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,265 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| #include <poll.h> | |||
| enum FdType | |||
| { | |||
| INTERNAL_QUEUE_FD, | |||
| WINDOW_SYSTEM_FD, | |||
| FD_COUNT, | |||
| }; | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| class InternalMessageQueue | |||
| { | |||
| public: | |||
| InternalMessageQueue() | |||
| { | |||
| auto ret = ::socketpair (AF_LOCAL, SOCK_STREAM, 0, fd); | |||
| ignoreUnused (ret); jassert (ret == 0); | |||
| auto internalQueueCb = [this] (int _fd) | |||
| { | |||
| if (const MessageManager::MessageBase::Ptr msg = this->popNextMessage (_fd)) | |||
| { | |||
| JUCE_TRY | |||
| { | |||
| msg->messageCallback(); | |||
| return true; | |||
| } | |||
| JUCE_CATCH_EXCEPTION | |||
| } | |||
| return false; | |||
| }; | |||
| pfds[INTERNAL_QUEUE_FD].fd = getReadHandle(); | |||
| pfds[INTERNAL_QUEUE_FD].events = POLLIN; | |||
| readCallback[INTERNAL_QUEUE_FD] = new LinuxEventLoop::CallbackFunction<decltype(internalQueueCb)> (internalQueueCb); | |||
| } | |||
| ~InternalMessageQueue() | |||
| { | |||
| close (getReadHandle()); | |||
| close (getWriteHandle()); | |||
| clearSingletonInstance(); | |||
| } | |||
| //============================================================================== | |||
| void postMessage (MessageManager::MessageBase* const msg) noexcept | |||
| { | |||
| ScopedLock sl (lock); | |||
| queue.add (msg); | |||
| const int maxBytesInSocketQueue = 128; | |||
| if (bytesInSocket < maxBytesInSocketQueue) | |||
| { | |||
| bytesInSocket++; | |||
| ScopedUnlock ul (lock); | |||
| const unsigned char x = 0xff; | |||
| ssize_t bytesWritten = write (getWriteHandle(), &x, 1); | |||
| ignoreUnused (bytesWritten); | |||
| } | |||
| } | |||
| void setWindowSystemFd (int _fd, LinuxEventLoop::CallbackFunctionBase* _readCallback) | |||
| { | |||
| jassert (fdCount == 1); | |||
| ScopedLock sl (lock); | |||
| fdCount = 2; | |||
| pfds[WINDOW_SYSTEM_FD].fd = _fd; | |||
| pfds[WINDOW_SYSTEM_FD].events = POLLIN; | |||
| readCallback[WINDOW_SYSTEM_FD] = _readCallback; | |||
| readCallback[WINDOW_SYSTEM_FD]->active = true; | |||
| } | |||
| void removeWindowSystemFd() | |||
| { | |||
| jassert (fdCount == FD_COUNT); | |||
| ScopedLock sl (lock); | |||
| fdCount = 1; | |||
| readCallback[WINDOW_SYSTEM_FD]->active = false; | |||
| } | |||
| bool dispatchNextEvent() noexcept | |||
| { | |||
| for (int counter = 0; counter < fdCount; counter++) | |||
| { | |||
| const int i = loopCount++; | |||
| loopCount %= fdCount; | |||
| if (readCallback[i] != nullptr && readCallback[i]->active) | |||
| if ((*readCallback[i]) (pfds[i].fd)) | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| bool sleepUntilEvent (const int timeoutMs) | |||
| { | |||
| const int pnum = poll (pfds, static_cast<nfds_t> (fdCount), timeoutMs); | |||
| return (pnum > 0); | |||
| } | |||
| //============================================================================== | |||
| juce_DeclareSingleton_SingleThreaded_Minimal (InternalMessageQueue) | |||
| private: | |||
| CriticalSection lock; | |||
| ReferenceCountedArray <MessageManager::MessageBase> queue; | |||
| int fd[2]; | |||
| pollfd pfds[FD_COUNT]; | |||
| ScopedPointer<LinuxEventLoop::CallbackFunctionBase> readCallback[FD_COUNT]; | |||
| int fdCount = 1; | |||
| int loopCount = 0; | |||
| int bytesInSocket = 0; | |||
| int getWriteHandle() const noexcept { return fd[0]; } | |||
| int getReadHandle() const noexcept { return fd[1]; } | |||
| MessageManager::MessageBase::Ptr popNextMessage (int _fd) noexcept | |||
| { | |||
| const ScopedLock sl (lock); | |||
| if (bytesInSocket > 0) | |||
| { | |||
| --bytesInSocket; | |||
| const ScopedUnlock ul (lock); | |||
| unsigned char x; | |||
| ssize_t numBytes = read (_fd, &x, 1); | |||
| ignoreUnused (numBytes); | |||
| } | |||
| return queue.removeAndReturn (0); | |||
| } | |||
| }; | |||
| juce_ImplementSingleton_SingleThreaded (InternalMessageQueue) | |||
| //============================================================================== | |||
| namespace LinuxErrorHandling | |||
| { | |||
| static bool keyboardBreakOccurred = false; | |||
| //============================================================================== | |||
| void keyboardBreakSignalHandler (int sig) | |||
| { | |||
| if (sig == SIGINT) | |||
| keyboardBreakOccurred = true; | |||
| } | |||
| void installKeyboardBreakHandler() | |||
| { | |||
| struct sigaction saction; | |||
| sigset_t maskSet; | |||
| sigemptyset (&maskSet); | |||
| saction.sa_handler = keyboardBreakSignalHandler; | |||
| saction.sa_mask = maskSet; | |||
| saction.sa_flags = 0; | |||
| sigaction (SIGINT, &saction, 0); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void MessageManager::doPlatformSpecificInitialisation() | |||
| { | |||
| if (JUCEApplicationBase::isStandaloneApp()) | |||
| LinuxErrorHandling::installKeyboardBreakHandler(); | |||
| // Create the internal message queue | |||
| auto* queue = InternalMessageQueue::getInstance(); | |||
| ignoreUnused (queue); | |||
| } | |||
| void MessageManager::doPlatformSpecificShutdown() | |||
| { | |||
| InternalMessageQueue::deleteInstance(); | |||
| } | |||
| bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) | |||
| { | |||
| if (auto* queue = InternalMessageQueue::getInstanceWithoutCreating()) | |||
| { | |||
| queue->postMessage (message); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| void MessageManager::broadcastMessage (const String&) | |||
| { | |||
| // TODO | |||
| } | |||
| // this function expects that it will NEVER be called simultaneously for two concurrent threads | |||
| bool MessageManager::dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages) | |||
| { | |||
| for (;;) | |||
| { | |||
| if (LinuxErrorHandling::keyboardBreakOccurred) | |||
| JUCEApplicationBase::getInstance()->quit(); | |||
| if (auto* queue = InternalMessageQueue::getInstanceWithoutCreating()) | |||
| { | |||
| if (queue->dispatchNextEvent()) | |||
| break; | |||
| if (returnIfNoPendingMessages) | |||
| return false; | |||
| // wait for 2000ms for next events if necessary | |||
| queue->sleepUntilEvent (2000); | |||
| } | |||
| } | |||
| return true; | |||
| } | |||
| //============================================================================== | |||
| void LinuxEventLoop::setWindowSystemFdInternal (int fd, LinuxEventLoop::CallbackFunctionBase* readCallback) noexcept | |||
| { | |||
| if (auto* queue = InternalMessageQueue::getInstanceWithoutCreating()) | |||
| queue->setWindowSystemFd (fd, readCallback); | |||
| } | |||
| void LinuxEventLoop::removeWindowSystemFd() noexcept | |||
| { | |||
| if (auto* queue = InternalMessageQueue::getInstanceWithoutCreating()) | |||
| queue->removeWindowSystemFd(); | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,430 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| typedef void (*AppFocusChangeCallback)(); | |||
| AppFocusChangeCallback appFocusChangeCallback = nullptr; | |||
| typedef bool (*CheckEventBlockedByModalComps) (NSEvent*); | |||
| CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr; | |||
| typedef void (*MenuTrackingChangedCallback)(bool); | |||
| MenuTrackingChangedCallback menuTrackingChangedCallback = nullptr; | |||
| //============================================================================== | |||
| struct AppDelegate | |||
| { | |||
| public: | |||
| AppDelegate() | |||
| { | |||
| static AppDelegateClass cls; | |||
| delegate = [cls.createInstance() init]; | |||
| NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; | |||
| [center addObserver: delegate selector: @selector (mainMenuTrackingBegan:) | |||
| name: NSMenuDidBeginTrackingNotification object: nil]; | |||
| [center addObserver: delegate selector: @selector (mainMenuTrackingEnded:) | |||
| name: NSMenuDidEndTrackingNotification object: nil]; | |||
| if (JUCEApplicationBase::isStandaloneApp()) | |||
| { | |||
| [NSApp setDelegate: delegate]; | |||
| [[NSDistributedNotificationCenter defaultCenter] addObserver: delegate | |||
| selector: @selector (broadcastMessageCallback:) | |||
| name: getBroadcastEventName() | |||
| object: nil | |||
| suspensionBehavior: NSNotificationSuspensionBehaviorDeliverImmediately]; | |||
| } | |||
| else | |||
| { | |||
| [center addObserver: delegate selector: @selector (applicationDidResignActive:) | |||
| name: NSApplicationDidResignActiveNotification object: NSApp]; | |||
| [center addObserver: delegate selector: @selector (applicationDidBecomeActive:) | |||
| name: NSApplicationDidBecomeActiveNotification object: NSApp]; | |||
| [center addObserver: delegate selector: @selector (applicationWillUnhide:) | |||
| name: NSApplicationWillUnhideNotification object: NSApp]; | |||
| } | |||
| } | |||
| ~AppDelegate() | |||
| { | |||
| [[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: delegate]; | |||
| [[NSNotificationCenter defaultCenter] removeObserver: delegate]; | |||
| if (JUCEApplicationBase::isStandaloneApp()) | |||
| { | |||
| [NSApp setDelegate: nil]; | |||
| [[NSDistributedNotificationCenter defaultCenter] removeObserver: delegate | |||
| name: getBroadcastEventName() | |||
| object: nil]; | |||
| } | |||
| [delegate release]; | |||
| } | |||
| static NSString* getBroadcastEventName() | |||
| { | |||
| return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64())); | |||
| } | |||
| MessageQueue messageQueue; | |||
| id delegate; | |||
| private: | |||
| //============================================================================== | |||
| struct AppDelegateClass : public ObjCClass<NSObject> | |||
| { | |||
| AppDelegateClass() : ObjCClass<NSObject> ("JUCEAppDelegate_") | |||
| { | |||
| addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching, "v@:@@"); | |||
| addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent, "v@:@@"); | |||
| addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); | |||
| addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); | |||
| addMethod (@selector (application:openFile:), application_openFile, "c@:@@"); | |||
| addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@"); | |||
| addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@"); | |||
| addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@"); | |||
| addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@"); | |||
| addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@"); | |||
| addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan, "v@:@"); | |||
| addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@"); | |||
| addMethod (@selector (dummyMethod), dummyMethod, "v@:"); | |||
| registerClass(); | |||
| } | |||
| private: | |||
| static void applicationWillFinishLaunching (id self, SEL, NSApplication*, NSNotification*) | |||
| { | |||
| [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self | |||
| andSelector: @selector (getUrl:withReplyEvent:) | |||
| forEventClass: kInternetEventClass | |||
| andEventID: kAEGetURL]; | |||
| } | |||
| static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*) | |||
| { | |||
| if (auto* app = JUCEApplicationBase::getInstance()) | |||
| { | |||
| app->systemRequestedQuit(); | |||
| if (! MessageManager::getInstance()->hasStopMessageBeenSent()) | |||
| return NSTerminateCancel; | |||
| } | |||
| return NSTerminateNow; | |||
| } | |||
| static void applicationWillTerminate (id /*self*/, SEL, NSNotification*) | |||
| { | |||
| JUCEApplicationBase::appWillTerminateByForce(); | |||
| } | |||
| static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename) | |||
| { | |||
| if (auto* app = JUCEApplicationBase::getInstance()) | |||
| { | |||
| app->anotherInstanceStarted (quotedIfContainsSpaces (filename)); | |||
| return YES; | |||
| } | |||
| return NO; | |||
| } | |||
| static void application_openFiles (id /*self*/, SEL, NSApplication*, NSArray* filenames) | |||
| { | |||
| if (auto* app = JUCEApplicationBase::getInstance()) | |||
| { | |||
| StringArray files; | |||
| for (NSString* f in filenames) | |||
| files.add (quotedIfContainsSpaces (f)); | |||
| if (files.size() > 0) | |||
| app->anotherInstanceStarted (files.joinIntoString (" ")); | |||
| } | |||
| } | |||
| static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } | |||
| static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } | |||
| static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); } | |||
| static void broadcastMessageCallback (id /*self*/, SEL, NSNotification* n) | |||
| { | |||
| NSDictionary* dict = (NSDictionary*) [n userInfo]; | |||
| const String messageString (nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")])); | |||
| MessageManager::getInstance()->deliverBroadcastMessage (messageString); | |||
| } | |||
| static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*) | |||
| { | |||
| if (menuTrackingChangedCallback != nullptr) | |||
| (*menuTrackingChangedCallback) (true); | |||
| } | |||
| static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*) | |||
| { | |||
| if (menuTrackingChangedCallback != nullptr) | |||
| (*menuTrackingChangedCallback) (false); | |||
| } | |||
| static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread) | |||
| static void focusChanged() | |||
| { | |||
| if (appFocusChangeCallback != nullptr) | |||
| (*appFocusChangeCallback)(); | |||
| } | |||
| static void getUrl_withReplyEvent (id /*self*/, SEL, NSAppleEventDescriptor* event, NSAppleEventDescriptor*) | |||
| { | |||
| if (auto* app = JUCEApplicationBase::getInstance()) | |||
| app->anotherInstanceStarted (quotedIfContainsSpaces ([[event paramDescriptorForKeyword: keyDirectObject] stringValue])); | |||
| } | |||
| static String quotedIfContainsSpaces (NSString* file) | |||
| { | |||
| String s (nsStringToJuce (file)); | |||
| if (s.containsChar (' ')) | |||
| s = s.quoted ('"'); | |||
| return s; | |||
| } | |||
| }; | |||
| }; | |||
| //============================================================================== | |||
| void MessageManager::runDispatchLoop() | |||
| { | |||
| if (! quitMessagePosted) // check that the quit message wasn't already posted.. | |||
| { | |||
| JUCE_AUTORELEASEPOOL | |||
| { | |||
| // must only be called by the message thread! | |||
| jassert (isThisTheMessageThread()); | |||
| #if JUCE_PROJUCER_LIVE_BUILD | |||
| runDispatchLoopUntil (std::numeric_limits<int>::max()); | |||
| #else | |||
| #if JUCE_CATCH_UNHANDLED_EXCEPTIONS | |||
| @try | |||
| { | |||
| [NSApp run]; | |||
| } | |||
| @catch (NSException* e) | |||
| { | |||
| // An AppKit exception will kill the app, but at least this provides a chance to log it., | |||
| std::runtime_error ex (std::string ("NSException: ") + [[e name] UTF8String] + ", Reason:" + [[e reason] UTF8String]); | |||
| JUCEApplicationBase::sendUnhandledException (&ex, __FILE__, __LINE__); | |||
| } | |||
| @finally | |||
| { | |||
| } | |||
| #else | |||
| [NSApp run]; | |||
| #endif | |||
| #endif | |||
| } | |||
| } | |||
| } | |||
| static void shutdownNSApp() | |||
| { | |||
| [NSApp stop: nil]; | |||
| [NSEvent startPeriodicEventsAfterDelay: 0 withPeriod: 0.1]; | |||
| } | |||
| void MessageManager::stopDispatchLoop() | |||
| { | |||
| #if JUCE_PROJUCER_LIVE_BUILD | |||
| quitMessagePosted = true; | |||
| #else | |||
| if (isThisTheMessageThread()) | |||
| { | |||
| quitMessagePosted = true; | |||
| shutdownNSApp(); | |||
| } | |||
| else | |||
| { | |||
| struct QuitCallback : public CallbackMessage | |||
| { | |||
| QuitCallback() {} | |||
| void messageCallback() override { MessageManager::getInstance()->stopDispatchLoop(); } | |||
| }; | |||
| (new QuitCallback())->post(); | |||
| } | |||
| #endif | |||
| } | |||
| #if JUCE_MODAL_LOOPS_PERMITTED | |||
| bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) | |||
| { | |||
| jassert (millisecondsToRunFor >= 0); | |||
| jassert (isThisTheMessageThread()); // must only be called by the message thread | |||
| uint32 endTime = Time::getMillisecondCounter() + (uint32) millisecondsToRunFor; | |||
| while (! quitMessagePosted) | |||
| { | |||
| JUCE_AUTORELEASEPOOL | |||
| { | |||
| CFRunLoopRunInMode (kCFRunLoopDefaultMode, 0.001, true); | |||
| NSEvent* e = [NSApp nextEventMatchingMask: NSEventMaskAny | |||
| untilDate: [NSDate dateWithTimeIntervalSinceNow: 0.001] | |||
| inMode: NSDefaultRunLoopMode | |||
| dequeue: YES]; | |||
| if (e != nil && (isEventBlockedByModalComps == nullptr || ! (*isEventBlockedByModalComps) (e))) | |||
| [NSApp sendEvent: e]; | |||
| if (Time::getMillisecondCounter() >= endTime) | |||
| break; | |||
| } | |||
| } | |||
| return ! quitMessagePosted; | |||
| } | |||
| #endif | |||
| //============================================================================== | |||
| void initialiseNSApplication(); | |||
| void initialiseNSApplication() | |||
| { | |||
| JUCE_AUTORELEASEPOOL | |||
| { | |||
| [NSApplication sharedApplication]; | |||
| } | |||
| } | |||
| static AppDelegate* appDelegate = nullptr; | |||
| void MessageManager::doPlatformSpecificInitialisation() | |||
| { | |||
| if (appDelegate == nil) | |||
| appDelegate = new AppDelegate(); | |||
| } | |||
| void MessageManager::doPlatformSpecificShutdown() | |||
| { | |||
| delete appDelegate; | |||
| appDelegate = nullptr; | |||
| } | |||
| bool MessageManager::postMessageToSystemQueue (MessageBase* message) | |||
| { | |||
| jassert (appDelegate != nil); | |||
| appDelegate->messageQueue.post (message); | |||
| return true; | |||
| } | |||
| void MessageManager::broadcastMessage (const String& message) | |||
| { | |||
| NSDictionary* info = [NSDictionary dictionaryWithObject: juceStringToNS (message) | |||
| forKey: nsStringLiteral ("message")]; | |||
| [[NSDistributedNotificationCenter defaultCenter] postNotificationName: AppDelegate::getBroadcastEventName() | |||
| object: nil | |||
| userInfo: info]; | |||
| } | |||
| // Special function used by some plugin classes to re-post carbon events | |||
| void __attribute__ ((visibility("default"))) repostCurrentNSEvent(); | |||
| void __attribute__ ((visibility("default"))) repostCurrentNSEvent() | |||
| { | |||
| struct EventReposter : public CallbackMessage | |||
| { | |||
| EventReposter() : e ([[NSApp currentEvent] retain]) {} | |||
| ~EventReposter() { [e release]; } | |||
| void messageCallback() override | |||
| { | |||
| [NSApp postEvent: e atStart: YES]; | |||
| } | |||
| NSEvent* e; | |||
| }; | |||
| (new EventReposter())->post(); | |||
| } | |||
| //============================================================================== | |||
| #if JUCE_MAC | |||
| struct MountedVolumeListChangeDetector::Pimpl | |||
| { | |||
| Pimpl (MountedVolumeListChangeDetector& d) : owner (d) | |||
| { | |||
| static ObserverClass cls; | |||
| delegate = [cls.createInstance() init]; | |||
| ObserverClass::setOwner (delegate, this); | |||
| NSNotificationCenter* nc = [[NSWorkspace sharedWorkspace] notificationCenter]; | |||
| [nc addObserver: delegate selector: @selector (changed:) name: NSWorkspaceDidMountNotification object: nil]; | |||
| [nc addObserver: delegate selector: @selector (changed:) name: NSWorkspaceDidUnmountNotification object: nil]; | |||
| } | |||
| ~Pimpl() | |||
| { | |||
| [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver: delegate]; | |||
| [delegate release]; | |||
| } | |||
| private: | |||
| MountedVolumeListChangeDetector& owner; | |||
| id delegate; | |||
| struct ObserverClass : public ObjCClass<NSObject> | |||
| { | |||
| ObserverClass() : ObjCClass<NSObject> ("JUCEDriveObserver_") | |||
| { | |||
| addIvar<Pimpl*> ("owner"); | |||
| addMethod (@selector (changed:), changed, "v@:@"); | |||
| addProtocol (@protocol (NSTextInput)); | |||
| registerClass(); | |||
| } | |||
| static Pimpl* getOwner (id self) { return getIvar<Pimpl*> (self, "owner"); } | |||
| static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); } | |||
| static void changed (id self, SEL, NSNotification*) | |||
| { | |||
| getOwner (self)->owner.mountedVolumeListChanged(); | |||
| } | |||
| }; | |||
| }; | |||
| MountedVolumeListChangeDetector::MountedVolumeListChangeDetector() { pimpl = new Pimpl (*this); } | |||
| MountedVolumeListChangeDetector::~MountedVolumeListChangeDetector() {} | |||
| #endif | |||
| } // namespace juce | |||
| @@ -0,0 +1,105 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /* An internal message pump class used in OSX and iOS. */ | |||
| class MessageQueue | |||
| { | |||
| public: | |||
| MessageQueue() | |||
| { | |||
| #if JUCE_IOS | |||
| runLoop = CFRunLoopGetCurrent(); | |||
| #else | |||
| runLoop = CFRunLoopGetMain(); | |||
| #endif | |||
| CFRunLoopSourceContext sourceContext; | |||
| zerostruct (sourceContext); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) | |||
| sourceContext.info = this; | |||
| sourceContext.perform = runLoopSourceCallback; | |||
| runLoopSource = CFRunLoopSourceCreate (kCFAllocatorDefault, 1, &sourceContext); | |||
| CFRunLoopAddSource (runLoop, runLoopSource, kCFRunLoopCommonModes); | |||
| } | |||
| ~MessageQueue() noexcept | |||
| { | |||
| CFRunLoopRemoveSource (runLoop, runLoopSource, kCFRunLoopCommonModes); | |||
| CFRunLoopSourceInvalidate (runLoopSource); | |||
| CFRelease (runLoopSource); | |||
| } | |||
| void post (MessageManager::MessageBase* const message) | |||
| { | |||
| messages.add (message); | |||
| wakeUp(); | |||
| } | |||
| private: | |||
| ReferenceCountedArray<MessageManager::MessageBase, CriticalSection> messages; | |||
| CFRunLoopRef runLoop; | |||
| CFRunLoopSourceRef runLoopSource; | |||
| void wakeUp() noexcept | |||
| { | |||
| CFRunLoopSourceSignal (runLoopSource); | |||
| CFRunLoopWakeUp (runLoop); | |||
| } | |||
| bool deliverNextMessage() | |||
| { | |||
| const MessageManager::MessageBase::Ptr nextMessage (messages.removeAndReturn (0)); | |||
| if (nextMessage == nullptr) | |||
| return false; | |||
| JUCE_AUTORELEASEPOOL | |||
| { | |||
| JUCE_TRY | |||
| { | |||
| nextMessage->messageCallback(); | |||
| } | |||
| JUCE_CATCH_EXCEPTION | |||
| } | |||
| return true; | |||
| } | |||
| void runLoopCallback() noexcept | |||
| { | |||
| for (int i = 4; --i >= 0;) | |||
| if (! deliverNextMessage()) | |||
| return; | |||
| wakeUp(); | |||
| } | |||
| static void runLoopSourceCallback (void* info) noexcept | |||
| { | |||
| static_cast<MessageQueue*> (info)->runLoopCallback(); | |||
| } | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,135 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| class HiddenMessageWindow | |||
| { | |||
| public: | |||
| HiddenMessageWindow (const TCHAR* const messageWindowName, WNDPROC wndProc) | |||
| { | |||
| String className ("JUCE_"); | |||
| className << String::toHexString (Time::getHighResolutionTicks()); | |||
| HMODULE moduleHandle = (HMODULE) Process::getCurrentModuleInstanceHandle(); | |||
| WNDCLASSEX wc = { 0 }; | |||
| wc.cbSize = sizeof (wc); | |||
| wc.lpfnWndProc = wndProc; | |||
| wc.cbWndExtra = 4; | |||
| wc.hInstance = moduleHandle; | |||
| wc.lpszClassName = className.toWideCharPointer(); | |||
| atom = RegisterClassEx (&wc); | |||
| jassert (atom != 0); | |||
| hwnd = CreateWindow (getClassNameFromAtom(), messageWindowName, | |||
| 0, 0, 0, 0, 0, 0, 0, moduleHandle, 0); | |||
| jassert (hwnd != 0); | |||
| } | |||
| ~HiddenMessageWindow() | |||
| { | |||
| DestroyWindow (hwnd); | |||
| UnregisterClass (getClassNameFromAtom(), 0); | |||
| } | |||
| inline HWND getHWND() const noexcept { return hwnd; } | |||
| private: | |||
| ATOM atom; | |||
| HWND hwnd; | |||
| LPCTSTR getClassNameFromAtom() noexcept { return (LPCTSTR) (pointer_sized_uint) atom; } | |||
| }; | |||
| //============================================================================== | |||
| class JuceWindowIdentifier | |||
| { | |||
| public: | |||
| static bool isJUCEWindow (HWND hwnd) noexcept | |||
| { | |||
| return GetWindowLongPtr (hwnd, GWLP_USERDATA) == getImprobableWindowNumber(); | |||
| } | |||
| static void setAsJUCEWindow (HWND hwnd, bool isJuceWindow) noexcept | |||
| { | |||
| SetWindowLongPtr (hwnd, GWLP_USERDATA, isJuceWindow ? getImprobableWindowNumber() : 0); | |||
| } | |||
| private: | |||
| static LONG_PTR getImprobableWindowNumber() noexcept | |||
| { | |||
| static auto number = (LONG_PTR) Random().nextInt64(); | |||
| return number; | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| class DeviceChangeDetector : private Timer | |||
| { | |||
| public: | |||
| DeviceChangeDetector (const wchar_t* const name) | |||
| : messageWindow (name, (WNDPROC) deviceChangeEventCallback) | |||
| { | |||
| SetWindowLongPtr (messageWindow.getHWND(), GWLP_USERDATA, (LONG_PTR) this); | |||
| } | |||
| virtual ~DeviceChangeDetector() {} | |||
| virtual void systemDeviceChanged() = 0; | |||
| void triggerAsyncDeviceChangeCallback() | |||
| { | |||
| // We'll pause before sending a message, because on device removal, the OS hasn't always updated | |||
| // its device lists correctly at this point. This also helps avoid repeated callbacks. | |||
| startTimer (500); | |||
| } | |||
| private: | |||
| HiddenMessageWindow messageWindow; | |||
| static LRESULT CALLBACK deviceChangeEventCallback (HWND h, const UINT message, | |||
| const WPARAM wParam, const LPARAM lParam) | |||
| { | |||
| if (message == WM_DEVICECHANGE | |||
| && (wParam == 0x8000 /*DBT_DEVICEARRIVAL*/ | |||
| || wParam == 0x8004 /*DBT_DEVICEREMOVECOMPLETE*/ | |||
| || wParam == 0x0007 /*DBT_DEVNODES_CHANGED*/)) | |||
| { | |||
| ((DeviceChangeDetector*) GetWindowLongPtr (h, GWLP_USERDATA)) | |||
| ->triggerAsyncDeviceChangeCallback(); | |||
| } | |||
| return DefWindowProc (h, message, wParam, lParam); | |||
| } | |||
| void timerCallback() override | |||
| { | |||
| stopTimer(); | |||
| systemDeviceChanged(); | |||
| } | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,232 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| extern HWND juce_messageWindowHandle; | |||
| typedef bool (*CheckEventBlockedByModalComps) (const MSG&); | |||
| CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr; | |||
| //============================================================================== | |||
| namespace WindowsMessageHelpers | |||
| { | |||
| const unsigned int customMessageID = WM_USER + 123; | |||
| const unsigned int broadcastMessageMagicNumber = 0xc403; | |||
| const TCHAR messageWindowName[] = _T("JUCEWindow"); | |||
| ScopedPointer<HiddenMessageWindow> messageWindow; | |||
| void dispatchMessageFromLParam (LPARAM lParam) | |||
| { | |||
| if (MessageManager::MessageBase* message = reinterpret_cast<MessageManager::MessageBase*> (lParam)) | |||
| { | |||
| JUCE_TRY | |||
| { | |||
| message->messageCallback(); | |||
| } | |||
| JUCE_CATCH_EXCEPTION | |||
| message->decReferenceCount(); | |||
| } | |||
| } | |||
| BOOL CALLBACK broadcastEnumWindowProc (HWND hwnd, LPARAM lParam) | |||
| { | |||
| if (hwnd != juce_messageWindowHandle) | |||
| { | |||
| TCHAR windowName[64] = { 0 }; // no need to read longer strings than this | |||
| GetWindowText (hwnd, windowName, 63); | |||
| if (String (windowName) == messageWindowName) | |||
| reinterpret_cast<Array<HWND>*> (lParam)->add (hwnd); | |||
| } | |||
| return TRUE; | |||
| } | |||
| void handleBroadcastMessage (const COPYDATASTRUCT* const data) | |||
| { | |||
| if (data != nullptr && data->dwData == broadcastMessageMagicNumber) | |||
| { | |||
| struct BroadcastMessage : public CallbackMessage | |||
| { | |||
| BroadcastMessage (CharPointer_UTF32 text, size_t length) : message (text, length) {} | |||
| void messageCallback() override { MessageManager::getInstance()->deliverBroadcastMessage (message); } | |||
| String message; | |||
| }; | |||
| (new BroadcastMessage (CharPointer_UTF32 ((const CharPointer_UTF32::CharType*) data->lpData), | |||
| data->cbData / sizeof (CharPointer_UTF32::CharType))) | |||
| ->post(); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| LRESULT CALLBACK messageWndProc (HWND h, const UINT message, const WPARAM wParam, const LPARAM lParam) noexcept | |||
| { | |||
| if (h == juce_messageWindowHandle) | |||
| { | |||
| if (message == customMessageID) | |||
| { | |||
| // (These are trapped early in our dispatch loop, but must also be checked | |||
| // here in case some 3rd-party code is running the dispatch loop). | |||
| dispatchMessageFromLParam (lParam); | |||
| return 0; | |||
| } | |||
| if (message == WM_COPYDATA) | |||
| { | |||
| handleBroadcastMessage (reinterpret_cast<const COPYDATASTRUCT*> (lParam)); | |||
| return 0; | |||
| } | |||
| } | |||
| return DefWindowProc (h, message, wParam, lParam); | |||
| } | |||
| } | |||
| #if JUCE_MODULE_AVAILABLE_juce_gui_extra && ! JUCE_MINGW | |||
| LRESULT juce_offerEventToActiveXControl (::MSG&); | |||
| #endif | |||
| //============================================================================== | |||
| bool MessageManager::dispatchNextMessageOnSystemQueue (const bool returnIfNoPendingMessages) | |||
| { | |||
| using namespace WindowsMessageHelpers; | |||
| MSG m; | |||
| if (returnIfNoPendingMessages && ! PeekMessage (&m, (HWND) 0, 0, 0, PM_NOREMOVE)) | |||
| return false; | |||
| if (GetMessage (&m, (HWND) 0, 0, 0) >= 0) | |||
| { | |||
| #if JUCE_MODULE_AVAILABLE_juce_gui_extra && ! JUCE_MINGW | |||
| if (juce_offerEventToActiveXControl (m) != S_FALSE) | |||
| return true; | |||
| #endif | |||
| if (m.message == customMessageID && m.hwnd == juce_messageWindowHandle) | |||
| { | |||
| dispatchMessageFromLParam (m.lParam); | |||
| } | |||
| else if (m.message == WM_QUIT) | |||
| { | |||
| if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) | |||
| app->systemRequestedQuit(); | |||
| } | |||
| else if (isEventBlockedByModalComps == nullptr || ! isEventBlockedByModalComps (m)) | |||
| { | |||
| if ((m.message == WM_LBUTTONDOWN || m.message == WM_RBUTTONDOWN) | |||
| && ! JuceWindowIdentifier::isJUCEWindow (m.hwnd)) | |||
| { | |||
| // if it's someone else's window being clicked on, and the focus is | |||
| // currently on a juce window, pass the kb focus over.. | |||
| HWND currentFocus = GetFocus(); | |||
| if (currentFocus == 0 || JuceWindowIdentifier::isJUCEWindow (currentFocus)) | |||
| SetFocus (m.hwnd); | |||
| } | |||
| TranslateMessage (&m); | |||
| DispatchMessage (&m); | |||
| } | |||
| } | |||
| return true; | |||
| } | |||
| bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) | |||
| { | |||
| message->incReferenceCount(); | |||
| return PostMessage (juce_messageWindowHandle, WindowsMessageHelpers::customMessageID, 0, (LPARAM) message) != 0; | |||
| } | |||
| void MessageManager::broadcastMessage (const String& value) | |||
| { | |||
| const String localCopy (value); | |||
| Array<HWND> windows; | |||
| EnumWindows (&WindowsMessageHelpers::broadcastEnumWindowProc, (LPARAM) &windows); | |||
| for (int i = windows.size(); --i >= 0;) | |||
| { | |||
| COPYDATASTRUCT data; | |||
| data.dwData = WindowsMessageHelpers::broadcastMessageMagicNumber; | |||
| data.cbData = (localCopy.length() + 1) * sizeof (CharPointer_UTF32::CharType); | |||
| data.lpData = (void*) localCopy.toUTF32().getAddress(); | |||
| DWORD_PTR result; | |||
| SendMessageTimeout (windows.getUnchecked (i), WM_COPYDATA, | |||
| (WPARAM) juce_messageWindowHandle, | |||
| (LPARAM) &data, | |||
| SMTO_BLOCK | SMTO_ABORTIFHUNG, 8000, &result); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void MessageManager::doPlatformSpecificInitialisation() | |||
| { | |||
| OleInitialize (0); | |||
| using namespace WindowsMessageHelpers; | |||
| messageWindow = new HiddenMessageWindow (messageWindowName, (WNDPROC) messageWndProc); | |||
| juce_messageWindowHandle = messageWindow->getHWND(); | |||
| } | |||
| void MessageManager::doPlatformSpecificShutdown() | |||
| { | |||
| WindowsMessageHelpers::messageWindow = nullptr; | |||
| OleUninitialize(); | |||
| } | |||
| //============================================================================== | |||
| struct MountedVolumeListChangeDetector::Pimpl : private DeviceChangeDetector | |||
| { | |||
| Pimpl (MountedVolumeListChangeDetector& d) : DeviceChangeDetector (L"MountedVolumeList"), owner (d) | |||
| { | |||
| File::findFileSystemRoots (lastVolumeList); | |||
| } | |||
| void systemDeviceChanged() override | |||
| { | |||
| Array<File> newList; | |||
| File::findFileSystemRoots (newList); | |||
| if (lastVolumeList != newList) | |||
| { | |||
| lastVolumeList = newList; | |||
| owner.mountedVolumeListChanged(); | |||
| } | |||
| } | |||
| MountedVolumeListChangeDetector& owner; | |||
| Array<File> lastVolumeList; | |||
| }; | |||
| MountedVolumeListChangeDetector::MountedVolumeListChangeDetector() { pimpl = new Pimpl (*this); } | |||
| MountedVolumeListChangeDetector::~MountedVolumeListChangeDetector() {} | |||
| } // namespace juce | |||
| @@ -0,0 +1,26 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| juce_ImplementSingleton (WinRTWrapper) | |||
| } | |||
| @@ -0,0 +1,131 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| class WinRTWrapper : public DeletedAtShutdown | |||
| { | |||
| public: | |||
| juce_DeclareSingleton (WinRTWrapper, true) | |||
| class ScopedHString | |||
| { | |||
| public: | |||
| ScopedHString (String str) | |||
| { | |||
| if (WinRTWrapper::getInstance()->isInitialised()) | |||
| WinRTWrapper::getInstance()->createHString (str.toWideCharPointer(), | |||
| static_cast<uint32_t> (str.length()), | |||
| &hstr); | |||
| } | |||
| ~ScopedHString() | |||
| { | |||
| if (WinRTWrapper::getInstance()->isInitialised() && hstr != nullptr) | |||
| WinRTWrapper::getInstance()->deleteHString (hstr); | |||
| } | |||
| HSTRING get() const noexcept | |||
| { | |||
| return hstr; | |||
| } | |||
| private: | |||
| HSTRING hstr = nullptr; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScopedHString) | |||
| }; | |||
| ~WinRTWrapper() | |||
| { | |||
| if (winRTHandle != nullptr) | |||
| ::FreeLibrary (winRTHandle); | |||
| } | |||
| String hStringToString (HSTRING hstr) | |||
| { | |||
| if (isInitialised()) | |||
| if (const wchar_t* str = getHStringRawBuffer (hstr, nullptr)) | |||
| return String (str); | |||
| return {}; | |||
| } | |||
| bool isInitialised() const noexcept | |||
| { | |||
| return initialised; | |||
| } | |||
| template <class ComClass> | |||
| ComSmartPtr<ComClass> getWRLFactory (const wchar_t* runtimeClassID) | |||
| { | |||
| ComSmartPtr<ComClass> comPtr; | |||
| if (isInitialised()) | |||
| { | |||
| ScopedHString classID (runtimeClassID); | |||
| if (classID.get() != nullptr) | |||
| roGetActivationFactory (classID.get(), __uuidof (ComClass), (void**) comPtr.resetAndGetPointerAddress()); | |||
| } | |||
| return comPtr; | |||
| } | |||
| private: | |||
| WinRTWrapper() | |||
| { | |||
| winRTHandle = ::LoadLibraryA ("api-ms-win-core-winrt-l1-1-0"); | |||
| if (winRTHandle == nullptr) | |||
| return; | |||
| roInitialize = (RoInitializeFuncPtr) ::GetProcAddress (winRTHandle, "RoInitialize"); | |||
| createHString = (WindowsCreateStringFuncPtr) ::GetProcAddress (winRTHandle, "WindowsCreateString"); | |||
| deleteHString = (WindowsDeleteStringFuncPtr) ::GetProcAddress (winRTHandle, "WindowsDeleteString"); | |||
| getHStringRawBuffer = (WindowsGetStringRawBufferFuncPtr) ::GetProcAddress (winRTHandle, "WindowsGetStringRawBuffer"); | |||
| roGetActivationFactory = (RoGetActivationFactoryFuncPtr) ::GetProcAddress (winRTHandle, "RoGetActivationFactory"); | |||
| if (roInitialize == nullptr || createHString == nullptr || deleteHString == nullptr | |||
| || getHStringRawBuffer == nullptr || roGetActivationFactory == nullptr) | |||
| return; | |||
| HRESULT status = roInitialize (1); | |||
| initialised = ! (status != S_OK && status != S_FALSE && status != 0x80010106L); | |||
| } | |||
| HMODULE winRTHandle = nullptr; | |||
| bool initialised = false; | |||
| typedef HRESULT (WINAPI* RoInitializeFuncPtr) (int); | |||
| typedef HRESULT (WINAPI* WindowsCreateStringFuncPtr) (LPCWSTR, UINT32, HSTRING*); | |||
| typedef HRESULT (WINAPI* WindowsDeleteStringFuncPtr) (HSTRING); | |||
| typedef PCWSTR (WINAPI* WindowsGetStringRawBufferFuncPtr) (HSTRING, UINT32*); | |||
| typedef HRESULT (WINAPI* RoGetActivationFactoryFuncPtr) (HSTRING, REFIID, void**); | |||
| RoInitializeFuncPtr roInitialize = nullptr; | |||
| WindowsCreateStringFuncPtr createHString = nullptr; | |||
| WindowsDeleteStringFuncPtr deleteHString = nullptr; | |||
| WindowsGetStringRawBufferFuncPtr getHStringRawBuffer = nullptr; | |||
| RoGetActivationFactoryFuncPtr roGetActivationFactory = nullptr; | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,108 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| struct MultiTimerCallback : public Timer | |||
| { | |||
| MultiTimerCallback (const int tid, MultiTimer& mt) noexcept | |||
| : owner (mt), timerID (tid) | |||
| { | |||
| } | |||
| void timerCallback() override | |||
| { | |||
| owner.timerCallback (timerID); | |||
| } | |||
| MultiTimer& owner; | |||
| const int timerID; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiTimerCallback) | |||
| }; | |||
| //============================================================================== | |||
| MultiTimer::MultiTimer() noexcept {} | |||
| MultiTimer::MultiTimer (const MultiTimer&) noexcept {} | |||
| MultiTimer::~MultiTimer() | |||
| { | |||
| const SpinLock::ScopedLockType sl (timerListLock); | |||
| timers.clear(); | |||
| } | |||
| //============================================================================== | |||
| Timer* MultiTimer::getCallback (int timerID) const noexcept | |||
| { | |||
| for (int i = timers.size(); --i >= 0;) | |||
| { | |||
| MultiTimerCallback* const t = static_cast<MultiTimerCallback*> (timers.getUnchecked(i)); | |||
| if (t->timerID == timerID) | |||
| return t; | |||
| } | |||
| return nullptr; | |||
| } | |||
| void MultiTimer::startTimer (const int timerID, const int intervalInMilliseconds) noexcept | |||
| { | |||
| const SpinLock::ScopedLockType sl (timerListLock); | |||
| Timer* timer = getCallback (timerID); | |||
| if (timer == nullptr) | |||
| timers.add (timer = new MultiTimerCallback (timerID, *this)); | |||
| timer->startTimer (intervalInMilliseconds); | |||
| } | |||
| void MultiTimer::stopTimer (const int timerID) noexcept | |||
| { | |||
| const SpinLock::ScopedLockType sl (timerListLock); | |||
| if (Timer* const t = getCallback (timerID)) | |||
| t->stopTimer(); | |||
| } | |||
| bool MultiTimer::isTimerRunning (const int timerID) const noexcept | |||
| { | |||
| const SpinLock::ScopedLockType sl (timerListLock); | |||
| if (Timer* const t = getCallback (timerID)) | |||
| return t->isTimerRunning(); | |||
| return false; | |||
| } | |||
| int MultiTimer::getTimerInterval (const int timerID) const noexcept | |||
| { | |||
| const SpinLock::ScopedLockType sl (timerListLock); | |||
| if (Timer* const t = getCallback (timerID)) | |||
| return t->getTimerInterval(); | |||
| return 0; | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,123 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| A type of timer class that can run multiple timers with different frequencies, | |||
| all of which share a single callback. | |||
| This class is very similar to the Timer class, but allows you run multiple | |||
| separate timers, where each one has a unique ID number. The methods in this | |||
| class are exactly equivalent to those in Timer, but with the addition of | |||
| this ID number. | |||
| To use it, you need to create a subclass of MultiTimer, implementing the | |||
| timerCallback() method. Then you can start timers with startTimer(), and | |||
| each time the callback is triggered, it passes in the ID of the timer that | |||
| caused it. | |||
| @see Timer | |||
| */ | |||
| class JUCE_API MultiTimer | |||
| { | |||
| protected: | |||
| //============================================================================== | |||
| /** Creates a MultiTimer. | |||
| When created, no timers are running, so use startTimer() to start things off. | |||
| */ | |||
| MultiTimer() noexcept; | |||
| /** Creates a copy of another timer. | |||
| Note that this timer will not contain any running timers, even if the one you're | |||
| copying from was running. | |||
| */ | |||
| MultiTimer (const MultiTimer&) noexcept; | |||
| public: | |||
| //============================================================================== | |||
| /** Destructor. */ | |||
| virtual ~MultiTimer(); | |||
| //============================================================================== | |||
| /** The user-defined callback routine that actually gets called by each of the | |||
| timers that are running. | |||
| It's perfectly ok to call startTimer() or stopTimer() from within this | |||
| callback to change the subsequent intervals. | |||
| */ | |||
| virtual void timerCallback (int timerID) = 0; | |||
| //============================================================================== | |||
| /** Starts a timer and sets the length of interval required. | |||
| If the timer is already started, this will reset it, so the | |||
| time between calling this method and the next timer callback | |||
| will not be less than the interval length passed in. | |||
| @param timerID a unique Id number that identifies the timer to | |||
| start. This is the id that will be passed back | |||
| to the timerCallback() method when this timer is | |||
| triggered | |||
| @param intervalInMilliseconds the interval to use (any values less than 1 will be | |||
| rounded up to 1) | |||
| */ | |||
| void startTimer (int timerID, int intervalInMilliseconds) noexcept; | |||
| /** Stops a timer. | |||
| If a timer has been started with the given ID number, it will be cancelled. | |||
| No more callbacks will be made for the specified timer after this method returns. | |||
| If this is called from a different thread, any callbacks that may | |||
| be currently executing may be allowed to finish before the method | |||
| returns. | |||
| */ | |||
| void stopTimer (int timerID) noexcept; | |||
| //============================================================================== | |||
| /** Checks whether a timer has been started for a specified ID. | |||
| @returns true if a timer with the given ID is running. | |||
| */ | |||
| bool isTimerRunning (int timerID) const noexcept; | |||
| /** Returns the interval for a specified timer ID. | |||
| @returns the timer's interval in milliseconds if it's running, or 0 if no | |||
| timer was running for the ID number specified. | |||
| */ | |||
| int getTimerInterval (int timerID) const noexcept; | |||
| //============================================================================== | |||
| private: | |||
| SpinLock timerListLock; | |||
| OwnedArray<Timer> timers; | |||
| Timer* getCallback (int) const noexcept; | |||
| MultiTimer& operator= (const MultiTimer&); | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,371 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| class Timer::TimerThread : private Thread, | |||
| private DeletedAtShutdown, | |||
| private AsyncUpdater | |||
| { | |||
| public: | |||
| typedef CriticalSection LockType; // (mysteriously, using a SpinLock here causes problems on some XP machines..) | |||
| TimerThread() | |||
| : Thread ("Juce Timer"), | |||
| firstTimer (nullptr) | |||
| { | |||
| triggerAsyncUpdate(); | |||
| } | |||
| ~TimerThread() noexcept | |||
| { | |||
| signalThreadShouldExit(); | |||
| callbackArrived.signal(); | |||
| stopThread (4000); | |||
| jassert (instance == this || instance == nullptr); | |||
| if (instance == this) | |||
| instance = nullptr; | |||
| } | |||
| void run() override | |||
| { | |||
| uint32 lastTime = Time::getMillisecondCounter(); | |||
| MessageManager::MessageBase::Ptr messageToSend (new CallTimersMessage()); | |||
| while (! threadShouldExit()) | |||
| { | |||
| const uint32 now = Time::getMillisecondCounter(); | |||
| const int elapsed = (int) (now >= lastTime ? (now - lastTime) | |||
| : (std::numeric_limits<uint32>::max() - (lastTime - now))); | |||
| lastTime = now; | |||
| const int timeUntilFirstTimer = getTimeUntilFirstTimer (elapsed); | |||
| if (timeUntilFirstTimer <= 0) | |||
| { | |||
| if (callbackArrived.wait (0)) | |||
| { | |||
| // already a message in flight - do nothing.. | |||
| } | |||
| else | |||
| { | |||
| messageToSend->post(); | |||
| if (! callbackArrived.wait (300)) | |||
| { | |||
| // Sometimes our message can get discarded by the OS (e.g. when running as an RTAS | |||
| // when the app has a modal loop), so this is how long to wait before assuming the | |||
| // message has been lost and trying again. | |||
| messageToSend->post(); | |||
| } | |||
| continue; | |||
| } | |||
| } | |||
| // don't wait for too long because running this loop also helps keep the | |||
| // Time::getApproximateMillisecondTimer value stay up-to-date | |||
| wait (jlimit (1, 100, timeUntilFirstTimer)); | |||
| } | |||
| } | |||
| void callTimers() | |||
| { | |||
| // avoid getting stuck in a loop if a timer callback repeatedly takes too long | |||
| const uint32 timeout = Time::getMillisecondCounter() + 100; | |||
| const LockType::ScopedLockType sl (lock); | |||
| while (firstTimer != nullptr && firstTimer->timerCountdownMs <= 0) | |||
| { | |||
| Timer* const t = firstTimer; | |||
| t->timerCountdownMs = t->timerPeriodMs; | |||
| removeTimer (t); | |||
| addTimer (t); | |||
| const LockType::ScopedUnlockType ul (lock); | |||
| JUCE_TRY | |||
| { | |||
| t->timerCallback(); | |||
| } | |||
| JUCE_CATCH_EXCEPTION | |||
| if (Time::getMillisecondCounter() > timeout) | |||
| break; | |||
| } | |||
| callbackArrived.signal(); | |||
| } | |||
| void callTimersSynchronously() | |||
| { | |||
| if (! isThreadRunning()) | |||
| { | |||
| // (This is relied on by some plugins in cases where the MM has | |||
| // had to restart and the async callback never started) | |||
| cancelPendingUpdate(); | |||
| triggerAsyncUpdate(); | |||
| } | |||
| callTimers(); | |||
| } | |||
| static inline void add (Timer* const tim) noexcept | |||
| { | |||
| if (instance == nullptr) | |||
| instance = new TimerThread(); | |||
| instance->addTimer (tim); | |||
| } | |||
| static inline void remove (Timer* const tim) noexcept | |||
| { | |||
| if (instance != nullptr) | |||
| instance->removeTimer (tim); | |||
| } | |||
| static inline void resetCounter (Timer* const tim, const int newCounter) noexcept | |||
| { | |||
| if (instance != nullptr) | |||
| { | |||
| tim->timerCountdownMs = newCounter; | |||
| tim->timerPeriodMs = newCounter; | |||
| if ((tim->nextTimer != nullptr && tim->nextTimer->timerCountdownMs < tim->timerCountdownMs) | |||
| || (tim->previousTimer != nullptr && tim->previousTimer->timerCountdownMs > tim->timerCountdownMs)) | |||
| { | |||
| instance->removeTimer (tim); | |||
| instance->addTimer (tim); | |||
| } | |||
| } | |||
| } | |||
| static TimerThread* instance; | |||
| static LockType lock; | |||
| private: | |||
| Timer* volatile firstTimer; | |||
| WaitableEvent callbackArrived; | |||
| struct CallTimersMessage : public MessageManager::MessageBase | |||
| { | |||
| CallTimersMessage() {} | |||
| void messageCallback() override | |||
| { | |||
| if (instance != nullptr) | |||
| instance->callTimers(); | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| void addTimer (Timer* const t) noexcept | |||
| { | |||
| #if JUCE_DEBUG | |||
| // trying to add a timer that's already here - shouldn't get to this point, | |||
| // so if you get this assertion, let me know! | |||
| jassert (! timerExists (t)); | |||
| #endif | |||
| Timer* i = firstTimer; | |||
| if (i == nullptr || i->timerCountdownMs > t->timerCountdownMs) | |||
| { | |||
| t->nextTimer = firstTimer; | |||
| firstTimer = t; | |||
| } | |||
| else | |||
| { | |||
| while (i->nextTimer != nullptr && i->nextTimer->timerCountdownMs <= t->timerCountdownMs) | |||
| i = i->nextTimer; | |||
| jassert (i != nullptr); | |||
| t->nextTimer = i->nextTimer; | |||
| t->previousTimer = i; | |||
| i->nextTimer = t; | |||
| } | |||
| if (t->nextTimer != nullptr) | |||
| t->nextTimer->previousTimer = t; | |||
| jassert ((t->nextTimer == nullptr || t->nextTimer->timerCountdownMs >= t->timerCountdownMs) | |||
| && (t->previousTimer == nullptr || t->previousTimer->timerCountdownMs <= t->timerCountdownMs)); | |||
| notify(); | |||
| } | |||
| void removeTimer (Timer* const t) noexcept | |||
| { | |||
| #if JUCE_DEBUG | |||
| // trying to remove a timer that's not here - shouldn't get to this point, | |||
| // so if you get this assertion, let me know! | |||
| jassert (timerExists (t)); | |||
| #endif | |||
| if (t->previousTimer != nullptr) | |||
| { | |||
| jassert (firstTimer != t); | |||
| t->previousTimer->nextTimer = t->nextTimer; | |||
| } | |||
| else | |||
| { | |||
| jassert (firstTimer == t); | |||
| firstTimer = t->nextTimer; | |||
| } | |||
| if (t->nextTimer != nullptr) | |||
| t->nextTimer->previousTimer = t->previousTimer; | |||
| t->nextTimer = nullptr; | |||
| t->previousTimer = nullptr; | |||
| } | |||
| int getTimeUntilFirstTimer (const int numMillisecsElapsed) const | |||
| { | |||
| const LockType::ScopedLockType sl (lock); | |||
| for (Timer* t = firstTimer; t != nullptr; t = t->nextTimer) | |||
| t->timerCountdownMs -= numMillisecsElapsed; | |||
| return firstTimer != nullptr ? firstTimer->timerCountdownMs : 1000; | |||
| } | |||
| void handleAsyncUpdate() override | |||
| { | |||
| startThread (7); | |||
| } | |||
| #if JUCE_DEBUG | |||
| bool timerExists (Timer* const t) const noexcept | |||
| { | |||
| for (Timer* tt = firstTimer; tt != nullptr; tt = tt->nextTimer) | |||
| if (tt == t) | |||
| return true; | |||
| return false; | |||
| } | |||
| #endif | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TimerThread) | |||
| }; | |||
| Timer::TimerThread* Timer::TimerThread::instance = nullptr; | |||
| Timer::TimerThread::LockType Timer::TimerThread::lock; | |||
| //============================================================================== | |||
| Timer::Timer() noexcept | |||
| : timerCountdownMs (0), | |||
| timerPeriodMs (0), | |||
| previousTimer (nullptr), | |||
| nextTimer (nullptr) | |||
| { | |||
| } | |||
| Timer::Timer (const Timer&) noexcept | |||
| : timerCountdownMs (0), | |||
| timerPeriodMs (0), | |||
| previousTimer (nullptr), | |||
| nextTimer (nullptr) | |||
| { | |||
| } | |||
| Timer::~Timer() | |||
| { | |||
| stopTimer(); | |||
| } | |||
| void Timer::startTimer (const int interval) noexcept | |||
| { | |||
| // If you're calling this before (or after) the MessageManager is | |||
| // running, then you're not going to get any timer callbacks! | |||
| jassert (MessageManager::getInstanceWithoutCreating() != nullptr); | |||
| const TimerThread::LockType::ScopedLockType sl (TimerThread::lock); | |||
| if (timerPeriodMs == 0) | |||
| { | |||
| timerCountdownMs = interval; | |||
| timerPeriodMs = jmax (1, interval); | |||
| TimerThread::add (this); | |||
| } | |||
| else | |||
| { | |||
| TimerThread::resetCounter (this, interval); | |||
| } | |||
| } | |||
| void Timer::startTimerHz (int timerFrequencyHz) noexcept | |||
| { | |||
| if (timerFrequencyHz > 0) | |||
| startTimer (1000 / timerFrequencyHz); | |||
| else | |||
| stopTimer(); | |||
| } | |||
| void Timer::stopTimer() noexcept | |||
| { | |||
| const TimerThread::LockType::ScopedLockType sl (TimerThread::lock); | |||
| if (timerPeriodMs > 0) | |||
| { | |||
| TimerThread::remove (this); | |||
| timerPeriodMs = 0; | |||
| } | |||
| } | |||
| void JUCE_CALLTYPE Timer::callPendingTimersSynchronously() | |||
| { | |||
| if (TimerThread::instance != nullptr) | |||
| TimerThread::instance->callTimersSynchronously(); | |||
| } | |||
| struct LambdaInvoker : private Timer | |||
| { | |||
| LambdaInvoker (int milliseconds, std::function<void()> f) : function (f) | |||
| { | |||
| startTimer (milliseconds); | |||
| } | |||
| void timerCallback() override | |||
| { | |||
| auto f = function; | |||
| delete this; | |||
| f(); | |||
| } | |||
| std::function<void()> function; | |||
| JUCE_DECLARE_NON_COPYABLE (LambdaInvoker) | |||
| }; | |||
| void JUCE_CALLTYPE Timer::callAfterDelay (int milliseconds, std::function<void()> f) | |||
| { | |||
| new LambdaInvoker (milliseconds, f); | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,135 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Makes repeated callbacks to a virtual method at a specified time interval. | |||
| A Timer's timerCallback() method will be repeatedly called at a given | |||
| interval. When you create a Timer object, it will do nothing until the | |||
| startTimer() method is called, which will cause the message thread to | |||
| start making callbacks at the specified interval, until stopTimer() is called | |||
| or the object is deleted. | |||
| The time interval isn't guaranteed to be precise to any more than maybe | |||
| 10-20ms, and the intervals may end up being much longer than requested if the | |||
| system is busy. Because the callbacks are made by the main message thread, | |||
| anything that blocks the message queue for a period of time will also prevent | |||
| any timers from running until it can carry on. | |||
| If you need to have a single callback that is shared by multiple timers with | |||
| different frequencies, then the MultiTimer class allows you to do that - its | |||
| structure is very similar to the Timer class, but contains multiple timers | |||
| internally, each one identified by an ID number. | |||
| @see HighResolutionTimer, MultiTimer | |||
| */ | |||
| class JUCE_API Timer | |||
| { | |||
| protected: | |||
| //============================================================================== | |||
| /** Creates a Timer. | |||
| When created, the timer is stopped, so use startTimer() to get it going. | |||
| */ | |||
| Timer() noexcept; | |||
| /** Creates a copy of another timer. | |||
| Note that this timer won't be started, even if the one you're copying | |||
| is running. | |||
| */ | |||
| Timer (const Timer&) noexcept; | |||
| public: | |||
| //============================================================================== | |||
| /** Destructor. */ | |||
| virtual ~Timer(); | |||
| //============================================================================== | |||
| /** The user-defined callback routine that actually gets called periodically. | |||
| It's perfectly ok to call startTimer() or stopTimer() from within this | |||
| callback to change the subsequent intervals. | |||
| */ | |||
| virtual void timerCallback() = 0; | |||
| //============================================================================== | |||
| /** Starts the timer and sets the length of interval required. | |||
| If the timer is already started, this will reset it, so the | |||
| time between calling this method and the next timer callback | |||
| will not be less than the interval length passed in. | |||
| @param intervalInMilliseconds the interval to use (any value less | |||
| than 1 will be rounded up to 1) | |||
| */ | |||
| void startTimer (int intervalInMilliseconds) noexcept; | |||
| /** Starts the timer with an interval specified in Hertz. | |||
| This is effectively the same as calling startTimer (1000 / timerFrequencyHz). | |||
| */ | |||
| void startTimerHz (int timerFrequencyHz) noexcept; | |||
| /** Stops the timer. | |||
| No more timer callbacks will be triggered after this method returns. | |||
| Note that if you call this from a background thread while the message-thread | |||
| is already in the middle of your callback, then this method will cancel any | |||
| future timer callbacks, but it will return without waiting for the current one | |||
| to finish. The current callback will continue, possibly still running some of | |||
| your timer code after this method has returned. | |||
| */ | |||
| void stopTimer() noexcept; | |||
| //============================================================================== | |||
| /** Returns true if the timer is currently running. */ | |||
| bool isTimerRunning() const noexcept { return timerPeriodMs > 0; } | |||
| /** Returns the timer's interval. | |||
| @returns the timer's interval in milliseconds if it's running, or 0 if it's not. | |||
| */ | |||
| int getTimerInterval() const noexcept { return timerPeriodMs; } | |||
| //============================================================================== | |||
| /** Invokes a lambda after a given number of milliseconds. */ | |||
| static void JUCE_CALLTYPE callAfterDelay (int milliseconds, std::function<void()> functionToCall); | |||
| //============================================================================== | |||
| /** For internal use only: invokes any timers that need callbacks. | |||
| Don't call this unless you really know what you're doing! | |||
| */ | |||
| static void JUCE_CALLTYPE callPendingTimersSynchronously(); | |||
| private: | |||
| class TimerThread; | |||
| friend class TimerThread; | |||
| int timerCountdownMs, timerPeriodMs; // NB: these member variable names are a little verbose | |||
| Timer* previousTimer, *nextTimer; // to reduce risk of name-clashes with user subclasses | |||
| Timer& operator= (const Timer&) JUCE_DELETED_FUNCTION; | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,123 @@ | |||
| #!/usr/bin/make -f | |||
| # Makefile for juce_graphics # | |||
| # -------------------------- # | |||
| # Created by falkTX | |||
| # | |||
| CWD=../.. | |||
| MODULENAME=juce_graphics | |||
| include ../Makefile.mk | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| BUILD_CXX_FLAGS += $(JUCE_GRAPHICS_FLAGS) -I.. | |||
| ifeq ($(WIN32),true) | |||
| BUILD_CXX_FLAGS += -Wno-missing-field-initializers -Wno-strict-overflow | |||
| endif | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| ifeq ($(MACOS),true) | |||
| OBJS = $(OBJDIR)/$(MODULENAME).mm.o | |||
| OBJS_posix32 = $(OBJDIR)/$(MODULENAME).mm.posix32.o | |||
| OBJS_posix64 = $(OBJDIR)/$(MODULENAME).mm.posix64.o | |||
| else | |||
| OBJS = $(OBJDIR)/$(MODULENAME).cpp.o | |||
| OBJS_posix32 = $(OBJDIR)/$(MODULENAME).cpp.posix32.o | |||
| OBJS_posix64 = $(OBJDIR)/$(MODULENAME).cpp.posix64.o | |||
| endif | |||
| OBJS_win32 = $(OBJDIR)/$(MODULENAME).cpp.win32.o | |||
| OBJS_win64 = $(OBJDIR)/$(MODULENAME).cpp.win64.o | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| all: $(MODULEDIR)/$(MODULENAME).a | |||
| posix32: $(MODULEDIR)/$(MODULENAME).posix32.a | |||
| posix64: $(MODULEDIR)/$(MODULENAME).posix64.a | |||
| win32: $(MODULEDIR)/$(MODULENAME).win32.a | |||
| win64: $(MODULEDIR)/$(MODULENAME).win64.a | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| clean: | |||
| rm -f $(OBJDIR)/*.o $(MODULEDIR)/$(MODULENAME)*.a | |||
| debug: | |||
| $(MAKE) DEBUG=true | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| $(MODULEDIR)/$(MODULENAME).a: $(OBJS) | |||
| -@mkdir -p $(MODULEDIR) | |||
| @echo "Creating $(MODULENAME).a" | |||
| @rm -f $@ | |||
| @$(AR) crs $@ $^ | |||
| $(MODULEDIR)/$(MODULENAME).posix32.a: $(OBJS_posix32) | |||
| -@mkdir -p $(MODULEDIR) | |||
| @echo "Creating $(MODULENAME).posix32.a" | |||
| @rm -f $@ | |||
| @$(AR) crs $@ $^ | |||
| $(MODULEDIR)/$(MODULENAME).posix64.a: $(OBJS_posix64) | |||
| -@mkdir -p $(MODULEDIR) | |||
| @echo "Creating $(MODULENAME).posix64.a" | |||
| @rm -f $@ | |||
| @$(AR) crs $@ $^ | |||
| $(MODULEDIR)/$(MODULENAME).win32.a: $(OBJS_win32) | |||
| -@mkdir -p $(MODULEDIR) | |||
| @echo "Creating $(MODULENAME).win32.a" | |||
| @rm -f $@ | |||
| @$(AR) crs $@ $^ | |||
| $(MODULEDIR)/$(MODULENAME).win64.a: $(OBJS_win64) | |||
| -@mkdir -p $(MODULEDIR) | |||
| @echo "Creating $(MODULENAME).win64.a" | |||
| @rm -f $@ | |||
| @$(AR) crs $@ $^ | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| $(OBJDIR)/$(MODULENAME).cpp.o: $(MODULENAME).cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $<" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | |||
| $(OBJDIR)/$(MODULENAME).cpp.%32.o: $(MODULENAME).cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $< (32bit)" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ | |||
| $(OBJDIR)/$(MODULENAME).cpp.%64.o: $(MODULENAME).cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $< (64bit)" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| $(OBJDIR)/$(MODULENAME).mm.o: $(MODULENAME).cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $<" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ | |||
| $(OBJDIR)/$(MODULENAME).mm.%32.o: $(MODULENAME).cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $< (32bit)" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -ObjC++ -c -o $@ | |||
| $(OBJDIR)/$(MODULENAME).mm.%64.o: $(MODULENAME).cpp | |||
| -@mkdir -p $(OBJDIR) | |||
| @echo "Compiling $< (64bit)" | |||
| @$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -ObjC++ -c -o $@ | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| -include $(OBJS:%.o=%.d) | |||
| -include $(OBJS_posix32:%.o=%.d) | |||
| -include $(OBJS_posix64:%.o=%.d) | |||
| -include $(OBJS_win32:%.o=%.d) | |||
| -include $(OBJS_win64:%.o=%.d) | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| @@ -0,0 +1,468 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| namespace ColourHelpers | |||
| { | |||
| static uint8 floatToUInt8 (const float n) noexcept | |||
| { | |||
| return n <= 0.0f ? 0 : (n >= 1.0f ? 255 : static_cast<uint8> (n * 255.996f)); | |||
| } | |||
| //============================================================================== | |||
| struct HSB | |||
| { | |||
| HSB (Colour col) noexcept | |||
| { | |||
| const int r = col.getRed(); | |||
| const int g = col.getGreen(); | |||
| const int b = col.getBlue(); | |||
| const int hi = jmax (r, g, b); | |||
| const int lo = jmin (r, g, b); | |||
| if (hi != 0) | |||
| { | |||
| saturation = (hi - lo) / (float) hi; | |||
| if (saturation > 0) | |||
| { | |||
| const float invDiff = 1.0f / (hi - lo); | |||
| const float red = (hi - r) * invDiff; | |||
| const float green = (hi - g) * invDiff; | |||
| const float blue = (hi - b) * invDiff; | |||
| if (r == hi) | |||
| hue = blue - green; | |||
| else if (g == hi) | |||
| hue = 2.0f + red - blue; | |||
| else | |||
| hue = 4.0f + green - red; | |||
| hue *= 1.0f / 6.0f; | |||
| if (hue < 0) | |||
| ++hue; | |||
| } | |||
| else | |||
| { | |||
| hue = 0; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| saturation = hue = 0; | |||
| } | |||
| brightness = hi / 255.0f; | |||
| } | |||
| Colour toColour (Colour original) const noexcept | |||
| { | |||
| return Colour (hue, saturation, brightness, original.getAlpha()); | |||
| } | |||
| static PixelARGB toRGB (float h, float s, float v, const uint8 alpha) noexcept | |||
| { | |||
| v = jlimit (0.0f, 255.0f, v * 255.0f); | |||
| const uint8 intV = (uint8) roundToInt (v); | |||
| if (s <= 0) | |||
| return PixelARGB (alpha, intV, intV, intV); | |||
| s = jmin (1.0f, s); | |||
| h = (h - std::floor (h)) * 6.0f + 0.00001f; // need a small adjustment to compensate for rounding errors | |||
| const float f = h - std::floor (h); | |||
| const uint8 x = (uint8) roundToInt (v * (1.0f - s)); | |||
| if (h < 1.0f) return PixelARGB (alpha, intV, (uint8) roundToInt (v * (1.0f - (s * (1.0f - f)))), x); | |||
| if (h < 2.0f) return PixelARGB (alpha, (uint8) roundToInt (v * (1.0f - s * f)), intV, x); | |||
| if (h < 3.0f) return PixelARGB (alpha, x, intV, (uint8) roundToInt (v * (1.0f - (s * (1.0f - f))))); | |||
| if (h < 4.0f) return PixelARGB (alpha, x, (uint8) roundToInt (v * (1.0f - s * f)), intV); | |||
| if (h < 5.0f) return PixelARGB (alpha, (uint8) roundToInt (v * (1.0f - (s * (1.0f - f)))), x, intV); | |||
| return PixelARGB (alpha, intV, x, (uint8) roundToInt (v * (1.0f - s * f))); | |||
| } | |||
| float hue, saturation, brightness; | |||
| }; | |||
| //============================================================================== | |||
| struct YIQ | |||
| { | |||
| YIQ (Colour c) noexcept | |||
| { | |||
| const float r = c.getFloatRed(); | |||
| const float g = c.getFloatGreen(); | |||
| const float b = c.getFloatBlue(); | |||
| y = 0.2999f * r + 0.5870f * g + 0.1140f * b; | |||
| i = 0.5957f * r - 0.2744f * g - 0.3212f * b; | |||
| q = 0.2114f * r - 0.5225f * g - 0.3113f * b; | |||
| alpha = c.getFloatAlpha(); | |||
| } | |||
| Colour toColour() const noexcept | |||
| { | |||
| return Colour::fromFloatRGBA (y + 0.9563f * i + 0.6210f * q, | |||
| y - 0.2721f * i - 0.6474f * q, | |||
| y - 1.1070f * i + 1.7046f * q, | |||
| alpha); | |||
| } | |||
| float y, i, q, alpha; | |||
| }; | |||
| } | |||
| //============================================================================== | |||
| Colour::Colour() noexcept | |||
| : argb (0, 0, 0, 0) | |||
| { | |||
| } | |||
| Colour::Colour (const Colour& other) noexcept | |||
| : argb (other.argb) | |||
| { | |||
| } | |||
| Colour& Colour::operator= (const Colour& other) noexcept | |||
| { | |||
| argb = other.argb; | |||
| return *this; | |||
| } | |||
| bool Colour::operator== (const Colour& other) const noexcept { return argb.getNativeARGB() == other.argb.getNativeARGB(); } | |||
| bool Colour::operator!= (const Colour& other) const noexcept { return argb.getNativeARGB() != other.argb.getNativeARGB(); } | |||
| //============================================================================== | |||
| Colour::Colour (const uint32 col) noexcept | |||
| : argb ((col >> 24) & 0xff, (col >> 16) & 0xff, (col >> 8) & 0xff, col & 0xff) | |||
| { | |||
| } | |||
| Colour::Colour (const uint8 red, const uint8 green, const uint8 blue) noexcept | |||
| { | |||
| argb.setARGB (0xff, red, green, blue); | |||
| } | |||
| Colour Colour::fromRGB (const uint8 red, const uint8 green, const uint8 blue) noexcept | |||
| { | |||
| return Colour (red, green, blue); | |||
| } | |||
| Colour::Colour (const uint8 red, const uint8 green, const uint8 blue, const uint8 alpha) noexcept | |||
| { | |||
| argb.setARGB (alpha, red, green, blue); | |||
| } | |||
| Colour Colour::fromRGBA (const uint8 red, const uint8 green, const uint8 blue, const uint8 alpha) noexcept | |||
| { | |||
| return Colour (red, green, blue, alpha); | |||
| } | |||
| Colour::Colour (const uint8 red, const uint8 green, const uint8 blue, const float alpha) noexcept | |||
| { | |||
| argb.setARGB (ColourHelpers::floatToUInt8 (alpha), red, green, blue); | |||
| } | |||
| Colour Colour::fromFloatRGBA (const float red, const float green, const float blue, const float alpha) noexcept | |||
| { | |||
| return Colour (ColourHelpers::floatToUInt8 (red), | |||
| ColourHelpers::floatToUInt8 (green), | |||
| ColourHelpers::floatToUInt8 (blue), alpha); | |||
| } | |||
| Colour::Colour (const float hue, const float saturation, const float brightness, const float alpha) noexcept | |||
| : argb (ColourHelpers::HSB::toRGB (hue, saturation, brightness, ColourHelpers::floatToUInt8 (alpha))) | |||
| { | |||
| } | |||
| Colour Colour::fromHSV (const float hue, const float saturation, const float brightness, const float alpha) noexcept | |||
| { | |||
| return Colour (hue, saturation, brightness, alpha); | |||
| } | |||
| Colour::Colour (const float hue, const float saturation, const float brightness, const uint8 alpha) noexcept | |||
| : argb (ColourHelpers::HSB::toRGB (hue, saturation, brightness, alpha)) | |||
| { | |||
| } | |||
| Colour::Colour (PixelARGB argb_) noexcept | |||
| : argb (argb_) | |||
| { | |||
| } | |||
| Colour::Colour (PixelRGB rgb) noexcept | |||
| : argb (Colour (rgb.getInARGBMaskOrder()).argb) | |||
| { | |||
| } | |||
| Colour::Colour (PixelAlpha alpha) noexcept | |||
| : argb (Colour (alpha.getInARGBMaskOrder()).argb) | |||
| { | |||
| } | |||
| Colour::~Colour() noexcept | |||
| { | |||
| } | |||
| //============================================================================== | |||
| const PixelARGB Colour::getPixelARGB() const noexcept | |||
| { | |||
| PixelARGB p (argb); | |||
| p.premultiply(); | |||
| return p; | |||
| } | |||
| uint32 Colour::getARGB() const noexcept | |||
| { | |||
| return argb.getInARGBMaskOrder(); | |||
| } | |||
| //============================================================================== | |||
| bool Colour::isTransparent() const noexcept | |||
| { | |||
| return getAlpha() == 0; | |||
| } | |||
| bool Colour::isOpaque() const noexcept | |||
| { | |||
| return getAlpha() == 0xff; | |||
| } | |||
| Colour Colour::withAlpha (const uint8 newAlpha) const noexcept | |||
| { | |||
| PixelARGB newCol (argb); | |||
| newCol.setAlpha (newAlpha); | |||
| return Colour (newCol); | |||
| } | |||
| Colour Colour::withAlpha (const float newAlpha) const noexcept | |||
| { | |||
| jassert (newAlpha >= 0 && newAlpha <= 1.0f); | |||
| PixelARGB newCol (argb); | |||
| newCol.setAlpha (ColourHelpers::floatToUInt8 (newAlpha)); | |||
| return Colour (newCol); | |||
| } | |||
| Colour Colour::withMultipliedAlpha (const float alphaMultiplier) const noexcept | |||
| { | |||
| jassert (alphaMultiplier >= 0); | |||
| PixelARGB newCol (argb); | |||
| newCol.setAlpha ((uint8) jmin (0xff, roundToInt (alphaMultiplier * newCol.getAlpha()))); | |||
| return Colour (newCol); | |||
| } | |||
| //============================================================================== | |||
| Colour Colour::overlaidWith (Colour src) const noexcept | |||
| { | |||
| const int destAlpha = getAlpha(); | |||
| if (destAlpha <= 0) | |||
| return src; | |||
| const int invA = 0xff - (int) src.getAlpha(); | |||
| const int resA = 0xff - (((0xff - destAlpha) * invA) >> 8); | |||
| if (resA <= 0) | |||
| return *this; | |||
| const int da = (invA * destAlpha) / resA; | |||
| return Colour ((uint8) (src.getRed() + ((((int) getRed() - src.getRed()) * da) >> 8)), | |||
| (uint8) (src.getGreen() + ((((int) getGreen() - src.getGreen()) * da) >> 8)), | |||
| (uint8) (src.getBlue() + ((((int) getBlue() - src.getBlue()) * da) >> 8)), | |||
| (uint8) resA); | |||
| } | |||
| Colour Colour::interpolatedWith (Colour other, float proportionOfOther) const noexcept | |||
| { | |||
| if (proportionOfOther <= 0) | |||
| return *this; | |||
| if (proportionOfOther >= 1.0f) | |||
| return other; | |||
| PixelARGB c1 (getPixelARGB()); | |||
| const PixelARGB c2 (other.getPixelARGB()); | |||
| c1.tween (c2, (uint32) roundToInt (proportionOfOther * 255.0f)); | |||
| c1.unpremultiply(); | |||
| return Colour (c1); | |||
| } | |||
| //============================================================================== | |||
| float Colour::getFloatRed() const noexcept { return getRed() / 255.0f; } | |||
| float Colour::getFloatGreen() const noexcept { return getGreen() / 255.0f; } | |||
| float Colour::getFloatBlue() const noexcept { return getBlue() / 255.0f; } | |||
| float Colour::getFloatAlpha() const noexcept { return getAlpha() / 255.0f; } | |||
| //============================================================================== | |||
| void Colour::getHSB (float& h, float& s, float& v) const noexcept | |||
| { | |||
| const ColourHelpers::HSB hsb (*this); | |||
| h = hsb.hue; | |||
| s = hsb.saturation; | |||
| v = hsb.brightness; | |||
| } | |||
| float Colour::getHue() const noexcept { return ColourHelpers::HSB (*this).hue; } | |||
| float Colour::getSaturation() const noexcept { return ColourHelpers::HSB (*this).saturation; } | |||
| float Colour::getBrightness() const noexcept { return ColourHelpers::HSB (*this).brightness; } | |||
| Colour Colour::withHue (float h) const noexcept { ColourHelpers::HSB hsb (*this); hsb.hue = h; return hsb.toColour (*this); } | |||
| Colour Colour::withSaturation (float s) const noexcept { ColourHelpers::HSB hsb (*this); hsb.saturation = s; return hsb.toColour (*this); } | |||
| Colour Colour::withBrightness (float v) const noexcept { ColourHelpers::HSB hsb (*this); hsb.brightness = v; return hsb.toColour (*this); } | |||
| float Colour::getPerceivedBrightness() const noexcept | |||
| { | |||
| return std::sqrt (0.241f * square (getFloatRed()) | |||
| + 0.691f * square (getFloatGreen()) | |||
| + 0.068f * square (getFloatBlue())); | |||
| } | |||
| //============================================================================== | |||
| Colour Colour::withRotatedHue (const float amountToRotate) const noexcept | |||
| { | |||
| ColourHelpers::HSB hsb (*this); | |||
| hsb.hue += amountToRotate; | |||
| return hsb.toColour (*this); | |||
| } | |||
| Colour Colour::withMultipliedSaturation (const float amount) const noexcept | |||
| { | |||
| ColourHelpers::HSB hsb (*this); | |||
| hsb.saturation = jmin (1.0f, hsb.saturation * amount); | |||
| return hsb.toColour (*this); | |||
| } | |||
| Colour Colour::withMultipliedBrightness (const float amount) const noexcept | |||
| { | |||
| ColourHelpers::HSB hsb (*this); | |||
| hsb.brightness = jmin (1.0f, hsb.brightness * amount); | |||
| return hsb.toColour (*this); | |||
| } | |||
| //============================================================================== | |||
| Colour Colour::brighter (float amount) const noexcept | |||
| { | |||
| amount = 1.0f / (1.0f + amount); | |||
| return Colour ((uint8) (255 - (amount * (255 - getRed()))), | |||
| (uint8) (255 - (amount * (255 - getGreen()))), | |||
| (uint8) (255 - (amount * (255 - getBlue()))), | |||
| getAlpha()); | |||
| } | |||
| Colour Colour::darker (float amount) const noexcept | |||
| { | |||
| amount = 1.0f / (1.0f + amount); | |||
| return Colour ((uint8) (amount * getRed()), | |||
| (uint8) (amount * getGreen()), | |||
| (uint8) (amount * getBlue()), | |||
| getAlpha()); | |||
| } | |||
| //============================================================================== | |||
| Colour Colour::greyLevel (const float brightness) noexcept | |||
| { | |||
| const uint8 level = ColourHelpers::floatToUInt8 (brightness); | |||
| return Colour (level, level, level); | |||
| } | |||
| //============================================================================== | |||
| Colour Colour::contrasting (const float amount) const noexcept | |||
| { | |||
| return overlaidWith ((getPerceivedBrightness() >= 0.5f | |||
| ? Colours::black | |||
| : Colours::white).withAlpha (amount)); | |||
| } | |||
| Colour Colour::contrasting (Colour target, float minContrast) const noexcept | |||
| { | |||
| const ColourHelpers::YIQ bg (*this); | |||
| ColourHelpers::YIQ fg (target); | |||
| if (std::abs (bg.y - fg.y) >= minContrast) | |||
| return target; | |||
| const float y1 = jmax (0.0f, bg.y - minContrast); | |||
| const float y2 = jmin (1.0f, bg.y + minContrast); | |||
| fg.y = (std::abs (y1 - bg.y) > std::abs (y2 - bg.y)) ? y1 : y2; | |||
| return fg.toColour(); | |||
| } | |||
| Colour Colour::contrasting (Colour colour1, | |||
| Colour colour2) noexcept | |||
| { | |||
| const float b1 = colour1.getPerceivedBrightness(); | |||
| const float b2 = colour2.getPerceivedBrightness(); | |||
| float best = 0.0f; | |||
| float bestDist = 0.0f; | |||
| for (float i = 0.0f; i < 1.0f; i += 0.02f) | |||
| { | |||
| const float d1 = std::abs (i - b1); | |||
| const float d2 = std::abs (i - b2); | |||
| const float dist = jmin (d1, d2, 1.0f - d1, 1.0f - d2); | |||
| if (dist > bestDist) | |||
| { | |||
| best = i; | |||
| bestDist = dist; | |||
| } | |||
| } | |||
| return colour1.overlaidWith (colour2.withMultipliedAlpha (0.5f)) | |||
| .withBrightness (best); | |||
| } | |||
| //============================================================================== | |||
| String Colour::toString() const | |||
| { | |||
| return String::toHexString ((int) argb.getInARGBMaskOrder()); | |||
| } | |||
| Colour Colour::fromString (StringRef encodedColourString) | |||
| { | |||
| return Colour ((uint32) CharacterFunctions::HexParser<int>::parse (encodedColourString.text)); | |||
| } | |||
| String Colour::toDisplayString (const bool includeAlphaValue) const | |||
| { | |||
| return String::toHexString ((int) (argb.getInARGBMaskOrder() & (includeAlphaValue ? 0xffffffff : 0xffffff))) | |||
| .paddedLeft ('0', includeAlphaValue ? 8 : 6) | |||
| .toUpperCase(); | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,367 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Represents a colour, also including a transparency value. | |||
| The colour is stored internally as unsigned 8-bit red, green, blue and alpha values. | |||
| */ | |||
| class JUCE_API Colour | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a transparent black colour. */ | |||
| Colour() noexcept; | |||
| /** Creates a copy of another Colour object. */ | |||
| Colour (const Colour& other) noexcept; | |||
| /** Creates a colour from a 32-bit ARGB value. | |||
| The format of this number is: | |||
| ((alpha << 24) | (red << 16) | (green << 8) | blue). | |||
| All components in the range 0x00 to 0xff. | |||
| An alpha of 0x00 is completely transparent, alpha of 0xff is opaque. | |||
| @see getPixelARGB | |||
| */ | |||
| explicit Colour (uint32 argb) noexcept; | |||
| /** Creates an opaque colour using 8-bit red, green and blue values */ | |||
| Colour (uint8 red, | |||
| uint8 green, | |||
| uint8 blue) noexcept; | |||
| /** Creates an opaque colour using 8-bit red, green and blue values */ | |||
| static Colour fromRGB (uint8 red, | |||
| uint8 green, | |||
| uint8 blue) noexcept; | |||
| /** Creates a colour using 8-bit red, green, blue and alpha values. */ | |||
| Colour (uint8 red, | |||
| uint8 green, | |||
| uint8 blue, | |||
| uint8 alpha) noexcept; | |||
| /** Creates a colour using 8-bit red, green, blue and alpha values. */ | |||
| static Colour fromRGBA (uint8 red, | |||
| uint8 green, | |||
| uint8 blue, | |||
| uint8 alpha) noexcept; | |||
| /** Creates a colour from 8-bit red, green, and blue values, and a floating-point alpha. | |||
| Alpha of 0.0 is transparent, alpha of 1.0f is opaque. | |||
| Values outside the valid range will be clipped. | |||
| */ | |||
| Colour (uint8 red, | |||
| uint8 green, | |||
| uint8 blue, | |||
| float alpha) noexcept; | |||
| /** Creates a colour using floating point red, green, blue and alpha values. | |||
| Numbers outside the range 0..1 will be clipped. | |||
| */ | |||
| static Colour fromFloatRGBA (float red, | |||
| float green, | |||
| float blue, | |||
| float alpha) noexcept; | |||
| /** Creates a colour using floating point hue, saturation and brightness values, and an 8-bit alpha. | |||
| The floating point values must be between 0.0 and 1.0. | |||
| An alpha of 0x00 is completely transparent, alpha of 0xff is opaque. | |||
| Values outside the valid range will be clipped. | |||
| */ | |||
| Colour (float hue, | |||
| float saturation, | |||
| float brightness, | |||
| uint8 alpha) noexcept; | |||
| /** Creates a colour using floating point hue, saturation, brightness and alpha values. | |||
| All values must be between 0.0 and 1.0. | |||
| Numbers outside the valid range will be clipped. | |||
| */ | |||
| Colour (float hue, | |||
| float saturation, | |||
| float brightness, | |||
| float alpha) noexcept; | |||
| /** Creates a colour using a PixelARGB object. This function assumes that the argb pixel is | |||
| not premultiplied. | |||
| */ | |||
| Colour (PixelARGB argb) noexcept; | |||
| /** Creates a colour using a PixelRGB object. | |||
| */ | |||
| Colour (PixelRGB rgb) noexcept; | |||
| /** Creates a colour using a PixelAlpha object. | |||
| */ | |||
| Colour (PixelAlpha alpha) noexcept; | |||
| /** Creates a colour using floating point hue, saturation and brightness values, and an 8-bit alpha. | |||
| The floating point values must be between 0.0 and 1.0. | |||
| An alpha of 0x00 is completely transparent, alpha of 0xff is opaque. | |||
| Values outside the valid range will be clipped. | |||
| */ | |||
| static Colour fromHSV (float hue, | |||
| float saturation, | |||
| float brightness, | |||
| float alpha) noexcept; | |||
| /** Destructor. */ | |||
| ~Colour() noexcept; | |||
| /** Copies another Colour object. */ | |||
| Colour& operator= (const Colour& other) noexcept; | |||
| /** Compares two colours. */ | |||
| bool operator== (const Colour& other) const noexcept; | |||
| /** Compares two colours. */ | |||
| bool operator!= (const Colour& other) const noexcept; | |||
| //============================================================================== | |||
| /** Returns the red component of this colour. | |||
| @returns a value between 0x00 and 0xff. | |||
| */ | |||
| uint8 getRed() const noexcept { return argb.getRed(); } | |||
| /** Returns the green component of this colour. | |||
| @returns a value between 0x00 and 0xff. | |||
| */ | |||
| uint8 getGreen() const noexcept { return argb.getGreen(); } | |||
| /** Returns the blue component of this colour. | |||
| @returns a value between 0x00 and 0xff. | |||
| */ | |||
| uint8 getBlue() const noexcept { return argb.getBlue(); } | |||
| /** Returns the red component of this colour as a floating point value. | |||
| @returns a value between 0.0 and 1.0 | |||
| */ | |||
| float getFloatRed() const noexcept; | |||
| /** Returns the green component of this colour as a floating point value. | |||
| @returns a value between 0.0 and 1.0 | |||
| */ | |||
| float getFloatGreen() const noexcept; | |||
| /** Returns the blue component of this colour as a floating point value. | |||
| @returns a value between 0.0 and 1.0 | |||
| */ | |||
| float getFloatBlue() const noexcept; | |||
| /** Returns a premultiplied ARGB pixel object that represents this colour. | |||
| */ | |||
| const PixelARGB getPixelARGB() const noexcept; | |||
| /** Returns a 32-bit integer that represents this colour. | |||
| The format of this number is: | |||
| ((alpha << 24) | (red << 16) | (green << 16) | blue). | |||
| */ | |||
| uint32 getARGB() const noexcept; | |||
| //============================================================================== | |||
| /** Returns the colour's alpha (opacity). | |||
| Alpha of 0x00 is completely transparent, 0xff is completely opaque. | |||
| */ | |||
| uint8 getAlpha() const noexcept { return argb.getAlpha(); } | |||
| /** Returns the colour's alpha (opacity) as a floating point value. | |||
| Alpha of 0.0 is completely transparent, 1.0 is completely opaque. | |||
| */ | |||
| float getFloatAlpha() const noexcept; | |||
| /** Returns true if this colour is completely opaque. | |||
| Equivalent to (getAlpha() == 0xff). | |||
| */ | |||
| bool isOpaque() const noexcept; | |||
| /** Returns true if this colour is completely transparent. | |||
| Equivalent to (getAlpha() == 0x00). | |||
| */ | |||
| bool isTransparent() const noexcept; | |||
| /** Returns a colour that's the same colour as this one, but with a new alpha value. */ | |||
| Colour withAlpha (uint8 newAlpha) const noexcept; | |||
| /** Returns a colour that's the same colour as this one, but with a new alpha value. */ | |||
| Colour withAlpha (float newAlpha) const noexcept; | |||
| /** Returns a colour that's the same colour as this one, but with a modified alpha value. | |||
| The new colour's alpha will be this object's alpha multiplied by the value passed-in. | |||
| */ | |||
| Colour withMultipliedAlpha (float alphaMultiplier) const noexcept; | |||
| //============================================================================== | |||
| /** Returns a colour that is the result of alpha-compositing a new colour over this one. | |||
| If the foreground colour is semi-transparent, it is blended onto this colour accordingly. | |||
| */ | |||
| Colour overlaidWith (Colour foregroundColour) const noexcept; | |||
| /** Returns a colour that lies somewhere between this one and another. | |||
| If amountOfOther is zero, the result is 100% this colour, if amountOfOther | |||
| is 1.0, the result is 100% of the other colour. | |||
| */ | |||
| Colour interpolatedWith (Colour other, float proportionOfOther) const noexcept; | |||
| //============================================================================== | |||
| /** Returns the colour's hue component. | |||
| The value returned is in the range 0.0 to 1.0 | |||
| */ | |||
| float getHue() const noexcept; | |||
| /** Returns the colour's saturation component. | |||
| The value returned is in the range 0.0 to 1.0 | |||
| */ | |||
| float getSaturation() const noexcept; | |||
| /** Returns the colour's brightness component. | |||
| The value returned is in the range 0.0 to 1.0 | |||
| */ | |||
| float getBrightness() const noexcept; | |||
| /** Returns a skewed brightness value, adjusted to better reflect the way the human | |||
| eye responds to different colour channels. This makes it better than getBrightness() | |||
| for comparing differences in brightness. | |||
| */ | |||
| float getPerceivedBrightness() const noexcept; | |||
| /** Returns the colour's hue, saturation and brightness components all at once. | |||
| The values returned are in the range 0.0 to 1.0 | |||
| */ | |||
| void getHSB (float& hue, | |||
| float& saturation, | |||
| float& brightness) const noexcept; | |||
| //============================================================================== | |||
| /** Returns a copy of this colour with a different hue. */ | |||
| Colour withHue (float newHue) const noexcept; | |||
| /** Returns a copy of this colour with a different saturation. */ | |||
| Colour withSaturation (float newSaturation) const noexcept; | |||
| /** Returns a copy of this colour with a different brightness. | |||
| @see brighter, darker, withMultipliedBrightness | |||
| */ | |||
| Colour withBrightness (float newBrightness) const noexcept; | |||
| /** Returns a copy of this colour with it hue rotated. | |||
| The new colour's hue is ((this->getHue() + amountToRotate) % 1.0) | |||
| @see brighter, darker, withMultipliedBrightness | |||
| */ | |||
| Colour withRotatedHue (float amountToRotate) const noexcept; | |||
| /** Returns a copy of this colour with its saturation multiplied by the given value. | |||
| The new colour's saturation is (this->getSaturation() * multiplier) | |||
| (the result is clipped to legal limits). | |||
| */ | |||
| Colour withMultipliedSaturation (float multiplier) const noexcept; | |||
| /** Returns a copy of this colour with its brightness multiplied by the given value. | |||
| The new colour's brightness is (this->getBrightness() * multiplier) | |||
| (the result is clipped to legal limits). | |||
| */ | |||
| Colour withMultipliedBrightness (float amount) const noexcept; | |||
| //============================================================================== | |||
| /** Returns a brighter version of this colour. | |||
| @param amountBrighter how much brighter to make it - a value from 0 to 1.0 where 0 is | |||
| unchanged, and higher values make it brighter | |||
| @see withMultipliedBrightness | |||
| */ | |||
| Colour brighter (float amountBrighter = 0.4f) const noexcept; | |||
| /** Returns a darker version of this colour. | |||
| @param amountDarker how much darker to make it - a value from 0 to 1.0 where 0 is | |||
| unchanged, and higher values make it darker | |||
| @see withMultipliedBrightness | |||
| */ | |||
| Colour darker (float amountDarker = 0.4f) const noexcept; | |||
| //============================================================================== | |||
| /** Returns a colour that will be clearly visible against this colour. | |||
| The amount parameter indicates how contrasting the new colour should | |||
| be, so e.g. Colours::black.contrasting (0.1f) will return a colour | |||
| that's just a little bit lighter; Colours::black.contrasting (1.0f) will | |||
| return white; Colours::white.contrasting (1.0f) will return black, etc. | |||
| */ | |||
| Colour contrasting (float amount = 1.0f) const noexcept; | |||
| /** Returns a colour that is as close as possible to a target colour whilst | |||
| still being in contrast to this one. | |||
| The colour that is returned will be the targetColour, but with its luminosity | |||
| nudged up or down so that it differs from the luminosity of this colour | |||
| by at least the amount specified by minLuminosityDiff. | |||
| */ | |||
| Colour contrasting (Colour targetColour, float minLuminosityDiff) const noexcept; | |||
| /** Returns a colour that contrasts against two colours. | |||
| Looks for a colour that contrasts with both of the colours passed-in. | |||
| Handy for things like choosing a highlight colour in text editors, etc. | |||
| */ | |||
| static Colour contrasting (Colour colour1, | |||
| Colour colour2) noexcept; | |||
| //============================================================================== | |||
| /** Returns an opaque shade of grey. | |||
| @param brightness the level of grey to return - 0 is black, 1.0 is white | |||
| */ | |||
| static Colour greyLevel (float brightness) noexcept; | |||
| //============================================================================== | |||
| /** Returns a stringified version of this colour. | |||
| The string can be turned back into a colour using the fromString() method. | |||
| */ | |||
| String toString() const; | |||
| /** Reads the colour from a string that was created with toString(). */ | |||
| static Colour fromString (StringRef encodedColourString); | |||
| /** Returns the colour as a hex string in the form RRGGBB or AARRGGBB. */ | |||
| String toDisplayString (bool includeAlphaValue) const; | |||
| private: | |||
| //============================================================================== | |||
| PixelARGB argb; | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,244 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| ColourGradient::ColourGradient() noexcept | |||
| { | |||
| #if JUCE_DEBUG | |||
| point1.setX (987654.0f); | |||
| #define JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED jassert (point1.x != 987654.0f); | |||
| #else | |||
| #define JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED | |||
| #endif | |||
| } | |||
| ColourGradient::ColourGradient (Colour colour1, const float x1, const float y1, | |||
| Colour colour2, const float x2, const float y2, | |||
| const bool radial) | |||
| : point1 (x1, y1), | |||
| point2 (x2, y2), | |||
| isRadial (radial) | |||
| { | |||
| colours.add (ColourPoint (0.0, colour1)); | |||
| colours.add (ColourPoint (1.0, colour2)); | |||
| } | |||
| ColourGradient::ColourGradient (Colour colour1, Point<float> p1, | |||
| Colour colour2, Point<float> p2, | |||
| const bool radial) | |||
| : point1 (p1), | |||
| point2 (p2), | |||
| isRadial (radial) | |||
| { | |||
| colours.add (ColourPoint (0.0, colour1)); | |||
| colours.add (ColourPoint (1.0, colour2)); | |||
| } | |||
| ColourGradient::~ColourGradient() | |||
| { | |||
| } | |||
| bool ColourGradient::operator== (const ColourGradient& other) const noexcept | |||
| { | |||
| return point1 == other.point1 && point2 == other.point2 | |||
| && isRadial == other.isRadial | |||
| && colours == other.colours; | |||
| } | |||
| bool ColourGradient::operator!= (const ColourGradient& other) const noexcept | |||
| { | |||
| return ! operator== (other); | |||
| } | |||
| //============================================================================== | |||
| void ColourGradient::clearColours() | |||
| { | |||
| colours.clear(); | |||
| } | |||
| int ColourGradient::addColour (const double proportionAlongGradient, Colour colour) | |||
| { | |||
| // must be within the two end-points | |||
| jassert (proportionAlongGradient >= 0 && proportionAlongGradient <= 1.0); | |||
| if (proportionAlongGradient <= 0) | |||
| { | |||
| colours.set (0, ColourPoint (0.0, colour)); | |||
| return 0; | |||
| } | |||
| const double pos = jmin (1.0, proportionAlongGradient); | |||
| int i; | |||
| for (i = 0; i < colours.size(); ++i) | |||
| if (colours.getReference(i).position > pos) | |||
| break; | |||
| colours.insert (i, ColourPoint (pos, colour)); | |||
| return i; | |||
| } | |||
| void ColourGradient::removeColour (int index) | |||
| { | |||
| jassert (index > 0 && index < colours.size() - 1); | |||
| colours.remove (index); | |||
| } | |||
| void ColourGradient::multiplyOpacity (const float multiplier) noexcept | |||
| { | |||
| for (int i = 0; i < colours.size(); ++i) | |||
| { | |||
| Colour& c = colours.getReference(i).colour; | |||
| c = c.withMultipliedAlpha (multiplier); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| int ColourGradient::getNumColours() const noexcept | |||
| { | |||
| return colours.size(); | |||
| } | |||
| double ColourGradient::getColourPosition (const int index) const noexcept | |||
| { | |||
| if (isPositiveAndBelow (index, colours.size())) | |||
| return colours.getReference (index).position; | |||
| return 0; | |||
| } | |||
| Colour ColourGradient::getColour (const int index) const noexcept | |||
| { | |||
| if (isPositiveAndBelow (index, colours.size())) | |||
| return colours.getReference (index).colour; | |||
| return Colour(); | |||
| } | |||
| void ColourGradient::setColour (int index, Colour newColour) noexcept | |||
| { | |||
| if (isPositiveAndBelow (index, colours.size())) | |||
| colours.getReference (index).colour = newColour; | |||
| } | |||
| Colour ColourGradient::getColourAtPosition (const double position) const noexcept | |||
| { | |||
| jassert (colours.getReference(0).position == 0.0); // the first colour specified has to go at position 0 | |||
| if (position <= 0 || colours.size() <= 1) | |||
| return colours.getReference(0).colour; | |||
| int i = colours.size() - 1; | |||
| while (position < colours.getReference(i).position) | |||
| --i; | |||
| auto& p1 = colours.getReference (i); | |||
| if (i >= colours.size() - 1) | |||
| return p1.colour; | |||
| auto& p2 = colours.getReference (i + 1); | |||
| return p1.colour.interpolatedWith (p2.colour, (float) ((position - p1.position) / (p2.position - p1.position))); | |||
| } | |||
| //============================================================================== | |||
| void ColourGradient::createLookupTable (PixelARGB* const lookupTable, const int numEntries) const noexcept | |||
| { | |||
| JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED // Trying to use this object without setting its coordinates? | |||
| jassert (colours.size() >= 2); | |||
| jassert (numEntries > 0); | |||
| jassert (colours.getReference(0).position == 0.0); // The first colour specified has to go at position 0 | |||
| PixelARGB pix1 (colours.getReference (0).colour.getPixelARGB()); | |||
| int index = 0; | |||
| for (int j = 1; j < colours.size(); ++j) | |||
| { | |||
| const ColourPoint& p = colours.getReference (j); | |||
| const int numToDo = roundToInt (p.position * (numEntries - 1)) - index; | |||
| const PixelARGB pix2 (p.colour.getPixelARGB()); | |||
| for (int i = 0; i < numToDo; ++i) | |||
| { | |||
| jassert (index >= 0 && index < numEntries); | |||
| lookupTable[index] = pix1; | |||
| lookupTable[index].tween (pix2, (uint32) ((i << 8) / numToDo)); | |||
| ++index; | |||
| } | |||
| pix1 = pix2; | |||
| } | |||
| while (index < numEntries) | |||
| lookupTable [index++] = pix1; | |||
| } | |||
| int ColourGradient::createLookupTable (const AffineTransform& transform, HeapBlock<PixelARGB>& lookupTable) const | |||
| { | |||
| JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED // Trying to use this object without setting its coordinates? | |||
| jassert (colours.size() >= 2); | |||
| const int numEntries = jlimit (1, jmax (1, (colours.size() - 1) << 8), | |||
| 3 * (int) point1.transformedBy (transform) | |||
| .getDistanceFrom (point2.transformedBy (transform))); | |||
| lookupTable.malloc ((size_t) numEntries); | |||
| createLookupTable (lookupTable, numEntries); | |||
| return numEntries; | |||
| } | |||
| bool ColourGradient::isOpaque() const noexcept | |||
| { | |||
| for (int i = 0; i < colours.size(); ++i) | |||
| if (! colours.getReference(i).colour.isOpaque()) | |||
| return false; | |||
| return true; | |||
| } | |||
| bool ColourGradient::isInvisible() const noexcept | |||
| { | |||
| for (int i = 0; i < colours.size(); ++i) | |||
| if (! colours.getReference(i).colour.isTransparent()) | |||
| return false; | |||
| return true; | |||
| } | |||
| bool ColourGradient::ColourPoint::operator== (const ColourPoint& other) const noexcept | |||
| { | |||
| return position == other.position && colour == other.colour; | |||
| } | |||
| bool ColourGradient::ColourPoint::operator!= (const ColourPoint& other) const noexcept | |||
| { | |||
| return position != other.position || colour != other.colour; | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,202 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Describes the layout and colours that should be used to paint a colour gradient. | |||
| @see Graphics::setGradientFill | |||
| */ | |||
| class JUCE_API ColourGradient | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a gradient object. | |||
| (x1, y1) is the location to draw with colour1. Likewise (x2, y2) is where | |||
| colour2 should be. In between them there's a gradient. | |||
| If isRadial is true, the colours form a circular gradient with (x1, y1) at | |||
| its centre. | |||
| The alpha transparencies of the colours are used, so note that | |||
| if you blend from transparent to a solid colour, the RGB of the transparent | |||
| colour will become visible in parts of the gradient. e.g. blending | |||
| from Colour::transparentBlack to Colours::white will produce a | |||
| muddy grey colour midway, but Colour::transparentWhite to Colours::white | |||
| will be white all the way across. | |||
| @see ColourGradient | |||
| */ | |||
| ColourGradient (Colour colour1, float x1, float y1, | |||
| Colour colour2, float x2, float y2, | |||
| bool isRadial); | |||
| /** Creates a gradient object. | |||
| point1 is the location to draw with colour1. Likewise point2 is where | |||
| colour2 should be. In between them there's a gradient. | |||
| If isRadial is true, the colours form a circular gradient with point1 at | |||
| its centre. | |||
| The alpha transparencies of the colours are used, so note that | |||
| if you blend from transparent to a solid colour, the RGB of the transparent | |||
| colour will become visible in parts of the gradient. e.g. blending | |||
| from Colour::transparentBlack to Colours::white will produce a | |||
| muddy grey colour midway, but Colour::transparentWhite to Colours::white | |||
| will be white all the way across. | |||
| @see ColourGradient | |||
| */ | |||
| ColourGradient (Colour colour1, Point<float> point1, | |||
| Colour colour2, Point<float> point2, | |||
| bool isRadial); | |||
| /** Creates an uninitialised gradient. | |||
| If you use this constructor instead of the other one, be sure to set all the | |||
| object's public member variables before using it! | |||
| */ | |||
| ColourGradient() noexcept; | |||
| /** Destructor */ | |||
| ~ColourGradient(); | |||
| //============================================================================== | |||
| /** Removes any colours that have been added. | |||
| This will also remove any start and end colours, so the gradient won't work. You'll | |||
| need to add more colours with addColour(). | |||
| */ | |||
| void clearColours(); | |||
| /** Adds a colour at a point along the length of the gradient. | |||
| This allows the gradient to go through a spectrum of colours, instead of just a | |||
| start and end colour. | |||
| @param proportionAlongGradient a value between 0 and 1.0, which is the proportion | |||
| of the distance along the line between the two points | |||
| at which the colour should occur. | |||
| @param colour the colour that should be used at this point | |||
| @returns the index at which the new point was added | |||
| */ | |||
| int addColour (double proportionAlongGradient, | |||
| Colour colour); | |||
| /** Removes one of the colours from the gradient. */ | |||
| void removeColour (int index); | |||
| /** Multiplies the alpha value of all the colours by the given scale factor */ | |||
| void multiplyOpacity (float multiplier) noexcept; | |||
| //============================================================================== | |||
| /** Returns the number of colour-stops that have been added. */ | |||
| int getNumColours() const noexcept; | |||
| /** Returns the position along the length of the gradient of the colour with this index. | |||
| The index is from 0 to getNumColours() - 1. The return value will be between 0.0 and 1.0 | |||
| */ | |||
| double getColourPosition (int index) const noexcept; | |||
| /** Returns the colour that was added with a given index. | |||
| The index is from 0 to getNumColours() - 1. | |||
| */ | |||
| Colour getColour (int index) const noexcept; | |||
| /** Changes the colour at a given index. | |||
| The index is from 0 to getNumColours() - 1. | |||
| */ | |||
| void setColour (int index, Colour newColour) noexcept; | |||
| /** Returns the an interpolated colour at any position along the gradient. | |||
| @param position the position along the gradient, between 0 and 1 | |||
| */ | |||
| Colour getColourAtPosition (double position) const noexcept; | |||
| //============================================================================== | |||
| /** Creates a set of interpolated premultiplied ARGB values. | |||
| This will resize the HeapBlock, fill it with the colours, and will return the number of | |||
| colours that it added. | |||
| When calling this, the ColourGradient must have at least 2 colour stops specified. | |||
| */ | |||
| int createLookupTable (const AffineTransform& transform, HeapBlock<PixelARGB>& resultLookupTable) const; | |||
| /** Creates a set of interpolated premultiplied ARGB values. | |||
| This will fill an array of a user-specified size with the gradient, interpolating to fit. | |||
| The numEntries argument specifies the size of the array, and this size must be greater than zero. | |||
| When calling this, the ColourGradient must have at least 2 colour stops specified. | |||
| */ | |||
| void createLookupTable (PixelARGB* resultLookupTable, int numEntries) const noexcept; | |||
| /** Returns true if all colours are opaque. */ | |||
| bool isOpaque() const noexcept; | |||
| /** Returns true if all colours are completely transparent. */ | |||
| bool isInvisible() const noexcept; | |||
| //============================================================================== | |||
| Point<float> point1, point2; | |||
| /** If true, the gradient should be filled circularly, centred around | |||
| point1, with point2 defining a point on the circumference. | |||
| If false, the gradient is linear between the two points. | |||
| */ | |||
| bool isRadial; | |||
| bool operator== (const ColourGradient&) const noexcept; | |||
| bool operator!= (const ColourGradient&) const noexcept; | |||
| private: | |||
| //============================================================================== | |||
| struct ColourPoint | |||
| { | |||
| ColourPoint() noexcept {} | |||
| ColourPoint (const double pos, Colour col) noexcept | |||
| : position (pos), colour (col) | |||
| {} | |||
| bool operator== (const ColourPoint&) const noexcept; | |||
| bool operator!= (const ColourPoint&) const noexcept; | |||
| double position; | |||
| Colour colour; | |||
| }; | |||
| Array<ColourPoint> colours; | |||
| JUCE_LEAK_DETECTOR (ColourGradient) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,335 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| const Colour Colours::transparentBlack (0); | |||
| const Colour Colours::transparentWhite (0x00ffffff); | |||
| const Colour Colours::aliceblue (0xfff0f8ff); | |||
| const Colour Colours::antiquewhite (0xfffaebd7); | |||
| const Colour Colours::aqua (0xff00ffff); | |||
| const Colour Colours::aquamarine (0xff7fffd4); | |||
| const Colour Colours::azure (0xfff0ffff); | |||
| const Colour Colours::beige (0xfff5f5dc); | |||
| const Colour Colours::bisque (0xffffe4c4); | |||
| const Colour Colours::black (0xff000000); | |||
| const Colour Colours::blanchedalmond (0xffffebcd); | |||
| const Colour Colours::blue (0xff0000ff); | |||
| const Colour Colours::blueviolet (0xff8a2be2); | |||
| const Colour Colours::brown (0xffa52a2a); | |||
| const Colour Colours::burlywood (0xffdeb887); | |||
| const Colour Colours::cadetblue (0xff5f9ea0); | |||
| const Colour Colours::chartreuse (0xff7fff00); | |||
| const Colour Colours::chocolate (0xffd2691e); | |||
| const Colour Colours::coral (0xffff7f50); | |||
| const Colour Colours::cornflowerblue (0xff6495ed); | |||
| const Colour Colours::cornsilk (0xfffff8dc); | |||
| const Colour Colours::crimson (0xffdc143c); | |||
| const Colour Colours::cyan (0xff00ffff); | |||
| const Colour Colours::darkblue (0xff00008b); | |||
| const Colour Colours::darkcyan (0xff008b8b); | |||
| const Colour Colours::darkgoldenrod (0xffb8860b); | |||
| const Colour Colours::darkgrey (0xff555555); | |||
| const Colour Colours::darkgreen (0xff006400); | |||
| const Colour Colours::darkkhaki (0xffbdb76b); | |||
| const Colour Colours::darkmagenta (0xff8b008b); | |||
| const Colour Colours::darkolivegreen (0xff556b2f); | |||
| const Colour Colours::darkorange (0xffff8c00); | |||
| const Colour Colours::darkorchid (0xff9932cc); | |||
| const Colour Colours::darkred (0xff8b0000); | |||
| const Colour Colours::darksalmon (0xffe9967a); | |||
| const Colour Colours::darkseagreen (0xff8fbc8f); | |||
| const Colour Colours::darkslateblue (0xff483d8b); | |||
| const Colour Colours::darkslategrey (0xff2f4f4f); | |||
| const Colour Colours::darkturquoise (0xff00ced1); | |||
| const Colour Colours::darkviolet (0xff9400d3); | |||
| const Colour Colours::deeppink (0xffff1493); | |||
| const Colour Colours::deepskyblue (0xff00bfff); | |||
| const Colour Colours::dimgrey (0xff696969); | |||
| const Colour Colours::dodgerblue (0xff1e90ff); | |||
| const Colour Colours::firebrick (0xffb22222); | |||
| const Colour Colours::floralwhite (0xfffffaf0); | |||
| const Colour Colours::forestgreen (0xff228b22); | |||
| const Colour Colours::fuchsia (0xffff00ff); | |||
| const Colour Colours::gainsboro (0xffdcdcdc); | |||
| const Colour Colours::ghostwhite (0xfff8f8ff); | |||
| const Colour Colours::gold (0xffffd700); | |||
| const Colour Colours::goldenrod (0xffdaa520); | |||
| const Colour Colours::grey (0xff808080); | |||
| const Colour Colours::green (0xff008000); | |||
| const Colour Colours::greenyellow (0xffadff2f); | |||
| const Colour Colours::honeydew (0xfff0fff0); | |||
| const Colour Colours::hotpink (0xffff69b4); | |||
| const Colour Colours::indianred (0xffcd5c5c); | |||
| const Colour Colours::indigo (0xff4b0082); | |||
| const Colour Colours::ivory (0xfffffff0); | |||
| const Colour Colours::khaki (0xfff0e68c); | |||
| const Colour Colours::lavender (0xffe6e6fa); | |||
| const Colour Colours::lavenderblush (0xfffff0f5); | |||
| const Colour Colours::lawngreen (0xff7cfc00); | |||
| const Colour Colours::lemonchiffon (0xfffffacd); | |||
| const Colour Colours::lightblue (0xffadd8e6); | |||
| const Colour Colours::lightcoral (0xfff08080); | |||
| const Colour Colours::lightcyan (0xffe0ffff); | |||
| const Colour Colours::lightgoldenrodyellow (0xfffafad2); | |||
| const Colour Colours::lightgreen (0xff90ee90); | |||
| const Colour Colours::lightgrey (0xffd3d3d3); | |||
| const Colour Colours::lightpink (0xffffb6c1); | |||
| const Colour Colours::lightsalmon (0xffffa07a); | |||
| const Colour Colours::lightseagreen (0xff20b2aa); | |||
| const Colour Colours::lightskyblue (0xff87cefa); | |||
| const Colour Colours::lightslategrey (0xff778899); | |||
| const Colour Colours::lightsteelblue (0xffb0c4de); | |||
| const Colour Colours::lightyellow (0xffffffe0); | |||
| const Colour Colours::lime (0xff00ff00); | |||
| const Colour Colours::limegreen (0xff32cd32); | |||
| const Colour Colours::linen (0xfffaf0e6); | |||
| const Colour Colours::magenta (0xffff00ff); | |||
| const Colour Colours::maroon (0xff800000); | |||
| const Colour Colours::mediumaquamarine (0xff66cdaa); | |||
| const Colour Colours::mediumblue (0xff0000cd); | |||
| const Colour Colours::mediumorchid (0xffba55d3); | |||
| const Colour Colours::mediumpurple (0xff9370db); | |||
| const Colour Colours::mediumseagreen (0xff3cb371); | |||
| const Colour Colours::mediumslateblue (0xff7b68ee); | |||
| const Colour Colours::mediumspringgreen (0xff00fa9a); | |||
| const Colour Colours::mediumturquoise (0xff48d1cc); | |||
| const Colour Colours::mediumvioletred (0xffc71585); | |||
| const Colour Colours::midnightblue (0xff191970); | |||
| const Colour Colours::mintcream (0xfff5fffa); | |||
| const Colour Colours::mistyrose (0xffffe4e1); | |||
| const Colour Colours::moccasin (0xffffe4b5); | |||
| const Colour Colours::navajowhite (0xffffdead); | |||
| const Colour Colours::navy (0xff000080); | |||
| const Colour Colours::oldlace (0xfffdf5e6); | |||
| const Colour Colours::olive (0xff808000); | |||
| const Colour Colours::olivedrab (0xff6b8e23); | |||
| const Colour Colours::orange (0xffffa500); | |||
| const Colour Colours::orangered (0xffff4500); | |||
| const Colour Colours::orchid (0xffda70d6); | |||
| const Colour Colours::palegoldenrod (0xffeee8aa); | |||
| const Colour Colours::palegreen (0xff98fb98); | |||
| const Colour Colours::paleturquoise (0xffafeeee); | |||
| const Colour Colours::palevioletred (0xffdb7093); | |||
| const Colour Colours::papayawhip (0xffffefd5); | |||
| const Colour Colours::peachpuff (0xffffdab9); | |||
| const Colour Colours::peru (0xffcd853f); | |||
| const Colour Colours::pink (0xffffc0cb); | |||
| const Colour Colours::plum (0xffdda0dd); | |||
| const Colour Colours::powderblue (0xffb0e0e6); | |||
| const Colour Colours::purple (0xff800080); | |||
| const Colour Colours::rebeccapurple (0xff663399); | |||
| const Colour Colours::red (0xffff0000); | |||
| const Colour Colours::rosybrown (0xffbc8f8f); | |||
| const Colour Colours::royalblue (0xff4169e1); | |||
| const Colour Colours::saddlebrown (0xff8b4513); | |||
| const Colour Colours::salmon (0xfffa8072); | |||
| const Colour Colours::sandybrown (0xfff4a460); | |||
| const Colour Colours::seagreen (0xff2e8b57); | |||
| const Colour Colours::seashell (0xfffff5ee); | |||
| const Colour Colours::sienna (0xffa0522d); | |||
| const Colour Colours::silver (0xffc0c0c0); | |||
| const Colour Colours::skyblue (0xff87ceeb); | |||
| const Colour Colours::slateblue (0xff6a5acd); | |||
| const Colour Colours::slategrey (0xff708090); | |||
| const Colour Colours::snow (0xfffffafa); | |||
| const Colour Colours::springgreen (0xff00ff7f); | |||
| const Colour Colours::steelblue (0xff4682b4); | |||
| const Colour Colours::tan (0xffd2b48c); | |||
| const Colour Colours::teal (0xff008080); | |||
| const Colour Colours::thistle (0xffd8bfd8); | |||
| const Colour Colours::tomato (0xffff6347); | |||
| const Colour Colours::turquoise (0xff40e0d0); | |||
| const Colour Colours::violet (0xffee82ee); | |||
| const Colour Colours::wheat (0xfff5deb3); | |||
| const Colour Colours::white (0xffffffff); | |||
| const Colour Colours::whitesmoke (0xfff5f5f5); | |||
| const Colour Colours::yellow (0xffffff00); | |||
| const Colour Colours::yellowgreen (0xff9acd32); | |||
| //============================================================================== | |||
| Colour Colours::findColourForName (const String& colourName, | |||
| Colour defaultColour) | |||
| { | |||
| static const uint32 presets[] = | |||
| { | |||
| // (first value is the string's hashcode, second is ARGB) | |||
| 0x05978fff, 0xff000000, /* black */ | |||
| 0x06bdcc29, 0xffffffff, /* white */ | |||
| 0x002e305a, 0xff0000ff, /* blue */ | |||
| 0x00308adf, 0xff808080, /* grey */ | |||
| 0x05e0cf03, 0xff008000, /* green */ | |||
| 0x0001b891, 0xffff0000, /* red */ | |||
| 0xd43c6474, 0xffffff00, /* yellow */ | |||
| 0x620886da, 0xfff0f8ff, /* aliceblue */ | |||
| 0x20a2676a, 0xfffaebd7, /* antiquewhite */ | |||
| 0x002dcebc, 0xff00ffff, /* aqua */ | |||
| 0x46bb5f7e, 0xff7fffd4, /* aquamarine */ | |||
| 0x0590228f, 0xfff0ffff, /* azure */ | |||
| 0x05947fe4, 0xfff5f5dc, /* beige */ | |||
| 0xad388e35, 0xffffe4c4, /* bisque */ | |||
| 0x00674f7e, 0xffffebcd, /* blanchedalmond */ | |||
| 0x39129959, 0xff8a2be2, /* blueviolet */ | |||
| 0x059a8136, 0xffa52a2a, /* brown */ | |||
| 0x89cea8f9, 0xffdeb887, /* burlywood */ | |||
| 0x0fa260cf, 0xff5f9ea0, /* cadetblue */ | |||
| 0x6b748956, 0xff7fff00, /* chartreuse */ | |||
| 0x2903623c, 0xffd2691e, /* chocolate */ | |||
| 0x05a74431, 0xffff7f50, /* coral */ | |||
| 0x618d42dd, 0xff6495ed, /* cornflowerblue */ | |||
| 0xe4b479fd, 0xfffff8dc, /* cornsilk */ | |||
| 0x3d8c4edf, 0xffdc143c, /* crimson */ | |||
| 0x002ed323, 0xff00ffff, /* cyan */ | |||
| 0x67cc74d0, 0xff00008b, /* darkblue */ | |||
| 0x67cd1799, 0xff008b8b, /* darkcyan */ | |||
| 0x31bbd168, 0xffb8860b, /* darkgoldenrod */ | |||
| 0x67cecf55, 0xff555555, /* darkgrey */ | |||
| 0x920b194d, 0xff006400, /* darkgreen */ | |||
| 0x923edd4c, 0xffbdb76b, /* darkkhaki */ | |||
| 0x5c293873, 0xff8b008b, /* darkmagenta */ | |||
| 0x6b6671fe, 0xff556b2f, /* darkolivegreen */ | |||
| 0xbcfd2524, 0xffff8c00, /* darkorange */ | |||
| 0xbcfdf799, 0xff9932cc, /* darkorchid */ | |||
| 0x55ee0d5b, 0xff8b0000, /* darkred */ | |||
| 0xc2e5f564, 0xffe9967a, /* darksalmon */ | |||
| 0x61be858a, 0xff8fbc8f, /* darkseagreen */ | |||
| 0xc2b0f2bd, 0xff483d8b, /* darkslateblue */ | |||
| 0xc2b34d42, 0xff2f4f4f, /* darkslategrey */ | |||
| 0x7cf2b06b, 0xff00ced1, /* darkturquoise */ | |||
| 0xc8769375, 0xff9400d3, /* darkviolet */ | |||
| 0x25832862, 0xffff1493, /* deeppink */ | |||
| 0xfcad568f, 0xff00bfff, /* deepskyblue */ | |||
| 0x634c8b67, 0xff696969, /* dimgrey */ | |||
| 0x45c1ce55, 0xff1e90ff, /* dodgerblue */ | |||
| 0xef19e3cb, 0xffb22222, /* firebrick */ | |||
| 0xb852b195, 0xfffffaf0, /* floralwhite */ | |||
| 0xd086fd06, 0xff228b22, /* forestgreen */ | |||
| 0xe106b6d7, 0xffff00ff, /* fuchsia */ | |||
| 0x7880d61e, 0xffdcdcdc, /* gainsboro */ | |||
| 0x2018a2fa, 0xfff8f8ff, /* ghostwhite */ | |||
| 0x00308060, 0xffffd700, /* gold */ | |||
| 0xb3b3bc1e, 0xffdaa520, /* goldenrod */ | |||
| 0xbab8a537, 0xffadff2f, /* greenyellow */ | |||
| 0xe4cacafb, 0xfff0fff0, /* honeydew */ | |||
| 0x41892743, 0xffff69b4, /* hotpink */ | |||
| 0xd5796f1a, 0xffcd5c5c, /* indianred */ | |||
| 0xb969fed2, 0xff4b0082, /* indigo */ | |||
| 0x05fef6a9, 0xfffffff0, /* ivory */ | |||
| 0x06149302, 0xfff0e68c, /* khaki */ | |||
| 0xad5a05c7, 0xffe6e6fa, /* lavender */ | |||
| 0x7c4d5b99, 0xfffff0f5, /* lavenderblush */ | |||
| 0x41cc4377, 0xff7cfc00, /* lawngreen */ | |||
| 0x195756f0, 0xfffffacd, /* lemonchiffon */ | |||
| 0x28e4ea70, 0xffadd8e6, /* lightblue */ | |||
| 0xf3c7ccdb, 0xfff08080, /* lightcoral */ | |||
| 0x28e58d39, 0xffe0ffff, /* lightcyan */ | |||
| 0x21234e3c, 0xfffafad2, /* lightgoldenrodyellow */ | |||
| 0xf40157ad, 0xff90ee90, /* lightgreen */ | |||
| 0x28e744f5, 0xffd3d3d3, /* lightgrey */ | |||
| 0x28eb3b8c, 0xffffb6c1, /* lightpink */ | |||
| 0x9fb78304, 0xffffa07a, /* lightsalmon */ | |||
| 0x50632b2a, 0xff20b2aa, /* lightseagreen */ | |||
| 0x68fb7b25, 0xff87cefa, /* lightskyblue */ | |||
| 0xa8a35ba2, 0xff778899, /* lightslategrey */ | |||
| 0xa20d484f, 0xffb0c4de, /* lightsteelblue */ | |||
| 0xaa2cf10a, 0xffffffe0, /* lightyellow */ | |||
| 0x0032afd5, 0xff00ff00, /* lime */ | |||
| 0x607bbc4e, 0xff32cd32, /* limegreen */ | |||
| 0x06234efa, 0xfffaf0e6, /* linen */ | |||
| 0x316858a9, 0xffff00ff, /* magenta */ | |||
| 0xbf8ca470, 0xff800000, /* maroon */ | |||
| 0xbd58e0b3, 0xff66cdaa, /* mediumaquamarine */ | |||
| 0x967dfd4f, 0xff0000cd, /* mediumblue */ | |||
| 0x056f5c58, 0xffba55d3, /* mediumorchid */ | |||
| 0x07556b71, 0xff9370db, /* mediumpurple */ | |||
| 0x5369b689, 0xff3cb371, /* mediumseagreen */ | |||
| 0x066be19e, 0xff7b68ee, /* mediumslateblue */ | |||
| 0x3256b281, 0xff00fa9a, /* mediumspringgreen */ | |||
| 0xc0ad9f4c, 0xff48d1cc, /* mediumturquoise */ | |||
| 0x628e63dd, 0xffc71585, /* mediumvioletred */ | |||
| 0x168eb32a, 0xff191970, /* midnightblue */ | |||
| 0x4306b960, 0xfff5fffa, /* mintcream */ | |||
| 0x4cbc0e6b, 0xffffe4e1, /* mistyrose */ | |||
| 0xd9447d59, 0xffffe4b5, /* moccasin */ | |||
| 0xe97218a6, 0xffffdead, /* navajowhite */ | |||
| 0x00337bb6, 0xff000080, /* navy */ | |||
| 0xadd2d33e, 0xfffdf5e6, /* oldlace */ | |||
| 0x064ee1db, 0xff808000, /* olive */ | |||
| 0x9e33a98a, 0xff6b8e23, /* olivedrab */ | |||
| 0xc3de262e, 0xffffa500, /* orange */ | |||
| 0x58bebba3, 0xffff4500, /* orangered */ | |||
| 0xc3def8a3, 0xffda70d6, /* orchid */ | |||
| 0x28cb4834, 0xffeee8aa, /* palegoldenrod */ | |||
| 0x3d9dd619, 0xff98fb98, /* palegreen */ | |||
| 0x74022737, 0xffafeeee, /* paleturquoise */ | |||
| 0x15e2ebc8, 0xffdb7093, /* palevioletred */ | |||
| 0x5fd898e2, 0xffffefd5, /* papayawhip */ | |||
| 0x93e1b776, 0xffffdab9, /* peachpuff */ | |||
| 0x003472f8, 0xffcd853f, /* peru */ | |||
| 0x00348176, 0xffffc0cb, /* pink */ | |||
| 0x00348d94, 0xffdda0dd, /* plum */ | |||
| 0xd036be93, 0xffb0e0e6, /* powderblue */ | |||
| 0xc5c507bc, 0xff800080, /* purple */ | |||
| 0xf381f607, 0xff663399, /* rebeccapurple */ | |||
| 0xa89d65b3, 0xffbc8f8f, /* rosybrown */ | |||
| 0xbd9413e1, 0xff4169e1, /* royalblue */ | |||
| 0xf456044f, 0xff8b4513, /* saddlebrown */ | |||
| 0xc9c6f66e, 0xfffa8072, /* salmon */ | |||
| 0x0bb131e1, 0xfff4a460, /* sandybrown */ | |||
| 0x34636c14, 0xff2e8b57, /* seagreen */ | |||
| 0x3507fb41, 0xfffff5ee, /* seashell */ | |||
| 0xca348772, 0xffa0522d, /* sienna */ | |||
| 0xca37d30d, 0xffc0c0c0, /* silver */ | |||
| 0x80da74fb, 0xff87ceeb, /* skyblue */ | |||
| 0x44a8dd73, 0xff6a5acd, /* slateblue */ | |||
| 0x44ab37f8, 0xff708090, /* slategrey */ | |||
| 0x0035f183, 0xfffffafa, /* snow */ | |||
| 0xd5440d16, 0xff00ff7f, /* springgreen */ | |||
| 0x3e1524a5, 0xff4682b4, /* steelblue */ | |||
| 0x0001bfa1, 0xffd2b48c, /* tan */ | |||
| 0x0036425c, 0xff008080, /* teal */ | |||
| 0xafc8858f, 0xffd8bfd8, /* thistle */ | |||
| 0xcc41600a, 0xffff6347, /* tomato */ | |||
| 0xfeea9b21, 0xff40e0d0, /* turquoise */ | |||
| 0xcf57947f, 0xffee82ee, /* violet */ | |||
| 0x06bdbae7, 0xfff5deb3, /* wheat */ | |||
| 0x10802ee6, 0xfff5f5f5, /* whitesmoke */ | |||
| 0xe1b5130f, 0xff9acd32 /* yellowgreen */ | |||
| }; | |||
| const uint32 hash = (uint32) colourName.trim().toLowerCase().hashCode(); | |||
| for (int i = 0; i < numElementsInArray (presets); i += 2) | |||
| if (presets [i] == hash) | |||
| return Colour (presets [i + 1]); | |||
| return defaultColour; | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,109 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Contains a set of predefined named colours (mostly standard HTML colours) | |||
| @see Colour, Colours::greyLevel | |||
| */ | |||
| class Colours | |||
| { | |||
| public: | |||
| static JUCE_API const Colour | |||
| //============================================================================== | |||
| transparentBlack, /**< ARGB = 0x00000000 */ | |||
| transparentWhite, /**< ARGB = 0x00ffffff */ | |||
| //============================================================================== | |||
| black, /**< ARGB = 0xff000000 */ | |||
| white, /**< ARGB = 0xffffffff */ | |||
| blue, /**< ARGB = 0xff0000ff */ | |||
| grey, /**< ARGB = 0xff808080 */ | |||
| green, /**< ARGB = 0xff008000 */ | |||
| red, /**< ARGB = 0xffff0000 */ | |||
| yellow, /**< ARGB = 0xffffff00 */ | |||
| //============================================================================== | |||
| aliceblue, antiquewhite, aqua, aquamarine, | |||
| azure, beige, bisque, blanchedalmond, | |||
| blueviolet, brown, burlywood, cadetblue, | |||
| chartreuse, chocolate, coral, cornflowerblue, | |||
| cornsilk, crimson, cyan, darkblue, | |||
| darkcyan, darkgoldenrod, darkgrey, darkgreen, | |||
| darkkhaki, darkmagenta, darkolivegreen, darkorange, | |||
| darkorchid, darkred, darksalmon, darkseagreen, | |||
| darkslateblue, darkslategrey, darkturquoise, darkviolet, | |||
| deeppink, deepskyblue, dimgrey, dodgerblue, | |||
| firebrick, floralwhite, forestgreen, fuchsia, | |||
| gainsboro, ghostwhite, gold, goldenrod, | |||
| greenyellow, honeydew, hotpink, indianred, | |||
| indigo, ivory, khaki, lavender, | |||
| lavenderblush, lawngreen, lemonchiffon, lightblue, | |||
| lightcoral, lightcyan, lightgoldenrodyellow, lightgreen, | |||
| lightgrey, lightpink, lightsalmon, lightseagreen, | |||
| lightskyblue, lightslategrey, lightsteelblue, lightyellow, | |||
| lime, limegreen, linen, magenta, | |||
| maroon, mediumaquamarine, mediumblue, mediumorchid, | |||
| mediumpurple, mediumseagreen, mediumslateblue, mediumspringgreen, | |||
| mediumturquoise, mediumvioletred, midnightblue, mintcream, | |||
| mistyrose, moccasin, navajowhite, navy, | |||
| oldlace, olive, olivedrab, orange, | |||
| orangered, orchid, palegoldenrod, palegreen, | |||
| paleturquoise, palevioletred, papayawhip, peachpuff, | |||
| peru, pink, plum, powderblue, | |||
| purple, rebeccapurple, rosybrown, royalblue, | |||
| saddlebrown, salmon, sandybrown, seagreen, | |||
| seashell, sienna, silver, skyblue, | |||
| slateblue, slategrey, snow, springgreen, | |||
| steelblue, tan, teal, thistle, | |||
| tomato, turquoise, violet, wheat, | |||
| whitesmoke, yellowgreen; | |||
| /** Attempts to look up a string in the list of known colour names, and return | |||
| the appropriate colour. | |||
| A non-case-sensitive search is made of the list of predefined colours, and | |||
| if a match is found, that colour is returned. If no match is found, the | |||
| colour passed in as the defaultColour parameter is returned. | |||
| */ | |||
| static JUCE_API Colour findColourForName (const String& colourName, | |||
| Colour defaultColour); | |||
| private: | |||
| //============================================================================== | |||
| // this isn't a class you should ever instantiate - it's just here for the | |||
| // static values in it. | |||
| Colours(); | |||
| JUCE_DECLARE_NON_COPYABLE (Colours) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,153 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| FillType::FillType() noexcept | |||
| : colour (0xff000000) | |||
| { | |||
| } | |||
| FillType::FillType (Colour c) noexcept | |||
| : colour (c) | |||
| { | |||
| } | |||
| FillType::FillType (const ColourGradient& gradient_) | |||
| : colour (0xff000000), gradient (new ColourGradient (gradient_)) | |||
| { | |||
| } | |||
| FillType::FillType (const Image& image_, const AffineTransform& transform_) noexcept | |||
| : colour (0xff000000), image (image_), transform (transform_) | |||
| { | |||
| } | |||
| FillType::FillType (const FillType& other) | |||
| : colour (other.colour), | |||
| gradient (other.gradient.createCopy()), | |||
| image (other.image), | |||
| transform (other.transform) | |||
| { | |||
| } | |||
| FillType& FillType::operator= (const FillType& other) | |||
| { | |||
| if (this != &other) | |||
| { | |||
| colour = other.colour; | |||
| gradient = other.gradient.createCopy(); | |||
| image = other.image; | |||
| transform = other.transform; | |||
| } | |||
| return *this; | |||
| } | |||
| FillType::FillType (FillType&& other) noexcept | |||
| : colour (other.colour), | |||
| gradient (other.gradient.release()), | |||
| image (static_cast<Image&&> (other.image)), | |||
| transform (other.transform) | |||
| { | |||
| } | |||
| FillType& FillType::operator= (FillType&& other) noexcept | |||
| { | |||
| jassert (this != &other); // hopefully the compiler should make this situation impossible! | |||
| colour = other.colour; | |||
| gradient = other.gradient.release(); | |||
| image = static_cast<Image&&> (other.image); | |||
| transform = other.transform; | |||
| return *this; | |||
| } | |||
| FillType::~FillType() noexcept | |||
| { | |||
| } | |||
| bool FillType::operator== (const FillType& other) const | |||
| { | |||
| return colour == other.colour && image == other.image | |||
| && transform == other.transform | |||
| && (gradient == other.gradient | |||
| || (gradient != nullptr && other.gradient != nullptr && *gradient == *other.gradient)); | |||
| } | |||
| bool FillType::operator!= (const FillType& other) const | |||
| { | |||
| return ! operator== (other); | |||
| } | |||
| void FillType::setColour (Colour newColour) noexcept | |||
| { | |||
| gradient = nullptr; | |||
| image = Image(); | |||
| colour = newColour; | |||
| } | |||
| void FillType::setGradient (const ColourGradient& newGradient) | |||
| { | |||
| if (gradient != nullptr) | |||
| { | |||
| *gradient = newGradient; | |||
| } | |||
| else | |||
| { | |||
| image = Image(); | |||
| gradient = new ColourGradient (newGradient); | |||
| colour = Colours::black; | |||
| } | |||
| } | |||
| void FillType::setTiledImage (const Image& image_, const AffineTransform& transform_) noexcept | |||
| { | |||
| gradient = nullptr; | |||
| image = image_; | |||
| transform = transform_; | |||
| colour = Colours::black; | |||
| } | |||
| void FillType::setOpacity (const float newOpacity) noexcept | |||
| { | |||
| colour = colour.withAlpha (newOpacity); | |||
| } | |||
| bool FillType::isInvisible() const noexcept | |||
| { | |||
| return colour.isTransparent() || (gradient != nullptr && gradient->isInvisible()); | |||
| } | |||
| FillType FillType::transformed (const AffineTransform& t) const | |||
| { | |||
| FillType f (*this); | |||
| f.transform = f.transform.followedBy (t); | |||
| return f; | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,150 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Represents a colour or fill pattern to use for rendering paths. | |||
| This is used by the Graphics and DrawablePath classes as a way to encapsulate | |||
| a brush type. It can either be a solid colour, a gradient, or a tiled image. | |||
| @see Graphics::setFillType, DrawablePath::setFill | |||
| */ | |||
| class JUCE_API FillType | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a default fill type, of solid black. */ | |||
| FillType() noexcept; | |||
| /** Creates a fill type of a solid colour. | |||
| @see setColour | |||
| */ | |||
| FillType (Colour colour) noexcept; | |||
| /** Creates a gradient fill type. | |||
| @see setGradient | |||
| */ | |||
| FillType (const ColourGradient& gradient); | |||
| /** Creates a tiled image fill type. The transform allows you to set the scaling, offset | |||
| and rotation of the pattern. | |||
| @see setTiledImage | |||
| */ | |||
| FillType (const Image& image, const AffineTransform& transform) noexcept; | |||
| /** Creates a copy of another FillType. */ | |||
| FillType (const FillType&); | |||
| /** Makes a copy of another FillType. */ | |||
| FillType& operator= (const FillType&); | |||
| /** Move constructor */ | |||
| FillType (FillType&&) noexcept; | |||
| /** Move assignment operator */ | |||
| FillType& operator= (FillType&&) noexcept; | |||
| /** Destructor. */ | |||
| ~FillType() noexcept; | |||
| //============================================================================== | |||
| /** Returns true if this is a solid colour fill, and not a gradient or image. */ | |||
| bool isColour() const noexcept { return gradient == nullptr && image.isNull(); } | |||
| /** Returns true if this is a gradient fill. */ | |||
| bool isGradient() const noexcept { return gradient != nullptr; } | |||
| /** Returns true if this is a tiled image pattern fill. */ | |||
| bool isTiledImage() const noexcept { return image.isValid(); } | |||
| /** Turns this object into a solid colour fill. | |||
| If the object was an image or gradient, those fields will no longer be valid. */ | |||
| void setColour (Colour newColour) noexcept; | |||
| /** Turns this object into a gradient fill. */ | |||
| void setGradient (const ColourGradient& newGradient); | |||
| /** Turns this object into a tiled image fill type. The transform allows you to set | |||
| the scaling, offset and rotation of the pattern. | |||
| */ | |||
| void setTiledImage (const Image& image, const AffineTransform& transform) noexcept; | |||
| /** Changes the opacity that should be used. | |||
| If the fill is a solid colour, this just changes the opacity of that colour. For | |||
| gradients and image tiles, it changes the opacity that will be used for them. | |||
| */ | |||
| void setOpacity (float newOpacity) noexcept; | |||
| /** Returns the current opacity to be applied to the colour, gradient, or image. | |||
| @see setOpacity | |||
| */ | |||
| float getOpacity() const noexcept { return colour.getFloatAlpha(); } | |||
| /** Returns true if this fill type is completely transparent. */ | |||
| bool isInvisible() const noexcept; | |||
| /** Returns a copy of this fill, adding the specified transform applied to the | |||
| existing transform. | |||
| */ | |||
| FillType transformed (const AffineTransform& transform) const; | |||
| //============================================================================== | |||
| /** The solid colour being used. | |||
| If the fill type is not a solid colour, the alpha channel of this colour indicates | |||
| the opacity that should be used for the fill, and the RGB channels are ignored. | |||
| */ | |||
| Colour colour; | |||
| /** Returns the gradient that should be used for filling. | |||
| This will be zero if the object is some other type of fill. | |||
| If a gradient is active, the overall opacity with which it should be applied | |||
| is indicated by the alpha channel of the colour variable. | |||
| */ | |||
| ScopedPointer<ColourGradient> gradient; | |||
| /** The image that should be used for tiling. | |||
| If an image fill is active, the overall opacity with which it should be applied | |||
| is indicated by the alpha channel of the colour variable. | |||
| */ | |||
| Image image; | |||
| /** The transform that should be applied to the image or gradient that's being drawn. */ | |||
| AffineTransform transform; | |||
| //============================================================================== | |||
| bool operator== (const FillType&) const; | |||
| bool operator!= (const FillType&) const; | |||
| private: | |||
| JUCE_LEAK_DETECTOR (FillType) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,757 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| #if JUCE_MSVC | |||
| #pragma pack (push, 1) | |||
| #endif | |||
| class PixelRGB; | |||
| class PixelAlpha; | |||
| inline uint32 maskPixelComponents (uint32 x) noexcept | |||
| { | |||
| return (x >> 8) & 0x00ff00ff; | |||
| } | |||
| inline uint32 clampPixelComponents (uint32 x) noexcept | |||
| { | |||
| return (x | (0x01000100 - maskPixelComponents (x))) & 0x00ff00ff; | |||
| } | |||
| //============================================================================== | |||
| /** | |||
| Represents a 32-bit INTERNAL pixel with premultiplied alpha, and can perform compositing | |||
| operations with it. | |||
| This is used internally by the imaging classes. | |||
| @see PixelRGB | |||
| */ | |||
| class JUCE_API PixelARGB | |||
| { | |||
| public: | |||
| /** Creates a pixel without defining its colour. */ | |||
| PixelARGB() noexcept {} | |||
| ~PixelARGB() noexcept {} | |||
| PixelARGB (const uint8 a, const uint8 r, const uint8 g, const uint8 b) noexcept | |||
| { | |||
| components.b = b; | |||
| components.g = g; | |||
| components.r = r; | |||
| components.a = a; | |||
| } | |||
| //============================================================================== | |||
| /** Returns a uint32 which represents the pixel in a platform dependent format. */ | |||
| forcedinline uint32 getNativeARGB() const noexcept { return internal; } | |||
| /** Returns a uint32 which will be in argb order as if constructed with the following mask operation | |||
| ((alpha << 24) | (red << 16) | (green << 8) | blue). */ | |||
| forcedinline uint32 getInARGBMaskOrder() const noexcept | |||
| { | |||
| #if JUCE_ANDROID | |||
| return (uint32) ((components.a << 24) | (components.r << 16) | (components.g << 8) | (components.b << 0)); | |||
| #else | |||
| return getNativeARGB(); | |||
| #endif | |||
| } | |||
| /** Returns a uint32 which when written to memory, will be in the order a, r, g, b. In other words, | |||
| if the return-value is read as a uint8 array then the elements will be in the order of a, r, g, b*/ | |||
| inline uint32 getInARGBMemoryOrder() const noexcept | |||
| { | |||
| #if JUCE_BIG_ENDIAN | |||
| return getInARGBMaskOrder(); | |||
| #else | |||
| return (uint32) ((components.b << 24) | (components.g << 16) | (components.r << 8) | components.a); | |||
| #endif | |||
| } | |||
| /** Return channels with an even index and insert zero bytes between them. This is useful for blending | |||
| operations. The exact channels which are returned is platform dependent. */ | |||
| forcedinline uint32 getEvenBytes() const noexcept { return 0x00ff00ff & internal; } | |||
| /** Return channels with an odd index and insert zero bytes between them. This is useful for blending | |||
| operations. The exact channels which are returned is platform dependent. */ | |||
| forcedinline uint32 getOddBytes() const noexcept { return 0x00ff00ff & (internal >> 8); } | |||
| //============================================================================== | |||
| forcedinline uint8 getAlpha() const noexcept { return components.a; } | |||
| forcedinline uint8 getRed() const noexcept { return components.r; } | |||
| forcedinline uint8 getGreen() const noexcept { return components.g; } | |||
| forcedinline uint8 getBlue() const noexcept { return components.b; } | |||
| #if JUCE_GCC | |||
| // NB these are here as a workaround because GCC refuses to bind to packed values. | |||
| forcedinline uint8& getAlpha() noexcept { return comps [indexA]; } | |||
| forcedinline uint8& getRed() noexcept { return comps [indexR]; } | |||
| forcedinline uint8& getGreen() noexcept { return comps [indexG]; } | |||
| forcedinline uint8& getBlue() noexcept { return comps [indexB]; } | |||
| #else | |||
| forcedinline uint8& getAlpha() noexcept { return components.a; } | |||
| forcedinline uint8& getRed() noexcept { return components.r; } | |||
| forcedinline uint8& getGreen() noexcept { return components.g; } | |||
| forcedinline uint8& getBlue() noexcept { return components.b; } | |||
| #endif | |||
| //============================================================================== | |||
| /** Copies another pixel colour over this one. | |||
| This doesn't blend it - this colour is simply replaced by the other one. | |||
| */ | |||
| template <class Pixel> | |||
| forcedinline void set (const Pixel& src) noexcept | |||
| { | |||
| internal = src.getNativeARGB(); | |||
| } | |||
| //============================================================================== | |||
| /** Sets the pixel's colour from individual components. */ | |||
| void setARGB (const uint8 a, const uint8 r, const uint8 g, const uint8 b) noexcept | |||
| { | |||
| components.b = b; | |||
| components.g = g; | |||
| components.r = r; | |||
| components.a = a; | |||
| } | |||
| //============================================================================== | |||
| /** Blends another pixel onto this one. | |||
| This takes into account the opacity of the pixel being overlaid, and blends | |||
| it accordingly. | |||
| */ | |||
| template <class Pixel> | |||
| forcedinline void blend (const Pixel& src) noexcept | |||
| { | |||
| uint32 rb = src.getEvenBytes(); | |||
| uint32 ag = src.getOddBytes(); | |||
| const uint32 alpha = 0x100 - (ag >> 16); | |||
| rb += maskPixelComponents (getEvenBytes() * alpha); | |||
| ag += maskPixelComponents (getOddBytes() * alpha); | |||
| internal = clampPixelComponents (rb) | (clampPixelComponents (ag) << 8); | |||
| } | |||
| /** Blends another pixel onto this one. | |||
| This takes into account the opacity of the pixel being overlaid, and blends | |||
| it accordingly. | |||
| */ | |||
| forcedinline void blend (const PixelRGB src) noexcept; | |||
| /** Blends another pixel onto this one, applying an extra multiplier to its opacity. | |||
| The opacity of the pixel being overlaid is scaled by the extraAlpha factor before | |||
| being used, so this can blend semi-transparently from a PixelRGB argument. | |||
| */ | |||
| template <class Pixel> | |||
| forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept | |||
| { | |||
| uint32 rb = maskPixelComponents (extraAlpha * src.getEvenBytes()); | |||
| uint32 ag = maskPixelComponents (extraAlpha * src.getOddBytes()); | |||
| const uint32 alpha = 0x100 - (ag >> 16); | |||
| rb += maskPixelComponents (getEvenBytes() * alpha); | |||
| ag += maskPixelComponents (getOddBytes() * alpha); | |||
| internal = clampPixelComponents (rb) | (clampPixelComponents (ag) << 8); | |||
| } | |||
| /** Blends another pixel with this one, creating a colour that is somewhere | |||
| between the two, as specified by the amount. | |||
| */ | |||
| template <class Pixel> | |||
| forcedinline void tween (const Pixel& src, const uint32 amount) noexcept | |||
| { | |||
| uint32 dEvenBytes = getEvenBytes(); | |||
| dEvenBytes += (((src.getEvenBytes() - dEvenBytes) * amount) >> 8); | |||
| dEvenBytes &= 0x00ff00ff; | |||
| uint32 dOddBytes = getOddBytes(); | |||
| dOddBytes += (((src.getOddBytes() - dOddBytes) * amount) >> 8); | |||
| dOddBytes &= 0x00ff00ff; | |||
| dOddBytes <<= 8; | |||
| dOddBytes |= dEvenBytes; | |||
| internal = dOddBytes; | |||
| } | |||
| //============================================================================== | |||
| /** Replaces the colour's alpha value with another one. */ | |||
| forcedinline void setAlpha (const uint8 newAlpha) noexcept | |||
| { | |||
| components.a = newAlpha; | |||
| } | |||
| /** Multiplies the colour's alpha value with another one. */ | |||
| forcedinline void multiplyAlpha (int multiplier) noexcept | |||
| { | |||
| // increment alpha by 1, so that if multiplier == 255 (full alpha), | |||
| // this function will not change the values. | |||
| ++multiplier; | |||
| internal = ((((uint32) multiplier) * getOddBytes()) & 0xff00ff00) | |||
| | (((((uint32) multiplier) * getEvenBytes()) >> 8) & 0x00ff00ff); | |||
| } | |||
| forcedinline void multiplyAlpha (const float multiplier) noexcept | |||
| { | |||
| multiplyAlpha ((int) (multiplier * 255.0f)); | |||
| } | |||
| inline PixelARGB getUnpremultiplied() const noexcept { PixelARGB p (internal); p.unpremultiply(); return p; } | |||
| /** Premultiplies the pixel's RGB values by its alpha. */ | |||
| forcedinline void premultiply() noexcept | |||
| { | |||
| const uint32 alpha = components.a; | |||
| if (alpha < 0xff) | |||
| { | |||
| if (alpha == 0) | |||
| { | |||
| components.b = 0; | |||
| components.g = 0; | |||
| components.r = 0; | |||
| } | |||
| else | |||
| { | |||
| components.b = (uint8) ((components.b * alpha + 0x7f) >> 8); | |||
| components.g = (uint8) ((components.g * alpha + 0x7f) >> 8); | |||
| components.r = (uint8) ((components.r * alpha + 0x7f) >> 8); | |||
| } | |||
| } | |||
| } | |||
| /** Unpremultiplies the pixel's RGB values. */ | |||
| forcedinline void unpremultiply() noexcept | |||
| { | |||
| const uint32 alpha = components.a; | |||
| if (alpha < 0xff) | |||
| { | |||
| if (alpha == 0) | |||
| { | |||
| components.b = 0; | |||
| components.g = 0; | |||
| components.r = 0; | |||
| } | |||
| else | |||
| { | |||
| components.b = (uint8) jmin ((uint32) 0xffu, (components.b * 0xffu) / alpha); | |||
| components.g = (uint8) jmin ((uint32) 0xffu, (components.g * 0xffu) / alpha); | |||
| components.r = (uint8) jmin ((uint32) 0xffu, (components.r * 0xffu) / alpha); | |||
| } | |||
| } | |||
| } | |||
| forcedinline void desaturate() noexcept | |||
| { | |||
| if (components.a < 0xff && components.a > 0) | |||
| { | |||
| const int newUnpremultipliedLevel = (0xff * ((int) components.r + (int) components.g + (int) components.b) / (3 * components.a)); | |||
| components.r = components.g = components.b | |||
| = (uint8) ((newUnpremultipliedLevel * components.a + 0x7f) >> 8); | |||
| } | |||
| else | |||
| { | |||
| components.r = components.g = components.b | |||
| = (uint8) (((int) components.r + (int) components.g + (int) components.b) / 3); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| /** The indexes of the different components in the byte layout of this type of colour. */ | |||
| #if JUCE_ANDROID | |||
| #if JUCE_BIG_ENDIAN | |||
| enum { indexA = 0, indexR = 3, indexG = 2, indexB = 1 }; | |||
| #else | |||
| enum { indexA = 3, indexR = 0, indexG = 1, indexB = 2 }; | |||
| #endif | |||
| #else | |||
| #if JUCE_BIG_ENDIAN | |||
| enum { indexA = 0, indexR = 1, indexG = 2, indexB = 3 }; | |||
| #else | |||
| enum { indexA = 3, indexR = 2, indexG = 1, indexB = 0 }; | |||
| #endif | |||
| #endif | |||
| private: | |||
| //============================================================================== | |||
| PixelARGB (const uint32 internalValue) noexcept | |||
| : internal (internalValue) | |||
| { | |||
| } | |||
| //============================================================================== | |||
| struct Components | |||
| { | |||
| #if JUCE_ANDROID | |||
| #if JUCE_BIG_ENDIAN | |||
| uint8 a, b, g, r; | |||
| #else | |||
| uint8 r, g, b, a; | |||
| #endif | |||
| #else | |||
| #if JUCE_BIG_ENDIAN | |||
| uint8 a, r, g, b; | |||
| #else | |||
| uint8 b, g, r, a; | |||
| #endif | |||
| #endif | |||
| } JUCE_PACKED; | |||
| union | |||
| { | |||
| uint32 internal; | |||
| Components components; | |||
| #if JUCE_GCC | |||
| uint8 comps[4]; // helper struct needed because gcc does not allow references to packed union members | |||
| #endif | |||
| }; | |||
| } | |||
| #ifndef DOXYGEN | |||
| JUCE_PACKED | |||
| #endif | |||
| ; | |||
| //============================================================================== | |||
| /** | |||
| Represents a 24-bit RGB pixel, and can perform compositing operations on it. | |||
| This is used internally by the imaging classes. | |||
| @see PixelARGB | |||
| */ | |||
| class JUCE_API PixelRGB | |||
| { | |||
| public: | |||
| /** Creates a pixel without defining its colour. */ | |||
| PixelRGB() noexcept {} | |||
| ~PixelRGB() noexcept {} | |||
| //============================================================================== | |||
| /** Returns a uint32 which represents the pixel in a platform dependent format which is compatible | |||
| with the native format of a PixelARGB. | |||
| @see PixelARGB::getNativeARGB */ | |||
| forcedinline uint32 getNativeARGB() const noexcept | |||
| { | |||
| #if JUCE_ANDROID | |||
| return (uint32) ((0xff << 24) | r | (g << 8) | (b << 16)); | |||
| #else | |||
| return (uint32) ((0xff << 24) | b | (g << 8) | (r << 16)); | |||
| #endif | |||
| } | |||
| /** Returns a uint32 which will be in argb order as if constructed with the following mask operation | |||
| ((alpha << 24) | (red << 16) | (green << 8) | blue). */ | |||
| forcedinline uint32 getInARGBMaskOrder() const noexcept | |||
| { | |||
| #if JUCE_ANDROID | |||
| return (uint32) ((0xff << 24) | (r << 16) | (g << 8) | (b << 0)); | |||
| #else | |||
| return getNativeARGB(); | |||
| #endif | |||
| } | |||
| /** Returns a uint32 which when written to memory, will be in the order a, r, g, b. In other words, | |||
| if the return-value is read as a uint8 array then the elements will be in the order of a, r, g, b*/ | |||
| inline uint32 getInARGBMemoryOrder() const noexcept | |||
| { | |||
| #if JUCE_BIG_ENDIAN | |||
| return getInARGBMaskOrder(); | |||
| #else | |||
| return (uint32) ((b << 24) | (g << 16) | (r << 8) | 0xff); | |||
| #endif | |||
| } | |||
| /** Return channels with an even index and insert zero bytes between them. This is useful for blending | |||
| operations. The exact channels which are returned is platform dependent but compatible with the | |||
| return value of getEvenBytes of the PixelARGB class. | |||
| @see PixelARGB::getEvenBytes */ | |||
| forcedinline uint32 getEvenBytes() const noexcept | |||
| { | |||
| #if JUCE_ANDROID | |||
| return (uint32) (r | (b << 16)); | |||
| #else | |||
| return (uint32) (b | (r << 16)); | |||
| #endif | |||
| } | |||
| /** Return channels with an odd index and insert zero bytes between them. This is useful for blending | |||
| operations. The exact channels which are returned is platform dependent but compatible with the | |||
| return value of getOddBytes of the PixelARGB class. | |||
| @see PixelARGB::getOddBytes */ | |||
| forcedinline uint32 getOddBytes() const noexcept { return (uint32)0xff0000 | g; } | |||
| //============================================================================== | |||
| forcedinline uint8 getAlpha() const noexcept { return 0xff; } | |||
| forcedinline uint8 getRed() const noexcept { return r; } | |||
| forcedinline uint8 getGreen() const noexcept { return g; } | |||
| forcedinline uint8 getBlue() const noexcept { return b; } | |||
| forcedinline uint8& getRed() noexcept { return r; } | |||
| forcedinline uint8& getGreen() noexcept { return g; } | |||
| forcedinline uint8& getBlue() noexcept { return b; } | |||
| //============================================================================== | |||
| /** Copies another pixel colour over this one. | |||
| This doesn't blend it - this colour is simply replaced by the other one. | |||
| Because PixelRGB has no alpha channel, any alpha value in the source pixel | |||
| is thrown away. | |||
| */ | |||
| template <class Pixel> | |||
| forcedinline void set (const Pixel& src) noexcept | |||
| { | |||
| b = src.getBlue(); | |||
| g = src.getGreen(); | |||
| r = src.getRed(); | |||
| } | |||
| /** Sets the pixel's colour from individual components. */ | |||
| void setARGB (const uint8, const uint8 red, const uint8 green, const uint8 blue) noexcept | |||
| { | |||
| r = red; | |||
| g = green; | |||
| b = blue; | |||
| } | |||
| //============================================================================== | |||
| /** Blends another pixel onto this one. | |||
| This takes into account the opacity of the pixel being overlaid, and blends | |||
| it accordingly. | |||
| */ | |||
| template <class Pixel> | |||
| forcedinline void blend (const Pixel& src) noexcept | |||
| { | |||
| const uint32 alpha = (uint32) (0x100 - src.getAlpha()); | |||
| // getEvenBytes returns 0x00rr00bb on non-android | |||
| uint32 rb = clampPixelComponents (src.getEvenBytes() + maskPixelComponents (getEvenBytes() * alpha)); | |||
| // getOddBytes returns 0x00aa00gg on non-android | |||
| uint32 ag = clampPixelComponents (src.getOddBytes() + ((g * alpha) >> 8)); | |||
| g = (uint8) (ag & 0xff); | |||
| #if JUCE_ANDROID | |||
| b = (uint8) (rb >> 16); | |||
| r = (uint8) (rb & 0xff); | |||
| #else | |||
| r = (uint8) (rb >> 16); | |||
| b = (uint8) (rb & 0xff); | |||
| #endif | |||
| } | |||
| forcedinline void blend (const PixelRGB src) noexcept | |||
| { | |||
| set (src); | |||
| } | |||
| /** Blends another pixel onto this one, applying an extra multiplier to its opacity. | |||
| The opacity of the pixel being overlaid is scaled by the extraAlpha factor before | |||
| being used, so this can blend semi-transparently from a PixelRGB argument. | |||
| */ | |||
| template <class Pixel> | |||
| forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept | |||
| { | |||
| uint32 ag = maskPixelComponents (extraAlpha * src.getOddBytes()); | |||
| uint32 rb = maskPixelComponents (extraAlpha * src.getEvenBytes()); | |||
| const uint32 alpha = 0x100 - (ag >> 16); | |||
| ag = clampPixelComponents (ag + (g * alpha >> 8)); | |||
| rb = clampPixelComponents (rb + maskPixelComponents (getEvenBytes() * alpha)); | |||
| g = (uint8) (ag & 0xff); | |||
| #if JUCE_ANDROID | |||
| b = (uint8) (rb >> 16); | |||
| r = (uint8) (rb & 0xff); | |||
| #else | |||
| r = (uint8) (rb >> 16); | |||
| b = (uint8) (rb & 0xff); | |||
| #endif | |||
| } | |||
| /** Blends another pixel with this one, creating a colour that is somewhere | |||
| between the two, as specified by the amount. | |||
| */ | |||
| template <class Pixel> | |||
| forcedinline void tween (const Pixel& src, const uint32 amount) noexcept | |||
| { | |||
| uint32 dEvenBytes = getEvenBytes(); | |||
| dEvenBytes += (((src.getEvenBytes() - dEvenBytes) * amount) >> 8); | |||
| uint32 dOddBytes = getOddBytes(); | |||
| dOddBytes += (((src.getOddBytes() - dOddBytes) * amount) >> 8); | |||
| g = (uint8) (dOddBytes & 0xff); // dOddBytes = 0x00aa00gg | |||
| #if JUCE_ANDROID | |||
| r = (uint8) (dEvenBytes & 0xff); // dEvenBytes = 0x00bb00rr | |||
| b = (uint8) (dEvenBytes >> 16); | |||
| #else | |||
| b = (uint8) (dEvenBytes & 0xff); // dEvenBytes = 0x00rr00bb | |||
| r = (uint8) (dEvenBytes >> 16); | |||
| #endif | |||
| } | |||
| //============================================================================== | |||
| /** This method is included for compatibility with the PixelARGB class. */ | |||
| forcedinline void setAlpha (const uint8) noexcept {} | |||
| /** Multiplies the colour's alpha value with another one. */ | |||
| forcedinline void multiplyAlpha (int) noexcept {} | |||
| /** Multiplies the colour's alpha value with another one. */ | |||
| forcedinline void multiplyAlpha (float) noexcept {} | |||
| /** Premultiplies the pixel's RGB values by its alpha. */ | |||
| forcedinline void premultiply() noexcept {} | |||
| /** Unpremultiplies the pixel's RGB values. */ | |||
| forcedinline void unpremultiply() noexcept {} | |||
| forcedinline void desaturate() noexcept | |||
| { | |||
| r = g = b = (uint8) (((int) r + (int) g + (int) b) / 3); | |||
| } | |||
| //============================================================================== | |||
| /** The indexes of the different components in the byte layout of this type of colour. */ | |||
| #if JUCE_MAC | |||
| enum { indexR = 0, indexG = 1, indexB = 2 }; | |||
| #else | |||
| enum { indexR = 2, indexG = 1, indexB = 0 }; | |||
| #endif | |||
| private: | |||
| //============================================================================== | |||
| PixelRGB (const uint32 internal) noexcept | |||
| { | |||
| #if JUCE_ANDROID | |||
| b = (uint8) (internal >> 16); | |||
| g = (uint8) (internal >> 8); | |||
| r = (uint8) (internal); | |||
| #else | |||
| r = (uint8) (internal >> 16); | |||
| g = (uint8) (internal >> 8); | |||
| b = (uint8) (internal); | |||
| #endif | |||
| } | |||
| //============================================================================== | |||
| #if JUCE_MAC | |||
| uint8 r, g, b; | |||
| #else | |||
| uint8 b, g, r; | |||
| #endif | |||
| } | |||
| #ifndef DOXYGEN | |||
| JUCE_PACKED | |||
| #endif | |||
| ; | |||
| forcedinline void PixelARGB::blend (const PixelRGB src) noexcept | |||
| { | |||
| set (src); | |||
| } | |||
| //============================================================================== | |||
| /** | |||
| Represents an 8-bit single-channel pixel, and can perform compositing operations on it. | |||
| This is used internally by the imaging classes. | |||
| @see PixelARGB, PixelRGB | |||
| */ | |||
| class JUCE_API PixelAlpha | |||
| { | |||
| public: | |||
| /** Creates a pixel without defining its colour. */ | |||
| PixelAlpha() noexcept {} | |||
| ~PixelAlpha() noexcept {} | |||
| //============================================================================== | |||
| /** Returns a uint32 which represents the pixel in a platform dependent format which is compatible | |||
| with the native format of a PixelARGB. | |||
| @see PixelARGB::getNativeARGB */ | |||
| forcedinline uint32 getNativeARGB() const noexcept { return (uint32) ((a << 24) | (a << 16) | (a << 8) | a); } | |||
| /** Returns a uint32 which will be in argb order as if constructed with the following mask operation | |||
| ((alpha << 24) | (red << 16) | (green << 8) | blue). */ | |||
| forcedinline uint32 getInARGBMaskOrder() const noexcept { return getNativeARGB(); } | |||
| /** Returns a uint32 which when written to memory, will be in the order a, r, g, b. In other words, | |||
| if the return-value is read as a uint8 array then the elements will be in the order of a, r, g, b*/ | |||
| inline uint32 getInARGBMemoryOrder() const noexcept { return getNativeARGB(); } | |||
| /** Return channels with an even index and insert zero bytes between them. This is useful for blending | |||
| operations. The exact channels which are returned is platform dependent but compatible with the | |||
| return value of getEvenBytes of the PixelARGB class. | |||
| @see PixelARGB::getEvenBytes */ | |||
| forcedinline uint32 getEvenBytes() const noexcept { return (uint32) ((a << 16) | a); } | |||
| /** Return channels with an odd index and insert zero bytes between them. This is useful for blending | |||
| operations. The exact channels which are returned is platform dependent but compatible with the | |||
| return value of getOddBytes of the PixelARGB class. | |||
| @see PixelARGB::getOddBytes */ | |||
| forcedinline uint32 getOddBytes() const noexcept { return (uint32) ((a << 16) | a); } | |||
| //============================================================================== | |||
| forcedinline uint8 getAlpha() const noexcept { return a; } | |||
| forcedinline uint8& getAlpha() noexcept { return a; } | |||
| forcedinline uint8 getRed() const noexcept { return 0; } | |||
| forcedinline uint8 getGreen() const noexcept { return 0; } | |||
| forcedinline uint8 getBlue() const noexcept { return 0; } | |||
| //============================================================================== | |||
| /** Copies another pixel colour over this one. | |||
| This doesn't blend it - this colour is simply replaced by the other one. | |||
| */ | |||
| template <class Pixel> | |||
| forcedinline void set (const Pixel& src) noexcept | |||
| { | |||
| a = src.getAlpha(); | |||
| } | |||
| /** Sets the pixel's colour from individual components. */ | |||
| forcedinline void setARGB (const uint8 a_, const uint8 /*r*/, const uint8 /*g*/, const uint8 /*b*/) noexcept | |||
| { | |||
| a = a_; | |||
| } | |||
| //============================================================================== | |||
| /** Blends another pixel onto this one. | |||
| This takes into account the opacity of the pixel being overlaid, and blends | |||
| it accordingly. | |||
| */ | |||
| template <class Pixel> | |||
| forcedinline void blend (const Pixel& src) noexcept | |||
| { | |||
| const int srcA = src.getAlpha(); | |||
| a = (uint8) ((a * (0x100 - srcA) >> 8) + srcA); | |||
| } | |||
| /** Blends another pixel onto this one, applying an extra multiplier to its opacity. | |||
| The opacity of the pixel being overlaid is scaled by the extraAlpha factor before | |||
| being used, so this can blend semi-transparently from a PixelRGB argument. | |||
| */ | |||
| template <class Pixel> | |||
| forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept | |||
| { | |||
| ++extraAlpha; | |||
| const int srcAlpha = (int) ((extraAlpha * src.getAlpha()) >> 8); | |||
| a = (uint8) ((a * (0x100 - srcAlpha) >> 8) + srcAlpha); | |||
| } | |||
| /** Blends another pixel with this one, creating a colour that is somewhere | |||
| between the two, as specified by the amount. | |||
| */ | |||
| template <class Pixel> | |||
| forcedinline void tween (const Pixel& src, const uint32 amount) noexcept | |||
| { | |||
| a += ((src.getAlpha() - a) * amount) >> 8; | |||
| } | |||
| //============================================================================== | |||
| /** Replaces the colour's alpha value with another one. */ | |||
| forcedinline void setAlpha (const uint8 newAlpha) noexcept | |||
| { | |||
| a = newAlpha; | |||
| } | |||
| /** Multiplies the colour's alpha value with another one. */ | |||
| forcedinline void multiplyAlpha (int multiplier) noexcept | |||
| { | |||
| ++multiplier; | |||
| a = (uint8) ((a * multiplier) >> 8); | |||
| } | |||
| forcedinline void multiplyAlpha (const float multiplier) noexcept | |||
| { | |||
| a = (uint8) (a * multiplier); | |||
| } | |||
| /** Premultiplies the pixel's RGB values by its alpha. */ | |||
| forcedinline void premultiply() noexcept {} | |||
| /** Unpremultiplies the pixel's RGB values. */ | |||
| forcedinline void unpremultiply() noexcept {} | |||
| forcedinline void desaturate() noexcept {} | |||
| //============================================================================== | |||
| /** The indexes of the different components in the byte layout of this type of colour. */ | |||
| enum { indexA = 0 }; | |||
| private: | |||
| //============================================================================== | |||
| PixelAlpha (const uint32 internal) noexcept | |||
| { | |||
| a = (uint8) (internal >> 24); | |||
| } | |||
| //============================================================================== | |||
| uint8 a; | |||
| } | |||
| #ifndef DOXYGEN | |||
| JUCE_PACKED | |||
| #endif | |||
| ; | |||
| #if JUCE_MSVC | |||
| #pragma pack (pop) | |||
| #endif | |||
| } // namespace juce | |||
| @@ -0,0 +1,699 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| namespace | |||
| { | |||
| template <typename Type> | |||
| Rectangle<Type> coordsToRectangle (Type x, Type y, Type w, Type h) noexcept | |||
| { | |||
| #if JUCE_DEBUG | |||
| const int maxVal = 0x3fffffff; | |||
| jassert ((int) x >= -maxVal && (int) x <= maxVal | |||
| && (int) y >= -maxVal && (int) y <= maxVal | |||
| && (int) w >= 0 && (int) w <= maxVal | |||
| && (int) h >= 0 && (int) h <= maxVal); | |||
| #endif | |||
| return { x, y, w, h }; | |||
| } | |||
| } | |||
| //============================================================================== | |||
| LowLevelGraphicsContext::LowLevelGraphicsContext() {} | |||
| LowLevelGraphicsContext::~LowLevelGraphicsContext() {} | |||
| //============================================================================== | |||
| Graphics::Graphics (const Image& imageToDrawOnto) | |||
| : context (*imageToDrawOnto.createLowLevelContext()), | |||
| contextToDelete (&context) | |||
| { | |||
| jassert (imageToDrawOnto.isValid()); // Can't draw into a null image! | |||
| } | |||
| Graphics::Graphics (LowLevelGraphicsContext& internalContext) noexcept | |||
| : context (internalContext) | |||
| { | |||
| } | |||
| Graphics::~Graphics() | |||
| { | |||
| } | |||
| //============================================================================== | |||
| void Graphics::resetToDefaultState() | |||
| { | |||
| saveStateIfPending(); | |||
| context.setFill (FillType()); | |||
| context.setFont (Font()); | |||
| context.setInterpolationQuality (Graphics::mediumResamplingQuality); | |||
| } | |||
| bool Graphics::isVectorDevice() const | |||
| { | |||
| return context.isVectorDevice(); | |||
| } | |||
| bool Graphics::reduceClipRegion (Rectangle<int> area) | |||
| { | |||
| saveStateIfPending(); | |||
| return context.clipToRectangle (area); | |||
| } | |||
| bool Graphics::reduceClipRegion (int x, int y, int w, int h) | |||
| { | |||
| return reduceClipRegion (coordsToRectangle (x, y, w, h)); | |||
| } | |||
| bool Graphics::reduceClipRegion (const RectangleList<int>& clipRegion) | |||
| { | |||
| saveStateIfPending(); | |||
| return context.clipToRectangleList (clipRegion); | |||
| } | |||
| bool Graphics::reduceClipRegion (const Path& path, const AffineTransform& transform) | |||
| { | |||
| saveStateIfPending(); | |||
| context.clipToPath (path, transform); | |||
| return ! context.isClipEmpty(); | |||
| } | |||
| bool Graphics::reduceClipRegion (const Image& image, const AffineTransform& transform) | |||
| { | |||
| saveStateIfPending(); | |||
| context.clipToImageAlpha (image, transform); | |||
| return ! context.isClipEmpty(); | |||
| } | |||
| void Graphics::excludeClipRegion (Rectangle<int> rectangleToExclude) | |||
| { | |||
| saveStateIfPending(); | |||
| context.excludeClipRectangle (rectangleToExclude); | |||
| } | |||
| bool Graphics::isClipEmpty() const | |||
| { | |||
| return context.isClipEmpty(); | |||
| } | |||
| Rectangle<int> Graphics::getClipBounds() const | |||
| { | |||
| return context.getClipBounds(); | |||
| } | |||
| void Graphics::saveState() | |||
| { | |||
| saveStateIfPending(); | |||
| saveStatePending = true; | |||
| } | |||
| void Graphics::restoreState() | |||
| { | |||
| if (saveStatePending) | |||
| saveStatePending = false; | |||
| else | |||
| context.restoreState(); | |||
| } | |||
| void Graphics::saveStateIfPending() | |||
| { | |||
| if (saveStatePending) | |||
| { | |||
| saveStatePending = false; | |||
| context.saveState(); | |||
| } | |||
| } | |||
| void Graphics::setOrigin (Point<int> newOrigin) | |||
| { | |||
| saveStateIfPending(); | |||
| context.setOrigin (newOrigin); | |||
| } | |||
| void Graphics::setOrigin (int x, int y) | |||
| { | |||
| setOrigin ({ x, y }); | |||
| } | |||
| void Graphics::addTransform (const AffineTransform& transform) | |||
| { | |||
| saveStateIfPending(); | |||
| context.addTransform (transform); | |||
| } | |||
| bool Graphics::clipRegionIntersects (Rectangle<int> area) const | |||
| { | |||
| return context.clipRegionIntersects (area); | |||
| } | |||
| void Graphics::beginTransparencyLayer (float layerOpacity) | |||
| { | |||
| saveStateIfPending(); | |||
| context.beginTransparencyLayer (layerOpacity); | |||
| } | |||
| void Graphics::endTransparencyLayer() | |||
| { | |||
| context.endTransparencyLayer(); | |||
| } | |||
| //============================================================================== | |||
| void Graphics::setColour (Colour newColour) | |||
| { | |||
| saveStateIfPending(); | |||
| context.setFill (newColour); | |||
| } | |||
| void Graphics::setOpacity (const float newOpacity) | |||
| { | |||
| saveStateIfPending(); | |||
| context.setOpacity (newOpacity); | |||
| } | |||
| void Graphics::setGradientFill (const ColourGradient& gradient) | |||
| { | |||
| setFillType (gradient); | |||
| } | |||
| void Graphics::setTiledImageFill (const Image& imageToUse, const int anchorX, const int anchorY, const float opacity) | |||
| { | |||
| saveStateIfPending(); | |||
| context.setFill (FillType (imageToUse, AffineTransform::translation ((float) anchorX, (float) anchorY))); | |||
| context.setOpacity (opacity); | |||
| } | |||
| void Graphics::setFillType (const FillType& newFill) | |||
| { | |||
| saveStateIfPending(); | |||
| context.setFill (newFill); | |||
| } | |||
| //============================================================================== | |||
| void Graphics::setFont (const Font& newFont) | |||
| { | |||
| saveStateIfPending(); | |||
| context.setFont (newFont); | |||
| } | |||
| void Graphics::setFont (const float newFontHeight) | |||
| { | |||
| setFont (context.getFont().withHeight (newFontHeight)); | |||
| } | |||
| Font Graphics::getCurrentFont() const | |||
| { | |||
| return context.getFont(); | |||
| } | |||
| //============================================================================== | |||
| void Graphics::drawSingleLineText (const String& text, const int startX, const int baselineY, | |||
| Justification justification) const | |||
| { | |||
| if (text.isNotEmpty()) | |||
| { | |||
| // Don't pass any vertical placement flags to this method - they'll be ignored. | |||
| jassert (justification.getOnlyVerticalFlags() == 0); | |||
| auto flags = justification.getOnlyHorizontalFlags(); | |||
| if (flags == Justification::right && startX < context.getClipBounds().getX()) | |||
| return; | |||
| if (flags == Justification::left && startX > context.getClipBounds().getRight()) | |||
| return; | |||
| GlyphArrangement arr; | |||
| arr.addLineOfText (context.getFont(), text, (float) startX, (float) baselineY); | |||
| if (flags != Justification::left) | |||
| { | |||
| auto w = arr.getBoundingBox (0, -1, true).getWidth(); | |||
| if ((flags & (Justification::horizontallyCentred | Justification::horizontallyJustified)) != 0) | |||
| w /= 2.0f; | |||
| arr.draw (*this, AffineTransform::translation (-w, 0)); | |||
| } | |||
| else | |||
| { | |||
| arr.draw (*this); | |||
| } | |||
| } | |||
| } | |||
| void Graphics::drawMultiLineText (const String& text, const int startX, | |||
| const int baselineY, const int maximumLineWidth) const | |||
| { | |||
| if (text.isNotEmpty() | |||
| && startX < context.getClipBounds().getRight()) | |||
| { | |||
| GlyphArrangement arr; | |||
| arr.addJustifiedText (context.getFont(), text, | |||
| (float) startX, (float) baselineY, (float) maximumLineWidth, | |||
| Justification::left); | |||
| arr.draw (*this); | |||
| } | |||
| } | |||
| void Graphics::drawText (const String& text, Rectangle<float> area, | |||
| Justification justificationType, bool useEllipsesIfTooBig) const | |||
| { | |||
| if (text.isNotEmpty() && context.clipRegionIntersects (area.getSmallestIntegerContainer())) | |||
| { | |||
| GlyphArrangement arr; | |||
| arr.addCurtailedLineOfText (context.getFont(), text, 0.0f, 0.0f, | |||
| area.getWidth(), useEllipsesIfTooBig); | |||
| arr.justifyGlyphs (0, arr.getNumGlyphs(), | |||
| area.getX(), area.getY(), area.getWidth(), area.getHeight(), | |||
| justificationType); | |||
| arr.draw (*this); | |||
| } | |||
| } | |||
| void Graphics::drawText (const String& text, Rectangle<int> area, | |||
| Justification justificationType, bool useEllipsesIfTooBig) const | |||
| { | |||
| drawText (text, area.toFloat(), justificationType, useEllipsesIfTooBig); | |||
| } | |||
| void Graphics::drawText (const String& text, int x, int y, int width, int height, | |||
| Justification justificationType, const bool useEllipsesIfTooBig) const | |||
| { | |||
| drawText (text, coordsToRectangle (x, y, width, height), justificationType, useEllipsesIfTooBig); | |||
| } | |||
| void Graphics::drawFittedText (const String& text, Rectangle<int> area, | |||
| Justification justification, | |||
| const int maximumNumberOfLines, | |||
| const float minimumHorizontalScale) const | |||
| { | |||
| if (text.isNotEmpty() && (! area.isEmpty()) && context.clipRegionIntersects (area)) | |||
| { | |||
| GlyphArrangement arr; | |||
| arr.addFittedText (context.getFont(), text, | |||
| (float) area.getX(), (float) area.getY(), | |||
| (float) area.getWidth(), (float) area.getHeight(), | |||
| justification, | |||
| maximumNumberOfLines, | |||
| minimumHorizontalScale); | |||
| arr.draw (*this); | |||
| } | |||
| } | |||
| void Graphics::drawFittedText (const String& text, int x, int y, int width, int height, | |||
| Justification justification, | |||
| const int maximumNumberOfLines, | |||
| const float minimumHorizontalScale) const | |||
| { | |||
| drawFittedText (text, coordsToRectangle (x, y, width, height), | |||
| justification, maximumNumberOfLines, minimumHorizontalScale); | |||
| } | |||
| //============================================================================== | |||
| void Graphics::fillRect (Rectangle<int> r) const | |||
| { | |||
| context.fillRect (r, false); | |||
| } | |||
| void Graphics::fillRect (Rectangle<float> r) const | |||
| { | |||
| context.fillRect (r); | |||
| } | |||
| void Graphics::fillRect (int x, int y, int width, int height) const | |||
| { | |||
| context.fillRect (coordsToRectangle (x, y, width, height), false); | |||
| } | |||
| void Graphics::fillRect (float x, float y, float width, float height) const | |||
| { | |||
| fillRect (coordsToRectangle (x, y, width, height)); | |||
| } | |||
| void Graphics::fillRectList (const RectangleList<float>& rectangles) const | |||
| { | |||
| context.fillRectList (rectangles); | |||
| } | |||
| void Graphics::fillRectList (const RectangleList<int>& rects) const | |||
| { | |||
| for (auto& r : rects) | |||
| context.fillRect (r, false); | |||
| } | |||
| void Graphics::fillAll() const | |||
| { | |||
| fillRect (context.getClipBounds()); | |||
| } | |||
| void Graphics::fillAll (Colour colourToUse) const | |||
| { | |||
| if (! colourToUse.isTransparent()) | |||
| { | |||
| auto clip = context.getClipBounds(); | |||
| context.saveState(); | |||
| context.setFill (colourToUse); | |||
| context.fillRect (clip, false); | |||
| context.restoreState(); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void Graphics::fillPath (const Path& path) const | |||
| { | |||
| if (! (context.isClipEmpty() || path.isEmpty())) | |||
| context.fillPath (path, AffineTransform()); | |||
| } | |||
| void Graphics::fillPath (const Path& path, const AffineTransform& transform) const | |||
| { | |||
| if (! (context.isClipEmpty() || path.isEmpty())) | |||
| context.fillPath (path, transform); | |||
| } | |||
| void Graphics::strokePath (const Path& path, | |||
| const PathStrokeType& strokeType, | |||
| const AffineTransform& transform) const | |||
| { | |||
| Path stroke; | |||
| strokeType.createStrokedPath (stroke, path, transform, context.getPhysicalPixelScaleFactor()); | |||
| fillPath (stroke); | |||
| } | |||
| //============================================================================== | |||
| void Graphics::drawRect (float x, float y, float width, float height, float lineThickness) const | |||
| { | |||
| drawRect (coordsToRectangle (x, y, width, height), lineThickness); | |||
| } | |||
| void Graphics::drawRect (int x, int y, int width, int height, int lineThickness) const | |||
| { | |||
| drawRect (coordsToRectangle (x, y, width, height), lineThickness); | |||
| } | |||
| void Graphics::drawRect (Rectangle<int> r, int lineThickness) const | |||
| { | |||
| drawRect (r.toFloat(), (float) lineThickness); | |||
| } | |||
| void Graphics::drawRect (Rectangle<float> r, const float lineThickness) const | |||
| { | |||
| jassert (r.getWidth() >= 0.0f && r.getHeight() >= 0.0f); | |||
| RectangleList<float> rects; | |||
| rects.addWithoutMerging (r.removeFromTop (lineThickness)); | |||
| rects.addWithoutMerging (r.removeFromBottom (lineThickness)); | |||
| rects.addWithoutMerging (r.removeFromLeft (lineThickness)); | |||
| rects.addWithoutMerging (r.removeFromRight (lineThickness)); | |||
| context.fillRectList (rects); | |||
| } | |||
| //============================================================================== | |||
| void Graphics::fillEllipse (Rectangle<float> area) const | |||
| { | |||
| Path p; | |||
| p.addEllipse (area); | |||
| fillPath (p); | |||
| } | |||
| void Graphics::fillEllipse (float x, float y, float w, float h) const | |||
| { | |||
| fillEllipse (coordsToRectangle (x, y, w, h)); | |||
| } | |||
| void Graphics::drawEllipse (float x, float y, float width, float height, float lineThickness) const | |||
| { | |||
| drawEllipse (coordsToRectangle (x, y, width, height), lineThickness); | |||
| } | |||
| void Graphics::drawEllipse (Rectangle<float> area, float lineThickness) const | |||
| { | |||
| Path p; | |||
| if (area.getWidth() == area.getHeight()) | |||
| { | |||
| // For a circle, we can avoid having to generate a stroke | |||
| p.addEllipse (area.expanded (lineThickness * 0.5f)); | |||
| p.addEllipse (area.reduced (lineThickness * 0.5f)); | |||
| p.setUsingNonZeroWinding (false); | |||
| fillPath (p); | |||
| } | |||
| else | |||
| { | |||
| p.addEllipse (area); | |||
| strokePath (p, PathStrokeType (lineThickness)); | |||
| } | |||
| } | |||
| void Graphics::fillRoundedRectangle (float x, float y, float width, float height, float cornerSize) const | |||
| { | |||
| fillRoundedRectangle (coordsToRectangle (x, y, width, height), cornerSize); | |||
| } | |||
| void Graphics::fillRoundedRectangle (Rectangle<float> r, const float cornerSize) const | |||
| { | |||
| Path p; | |||
| p.addRoundedRectangle (r, cornerSize); | |||
| fillPath (p); | |||
| } | |||
| void Graphics::drawRoundedRectangle (float x, float y, float width, float height, | |||
| float cornerSize, float lineThickness) const | |||
| { | |||
| drawRoundedRectangle (coordsToRectangle (x, y, width, height), cornerSize, lineThickness); | |||
| } | |||
| void Graphics::drawRoundedRectangle (Rectangle<float> r, float cornerSize, float lineThickness) const | |||
| { | |||
| Path p; | |||
| p.addRoundedRectangle (r, cornerSize); | |||
| strokePath (p, PathStrokeType (lineThickness)); | |||
| } | |||
| void Graphics::drawArrow (Line<float> line, float lineThickness, float arrowheadWidth, float arrowheadLength) const | |||
| { | |||
| Path p; | |||
| p.addArrow (line, lineThickness, arrowheadWidth, arrowheadLength); | |||
| fillPath (p); | |||
| } | |||
| void Graphics::fillCheckerBoard (Rectangle<int> area, | |||
| const int checkWidth, const int checkHeight, | |||
| Colour colour1, Colour colour2) const | |||
| { | |||
| jassert (checkWidth > 0 && checkHeight > 0); // can't be zero or less! | |||
| if (checkWidth > 0 && checkHeight > 0) | |||
| { | |||
| context.saveState(); | |||
| if (colour1 == colour2) | |||
| { | |||
| context.setFill (colour1); | |||
| context.fillRect (area, false); | |||
| } | |||
| else | |||
| { | |||
| auto clipped = context.getClipBounds().getIntersection (area); | |||
| if (! clipped.isEmpty()) | |||
| { | |||
| context.clipToRectangle (clipped); | |||
| const int checkNumX = (clipped.getX() - area.getX()) / checkWidth; | |||
| const int checkNumY = (clipped.getY() - area.getY()) / checkHeight; | |||
| const int startX = area.getX() + checkNumX * checkWidth; | |||
| const int startY = area.getY() + checkNumY * checkHeight; | |||
| const int right = clipped.getRight(); | |||
| const int bottom = clipped.getBottom(); | |||
| for (int i = 0; i < 2; ++i) | |||
| { | |||
| context.setFill (i == ((checkNumX ^ checkNumY) & 1) ? colour1 : colour2); | |||
| int cy = i; | |||
| for (int y = startY; y < bottom; y += checkHeight) | |||
| for (int x = startX + (cy++ & 1) * checkWidth; x < right; x += checkWidth * 2) | |||
| context.fillRect (Rectangle<int> (x, y, checkWidth, checkHeight), false); | |||
| } | |||
| } | |||
| } | |||
| context.restoreState(); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void Graphics::drawVerticalLine (const int x, float top, float bottom) const | |||
| { | |||
| if (top < bottom) | |||
| context.fillRect (Rectangle<float> ((float) x, top, 1.0f, bottom - top)); | |||
| } | |||
| void Graphics::drawHorizontalLine (const int y, float left, float right) const | |||
| { | |||
| if (left < right) | |||
| context.fillRect (Rectangle<float> (left, (float) y, right - left, 1.0f)); | |||
| } | |||
| void Graphics::drawLine (Line<float> line) const | |||
| { | |||
| context.drawLine (line); | |||
| } | |||
| void Graphics::drawLine (float x1, float y1, float x2, float y2) const | |||
| { | |||
| context.drawLine (Line<float> (x1, y1, x2, y2)); | |||
| } | |||
| void Graphics::drawLine (float x1, float y1, float x2, float y2, float lineThickness) const | |||
| { | |||
| drawLine (Line<float> (x1, y1, x2, y2), lineThickness); | |||
| } | |||
| void Graphics::drawLine (Line<float> line, const float lineThickness) const | |||
| { | |||
| Path p; | |||
| p.addLineSegment (line, lineThickness); | |||
| fillPath (p); | |||
| } | |||
| void Graphics::drawDashedLine (Line<float> line, const float* dashLengths, | |||
| int numDashLengths, float lineThickness, int n) const | |||
| { | |||
| jassert (n >= 0 && n < numDashLengths); // your start index must be valid! | |||
| const Point<double> delta ((line.getEnd() - line.getStart()).toDouble()); | |||
| const double totalLen = delta.getDistanceFromOrigin(); | |||
| if (totalLen >= 0.1) | |||
| { | |||
| const double onePixAlpha = 1.0 / totalLen; | |||
| for (double alpha = 0.0; alpha < 1.0;) | |||
| { | |||
| jassert (dashLengths[n] > 0); // can't have zero-length dashes! | |||
| const double lastAlpha = alpha; | |||
| alpha += dashLengths [n] * onePixAlpha; | |||
| n = (n + 1) % numDashLengths; | |||
| if ((n & 1) != 0) | |||
| { | |||
| const Line<float> segment (line.getStart() + (delta * lastAlpha).toFloat(), | |||
| line.getStart() + (delta * jmin (1.0, alpha)).toFloat()); | |||
| if (lineThickness != 1.0f) | |||
| drawLine (segment, lineThickness); | |||
| else | |||
| context.drawLine (segment); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void Graphics::setImageResamplingQuality (const Graphics::ResamplingQuality newQuality) | |||
| { | |||
| saveStateIfPending(); | |||
| context.setInterpolationQuality (newQuality); | |||
| } | |||
| //============================================================================== | |||
| void Graphics::drawImageAt (const Image& imageToDraw, int x, int y, bool fillAlphaChannel) const | |||
| { | |||
| drawImageTransformed (imageToDraw, | |||
| AffineTransform::translation ((float) x, (float) y), | |||
| fillAlphaChannel); | |||
| } | |||
| void Graphics::drawImage (const Image& imageToDraw, Rectangle<float> targetArea, | |||
| RectanglePlacement placementWithinTarget, bool fillAlphaChannelWithCurrentBrush) const | |||
| { | |||
| if (imageToDraw.isValid()) | |||
| drawImageTransformed (imageToDraw, | |||
| placementWithinTarget.getTransformToFit (imageToDraw.getBounds().toFloat(), targetArea), | |||
| fillAlphaChannelWithCurrentBrush); | |||
| } | |||
| void Graphics::drawImageWithin (const Image& imageToDraw, int dx, int dy, int dw, int dh, | |||
| RectanglePlacement placementWithinTarget, bool fillAlphaChannelWithCurrentBrush) const | |||
| { | |||
| drawImage (imageToDraw, coordsToRectangle (dx, dy, dw, dh).toFloat(), | |||
| placementWithinTarget, fillAlphaChannelWithCurrentBrush); | |||
| } | |||
| void Graphics::drawImage (const Image& imageToDraw, | |||
| int dx, int dy, int dw, int dh, | |||
| int sx, int sy, int sw, int sh, | |||
| const bool fillAlphaChannelWithCurrentBrush) const | |||
| { | |||
| if (imageToDraw.isValid() && context.clipRegionIntersects (coordsToRectangle (dx, dy, dw, dh))) | |||
| drawImageTransformed (imageToDraw.getClippedImage (coordsToRectangle (sx, sy, sw, sh)), | |||
| AffineTransform::scale (dw / (float) sw, dh / (float) sh) | |||
| .translated ((float) dx, (float) dy), | |||
| fillAlphaChannelWithCurrentBrush); | |||
| } | |||
| void Graphics::drawImageTransformed (const Image& imageToDraw, | |||
| const AffineTransform& transform, | |||
| const bool fillAlphaChannelWithCurrentBrush) const | |||
| { | |||
| if (imageToDraw.isValid() && ! context.isClipEmpty()) | |||
| { | |||
| if (fillAlphaChannelWithCurrentBrush) | |||
| { | |||
| context.saveState(); | |||
| context.clipToImageAlpha (imageToDraw, transform); | |||
| fillAll(); | |||
| context.restoreState(); | |||
| } | |||
| else | |||
| { | |||
| context.drawImage (imageToDraw, transform); | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| Graphics::ScopedSaveState::ScopedSaveState (Graphics& g) : context (g) | |||
| { | |||
| context.saveState(); | |||
| } | |||
| Graphics::ScopedSaveState::~ScopedSaveState() | |||
| { | |||
| context.restoreState(); | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,746 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| A graphics context, used for drawing a component or image. | |||
| When a Component needs painting, a Graphics context is passed to its | |||
| Component::paint() method, and this you then call methods within this | |||
| object to actually draw the component's content. | |||
| A Graphics can also be created from an image, to allow drawing directly onto | |||
| that image. | |||
| @see Component::paint | |||
| */ | |||
| class JUCE_API Graphics | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a Graphics object to draw directly onto the given image. | |||
| The graphics object that is created will be set up to draw onto the image, | |||
| with the context's clipping area being the entire size of the image, and its | |||
| origin being the image's origin. To draw into a subsection of an image, use the | |||
| reduceClipRegion() and setOrigin() methods. | |||
| Obviously you shouldn't delete the image before this context is deleted. | |||
| */ | |||
| explicit Graphics (const Image& imageToDrawOnto); | |||
| /** Destructor. */ | |||
| ~Graphics(); | |||
| //============================================================================== | |||
| /** Changes the current drawing colour. | |||
| This sets the colour that will now be used for drawing operations - it also | |||
| sets the opacity to that of the colour passed-in. | |||
| If a brush is being used when this method is called, the brush will be deselected, | |||
| and any subsequent drawing will be done with a solid colour brush instead. | |||
| @see setOpacity | |||
| */ | |||
| void setColour (Colour newColour); | |||
| /** Changes the opacity to use with the current colour. | |||
| If a solid colour is being used for drawing, this changes its opacity | |||
| to this new value (i.e. it doesn't multiply the colour's opacity by this amount). | |||
| If a gradient is being used, this will have no effect on it. | |||
| A value of 0.0 is completely transparent, 1.0 is completely opaque. | |||
| */ | |||
| void setOpacity (float newOpacity); | |||
| /** Sets the context to use a gradient for its fill pattern. | |||
| */ | |||
| void setGradientFill (const ColourGradient& gradient); | |||
| /** Sets the context to use a tiled image pattern for filling. | |||
| Make sure that you don't delete this image while it's still being used by | |||
| this context! | |||
| */ | |||
| void setTiledImageFill (const Image& imageToUse, | |||
| int anchorX, int anchorY, | |||
| float opacity); | |||
| /** Changes the current fill settings. | |||
| @see setColour, setGradientFill, setTiledImageFill | |||
| */ | |||
| void setFillType (const FillType& newFill); | |||
| //============================================================================== | |||
| /** Changes the font to use for subsequent text-drawing functions. | |||
| @see drawSingleLineText, drawMultiLineText, drawText, drawFittedText | |||
| */ | |||
| void setFont (const Font& newFont); | |||
| /** Changes the size of the currently-selected font. | |||
| This is a convenient shortcut that changes the context's current font to a | |||
| different size. The typeface won't be changed. | |||
| @see Font | |||
| */ | |||
| void setFont (float newFontHeight); | |||
| /** Returns the currently selected font. */ | |||
| Font getCurrentFont() const; | |||
| /** Draws a one-line text string. | |||
| This will use the current colour (or brush) to fill the text. The font is the last | |||
| one specified by setFont(). | |||
| @param text the string to draw | |||
| @param startX the position to draw the left-hand edge of the text | |||
| @param baselineY the position of the text's baseline | |||
| @param justification the horizontal flags indicate which end of the text string is | |||
| anchored at the specified point. | |||
| @see drawMultiLineText, drawText, drawFittedText, GlyphArrangement::addLineOfText | |||
| */ | |||
| void drawSingleLineText (const String& text, | |||
| int startX, int baselineY, | |||
| Justification justification = Justification::left) const; | |||
| /** Draws text across multiple lines. | |||
| This will break the text onto a new line where there's a new-line or | |||
| carriage-return character, or at a word-boundary when the text becomes wider | |||
| than the size specified by the maximumLineWidth parameter. | |||
| @see setFont, drawSingleLineText, drawFittedText, GlyphArrangement::addJustifiedText | |||
| */ | |||
| void drawMultiLineText (const String& text, | |||
| int startX, int baselineY, | |||
| int maximumLineWidth) const; | |||
| /** Draws a line of text within a specified rectangle. | |||
| The text will be positioned within the rectangle based on the justification | |||
| flags passed-in. If the string is too long to fit inside the rectangle, it will | |||
| either be truncated or will have ellipsis added to its end (if the useEllipsesIfTooBig | |||
| flag is true). | |||
| @see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText | |||
| */ | |||
| void drawText (const String& text, | |||
| int x, int y, int width, int height, | |||
| Justification justificationType, | |||
| bool useEllipsesIfTooBig = true) const; | |||
| /** Draws a line of text within a specified rectangle. | |||
| The text will be positioned within the rectangle based on the justification | |||
| flags passed-in. If the string is too long to fit inside the rectangle, it will | |||
| either be truncated or will have ellipsis added to its end (if the useEllipsesIfTooBig | |||
| flag is true). | |||
| @see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText | |||
| */ | |||
| void drawText (const String& text, | |||
| Rectangle<int> area, | |||
| Justification justificationType, | |||
| bool useEllipsesIfTooBig = true) const; | |||
| /** Draws a line of text within a specified rectangle. | |||
| The text will be positioned within the rectangle based on the justification | |||
| flags passed-in. If the string is too long to fit inside the rectangle, it will | |||
| either be truncated or will have ellipsis added to its end (if the useEllipsesIfTooBig | |||
| flag is true). | |||
| @see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText | |||
| */ | |||
| void drawText (const String& text, | |||
| Rectangle<float> area, | |||
| Justification justificationType, | |||
| bool useEllipsesIfTooBig = true) const; | |||
| /** Tries to draw a text string inside a given space. | |||
| This does its best to make the given text readable within the specified rectangle, | |||
| so it useful for labelling things. | |||
| If the text is too big, it'll be squashed horizontally or broken over multiple lines | |||
| if the maximumLinesToUse value allows this. If the text just won't fit into the space, | |||
| it'll cram as much as possible in there, and put some ellipsis at the end to show that | |||
| it's been truncated. | |||
| A Justification parameter lets you specify how the text is laid out within the rectangle, | |||
| both horizontally and vertically. | |||
| The minimumHorizontalScale parameter specifies how much the text can be squashed horizontally | |||
| to try to squeeze it into the space. If you don't want any horizontal scaling to occur, you | |||
| can set this value to 1.0f. Pass 0 if you want it to use a default value. | |||
| @see GlyphArrangement::addFittedText | |||
| */ | |||
| void drawFittedText (const String& text, | |||
| int x, int y, int width, int height, | |||
| Justification justificationFlags, | |||
| int maximumNumberOfLines, | |||
| float minimumHorizontalScale = 0.0f) const; | |||
| /** Tries to draw a text string inside a given space. | |||
| This does its best to make the given text readable within the specified rectangle, | |||
| so it useful for labelling things. | |||
| If the text is too big, it'll be squashed horizontally or broken over multiple lines | |||
| if the maximumLinesToUse value allows this. If the text just won't fit into the space, | |||
| it'll cram as much as possible in there, and put some ellipsis at the end to show that | |||
| it's been truncated. | |||
| A Justification parameter lets you specify how the text is laid out within the rectangle, | |||
| both horizontally and vertically. | |||
| The minimumHorizontalScale parameter specifies how much the text can be squashed horizontally | |||
| to try to squeeze it into the space. If you don't want any horizontal scaling to occur, you | |||
| can set this value to 1.0f. Pass 0 if you want it to use a default value. | |||
| @see GlyphArrangement::addFittedText | |||
| */ | |||
| void drawFittedText (const String& text, | |||
| Rectangle<int> area, | |||
| Justification justificationFlags, | |||
| int maximumNumberOfLines, | |||
| float minimumHorizontalScale = 0.0f) const; | |||
| //============================================================================== | |||
| /** Fills the context's entire clip region with the current colour or brush. | |||
| (See also the fillAll (Colour) method which is a quick way of filling | |||
| it with a given colour). | |||
| */ | |||
| void fillAll() const; | |||
| /** Fills the context's entire clip region with a given colour. | |||
| This leaves the context's current colour and brush unchanged, it just | |||
| uses the specified colour temporarily. | |||
| */ | |||
| void fillAll (Colour colourToUse) const; | |||
| //============================================================================== | |||
| /** Fills a rectangle with the current colour or brush. | |||
| @see drawRect, fillRoundedRectangle | |||
| */ | |||
| void fillRect (Rectangle<int> rectangle) const; | |||
| /** Fills a rectangle with the current colour or brush. | |||
| @see drawRect, fillRoundedRectangle | |||
| */ | |||
| void fillRect (Rectangle<float> rectangle) const; | |||
| /** Fills a rectangle with the current colour or brush. | |||
| @see drawRect, fillRoundedRectangle | |||
| */ | |||
| void fillRect (int x, int y, int width, int height) const; | |||
| /** Fills a rectangle with the current colour or brush. | |||
| @see drawRect, fillRoundedRectangle | |||
| */ | |||
| void fillRect (float x, float y, float width, float height) const; | |||
| /** Fills a set of rectangles using the current colour or brush. | |||
| If you have a lot of rectangles to draw, it may be more efficient | |||
| to create a RectangleList and use this method than to call fillRect() | |||
| multiple times. | |||
| */ | |||
| void fillRectList (const RectangleList<float>& rectangles) const; | |||
| /** Fills a set of rectangles using the current colour or brush. | |||
| If you have a lot of rectangles to draw, it may be more efficient | |||
| to create a RectangleList and use this method than to call fillRect() | |||
| multiple times. | |||
| */ | |||
| void fillRectList (const RectangleList<int>& rectangles) const; | |||
| /** Uses the current colour or brush to fill a rectangle with rounded corners. | |||
| @see drawRoundedRectangle, Path::addRoundedRectangle | |||
| */ | |||
| void fillRoundedRectangle (float x, float y, float width, float height, | |||
| float cornerSize) const; | |||
| /** Uses the current colour or brush to fill a rectangle with rounded corners. | |||
| @see drawRoundedRectangle, Path::addRoundedRectangle | |||
| */ | |||
| void fillRoundedRectangle (Rectangle<float> rectangle, | |||
| float cornerSize) const; | |||
| /** Fills a rectangle with a checkerboard pattern, alternating between two colours. */ | |||
| void fillCheckerBoard (Rectangle<int> area, | |||
| int checkWidth, int checkHeight, | |||
| Colour colour1, Colour colour2) const; | |||
| /** Draws a rectangular outline, using the current colour or brush. | |||
| The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards. | |||
| @see fillRect | |||
| */ | |||
| void drawRect (int x, int y, int width, int height, int lineThickness = 1) const; | |||
| /** Draws a rectangular outline, using the current colour or brush. | |||
| The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards. | |||
| @see fillRect | |||
| */ | |||
| void drawRect (float x, float y, float width, float height, float lineThickness = 1.0f) const; | |||
| /** Draws a rectangular outline, using the current colour or brush. | |||
| The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards. | |||
| @see fillRect | |||
| */ | |||
| void drawRect (Rectangle<int> rectangle, int lineThickness = 1) const; | |||
| /** Draws a rectangular outline, using the current colour or brush. | |||
| The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards. | |||
| @see fillRect | |||
| */ | |||
| void drawRect (Rectangle<float> rectangle, float lineThickness = 1.0f) const; | |||
| /** Uses the current colour or brush to draw the outline of a rectangle with rounded corners. | |||
| @see fillRoundedRectangle, Path::addRoundedRectangle | |||
| */ | |||
| void drawRoundedRectangle (float x, float y, float width, float height, | |||
| float cornerSize, float lineThickness) const; | |||
| /** Uses the current colour or brush to draw the outline of a rectangle with rounded corners. | |||
| @see fillRoundedRectangle, Path::addRoundedRectangle | |||
| */ | |||
| void drawRoundedRectangle (Rectangle<float> rectangle, | |||
| float cornerSize, float lineThickness) const; | |||
| //============================================================================== | |||
| /** Fills an ellipse with the current colour or brush. | |||
| The ellipse is drawn to fit inside the given rectangle. | |||
| @see drawEllipse, Path::addEllipse | |||
| */ | |||
| void fillEllipse (float x, float y, float width, float height) const; | |||
| /** Fills an ellipse with the current colour or brush. | |||
| The ellipse is drawn to fit inside the given rectangle. | |||
| @see drawEllipse, Path::addEllipse | |||
| */ | |||
| void fillEllipse (Rectangle<float> area) const; | |||
| /** Draws an elliptical stroke using the current colour or brush. | |||
| @see fillEllipse, Path::addEllipse | |||
| */ | |||
| void drawEllipse (float x, float y, float width, float height, | |||
| float lineThickness) const; | |||
| /** Draws an elliptical stroke using the current colour or brush. | |||
| @see fillEllipse, Path::addEllipse | |||
| */ | |||
| void drawEllipse (Rectangle<float> area, float lineThickness) const; | |||
| //============================================================================== | |||
| /** Draws a line between two points. | |||
| The line is 1 pixel wide and drawn with the current colour or brush. | |||
| TIP: If you're trying to draw horizontal or vertical lines, don't use this - | |||
| it's better to use fillRect() instead unless you really need an angled line. | |||
| */ | |||
| void drawLine (float startX, float startY, float endX, float endY) const; | |||
| /** Draws a line between two points with a given thickness. | |||
| TIP: If you're trying to draw horizontal or vertical lines, don't use this - | |||
| it's better to use fillRect() instead unless you really need an angled line. | |||
| @see Path::addLineSegment | |||
| */ | |||
| void drawLine (float startX, float startY, float endX, float endY, float lineThickness) const; | |||
| /** Draws a line between two points. | |||
| The line is 1 pixel wide and drawn with the current colour or brush. | |||
| TIP: If you're trying to draw horizontal or vertical lines, don't use this - | |||
| it's better to use fillRect() instead unless you really need an angled line. | |||
| */ | |||
| void drawLine (Line<float> line) const; | |||
| /** Draws a line between two points with a given thickness. | |||
| @see Path::addLineSegment | |||
| TIP: If you're trying to draw horizontal or vertical lines, don't use this - | |||
| it's better to use fillRect() instead unless you really need an angled line. | |||
| */ | |||
| void drawLine (Line<float> line, float lineThickness) const; | |||
| /** Draws a dashed line using a custom set of dash-lengths. | |||
| @param line the line to draw | |||
| @param dashLengths a series of lengths to specify the on/off lengths - e.g. | |||
| { 4, 5, 6, 7 } will draw a line of 4 pixels, skip 5 pixels, | |||
| draw 6 pixels, skip 7 pixels, and then repeat. | |||
| @param numDashLengths the number of elements in the array (this must be an even number). | |||
| @param lineThickness the thickness of the line to draw | |||
| @param dashIndexToStartFrom the index in the dash-length array to use for the first segment | |||
| @see PathStrokeType::createDashedStroke | |||
| */ | |||
| void drawDashedLine (Line<float> line, | |||
| const float* dashLengths, int numDashLengths, | |||
| float lineThickness = 1.0f, | |||
| int dashIndexToStartFrom = 0) const; | |||
| /** Draws a vertical line of pixels at a given x position. | |||
| The x position is an integer, but the top and bottom of the line can be sub-pixel | |||
| positions, and these will be anti-aliased if necessary. | |||
| The bottom parameter must be greater than or equal to the top parameter. | |||
| */ | |||
| void drawVerticalLine (int x, float top, float bottom) const; | |||
| /** Draws a horizontal line of pixels at a given y position. | |||
| The y position is an integer, but the left and right ends of the line can be sub-pixel | |||
| positions, and these will be anti-aliased if necessary. | |||
| The right parameter must be greater than or equal to the left parameter. | |||
| */ | |||
| void drawHorizontalLine (int y, float left, float right) const; | |||
| //============================================================================== | |||
| /** Fills a path using the currently selected colour or brush. */ | |||
| void fillPath (const Path& path) const; | |||
| /** Fills a path using the currently selected colour or brush, and adds a transform. */ | |||
| void fillPath (const Path& path, const AffineTransform& transform) const; | |||
| /** Draws a path's outline using the currently selected colour or brush. */ | |||
| void strokePath (const Path& path, | |||
| const PathStrokeType& strokeType, | |||
| const AffineTransform& transform = {}) const; | |||
| /** Draws a line with an arrowhead at its end. | |||
| @param line the line to draw | |||
| @param lineThickness the thickness of the line | |||
| @param arrowheadWidth the width of the arrow head (perpendicular to the line) | |||
| @param arrowheadLength the length of the arrow head (along the length of the line) | |||
| */ | |||
| void drawArrow (Line<float> line, | |||
| float lineThickness, | |||
| float arrowheadWidth, | |||
| float arrowheadLength) const; | |||
| //============================================================================== | |||
| /** Types of rendering quality that can be specified when drawing images. | |||
| @see Graphics::setImageResamplingQuality | |||
| */ | |||
| enum ResamplingQuality | |||
| { | |||
| lowResamplingQuality = 0, /**< Just uses a nearest-neighbour algorithm for resampling. */ | |||
| mediumResamplingQuality = 1, /**< Uses bilinear interpolation for upsampling and area-averaging for downsampling. */ | |||
| highResamplingQuality = 2, /**< Uses bicubic interpolation for upsampling and area-averaging for downsampling. */ | |||
| }; | |||
| /** Changes the quality that will be used when resampling images. | |||
| By default a Graphics object will be set to mediumRenderingQuality. | |||
| @see Graphics::drawImage, Graphics::drawImageTransformed, Graphics::drawImageWithin | |||
| */ | |||
| void setImageResamplingQuality (const ResamplingQuality newQuality); | |||
| /** Draws an image. | |||
| This will draw the whole of an image, positioning its top-left corner at the | |||
| given coordinates, and keeping its size the same. This is the simplest image | |||
| drawing method - the others give more control over the scaling and clipping | |||
| of the images. | |||
| Images are composited using the context's current opacity, so if you | |||
| don't want it to be drawn semi-transparently, be sure to call setOpacity (1.0f) | |||
| (or setColour() with an opaque colour) before drawing images. | |||
| */ | |||
| void drawImageAt (const Image& imageToDraw, int topLeftX, int topLeftY, | |||
| bool fillAlphaChannelWithCurrentBrush = false) const; | |||
| /** Draws part of an image, rescaling it to fit in a given target region. | |||
| The specified area of the source image is rescaled and drawn to fill the | |||
| specifed destination rectangle. | |||
| Images are composited using the context's current opacity, so if you | |||
| don't want it to be drawn semi-transparently, be sure to call setOpacity (1.0f) | |||
| (or setColour() with an opaque colour) before drawing images. | |||
| @param imageToDraw the image to overlay | |||
| @param destX the left of the destination rectangle | |||
| @param destY the top of the destination rectangle | |||
| @param destWidth the width of the destination rectangle | |||
| @param destHeight the height of the destination rectangle | |||
| @param sourceX the left of the rectangle to copy from the source image | |||
| @param sourceY the top of the rectangle to copy from the source image | |||
| @param sourceWidth the width of the rectangle to copy from the source image | |||
| @param sourceHeight the height of the rectangle to copy from the source image | |||
| @param fillAlphaChannelWithCurrentBrush if true, then instead of drawing the source image's pixels, | |||
| the source image's alpha channel is used as a mask with | |||
| which to fill the destination using the current colour | |||
| or brush. (If the source is has no alpha channel, then | |||
| it will just fill the target with a solid rectangle) | |||
| @see setImageResamplingQuality, drawImageAt, drawImageWithin, fillAlphaMap | |||
| */ | |||
| void drawImage (const Image& imageToDraw, | |||
| int destX, int destY, int destWidth, int destHeight, | |||
| int sourceX, int sourceY, int sourceWidth, int sourceHeight, | |||
| bool fillAlphaChannelWithCurrentBrush = false) const; | |||
| /** Draws an image, having applied an affine transform to it. | |||
| This lets you throw the image around in some wacky ways, rotate it, shear, | |||
| scale it, etc. | |||
| Images are composited using the context's current opacity, so if you | |||
| don't want it to be drawn semi-transparently, be sure to call setOpacity (1.0f) | |||
| (or setColour() with an opaque colour) before drawing images. | |||
| If fillAlphaChannelWithCurrentBrush is set to true, then the image's RGB channels | |||
| are ignored and it is filled with the current brush, masked by its alpha channel. | |||
| If you want to render only a subsection of an image, use Image::getClippedImage() to | |||
| create the section that you need. | |||
| @see setImageResamplingQuality, drawImage | |||
| */ | |||
| void drawImageTransformed (const Image& imageToDraw, | |||
| const AffineTransform& transform, | |||
| bool fillAlphaChannelWithCurrentBrush = false) const; | |||
| /** Draws an image to fit within a designated rectangle. | |||
| @param imageToDraw the source image to draw | |||
| @param targetArea the target rectangle to fit it into | |||
| @param placementWithinTarget this specifies how the image should be positioned | |||
| within the target rectangle - see the RectanglePlacement | |||
| class for more details about this. | |||
| @param fillAlphaChannelWithCurrentBrush if true, then instead of drawing the image, just its | |||
| alpha channel will be used as a mask with which to | |||
| draw with the current brush or colour. This is | |||
| similar to fillAlphaMap(), and see also drawImage() | |||
| @see drawImage, drawImageTransformed, drawImageAt, RectanglePlacement | |||
| */ | |||
| void drawImage (const Image& imageToDraw, Rectangle<float> targetArea, | |||
| RectanglePlacement placementWithinTarget = RectanglePlacement::stretchToFit, | |||
| bool fillAlphaChannelWithCurrentBrush = false) const; | |||
| /** Draws an image to fit within a designated rectangle. | |||
| If the image is too big or too small for the space, it will be rescaled | |||
| to fit as nicely as it can do without affecting its aspect ratio. It will | |||
| then be placed within the target rectangle according to the justification flags | |||
| specified. | |||
| @param imageToDraw the source image to draw | |||
| @param destX top-left of the target rectangle to fit it into | |||
| @param destY top-left of the target rectangle to fit it into | |||
| @param destWidth size of the target rectangle to fit the image into | |||
| @param destHeight size of the target rectangle to fit the image into | |||
| @param placementWithinTarget this specifies how the image should be positioned | |||
| within the target rectangle - see the RectanglePlacement | |||
| class for more details about this. | |||
| @param fillAlphaChannelWithCurrentBrush if true, then instead of drawing the image, just its | |||
| alpha channel will be used as a mask with which to | |||
| draw with the current brush or colour. This is | |||
| similar to fillAlphaMap(), and see also drawImage() | |||
| @see setImageResamplingQuality, drawImage, drawImageTransformed, drawImageAt, RectanglePlacement | |||
| */ | |||
| void drawImageWithin (const Image& imageToDraw, | |||
| int destX, int destY, int destWidth, int destHeight, | |||
| RectanglePlacement placementWithinTarget, | |||
| bool fillAlphaChannelWithCurrentBrush = false) const; | |||
| //============================================================================== | |||
| /** Returns the position of the bounding box for the current clipping region. | |||
| @see getClipRegion, clipRegionIntersects | |||
| */ | |||
| Rectangle<int> getClipBounds() const; | |||
| /** Checks whether a rectangle overlaps the context's clipping region. | |||
| If this returns false, no part of the given area can be drawn onto, so this | |||
| method can be used to optimise a component's paint() method, by letting it | |||
| avoid drawing complex objects that aren't within the region being repainted. | |||
| */ | |||
| bool clipRegionIntersects (Rectangle<int> area) const; | |||
| /** Intersects the current clipping region with another region. | |||
| @returns true if the resulting clipping region is non-zero in size | |||
| @see setOrigin, clipRegionIntersects | |||
| */ | |||
| bool reduceClipRegion (int x, int y, int width, int height); | |||
| /** Intersects the current clipping region with another region. | |||
| @returns true if the resulting clipping region is non-zero in size | |||
| @see setOrigin, clipRegionIntersects | |||
| */ | |||
| bool reduceClipRegion (Rectangle<int> area); | |||
| /** Intersects the current clipping region with a rectangle list region. | |||
| @returns true if the resulting clipping region is non-zero in size | |||
| @see setOrigin, clipRegionIntersects | |||
| */ | |||
| bool reduceClipRegion (const RectangleList<int>& clipRegion); | |||
| /** Intersects the current clipping region with a path. | |||
| @returns true if the resulting clipping region is non-zero in size | |||
| @see reduceClipRegion | |||
| */ | |||
| bool reduceClipRegion (const Path& path, const AffineTransform& transform = AffineTransform()); | |||
| /** Intersects the current clipping region with an image's alpha-channel. | |||
| The current clipping path is intersected with the area covered by this image's | |||
| alpha-channel, after the image has been transformed by the specified matrix. | |||
| @param image the image whose alpha-channel should be used. If the image doesn't | |||
| have an alpha-channel, it is treated as entirely opaque. | |||
| @param transform a matrix to apply to the image | |||
| @returns true if the resulting clipping region is non-zero in size | |||
| @see reduceClipRegion | |||
| */ | |||
| bool reduceClipRegion (const Image& image, const AffineTransform& transform); | |||
| /** Excludes a rectangle to stop it being drawn into. */ | |||
| void excludeClipRegion (Rectangle<int> rectangleToExclude); | |||
| /** Returns true if no drawing can be done because the clip region is zero. */ | |||
| bool isClipEmpty() const; | |||
| //============================================================================== | |||
| /** Saves the current graphics state on an internal stack. | |||
| To restore the state, use restoreState(). | |||
| @see ScopedSaveState | |||
| */ | |||
| void saveState(); | |||
| /** Restores a graphics state that was previously saved with saveState(). | |||
| @see ScopedSaveState | |||
| */ | |||
| void restoreState(); | |||
| /** Uses RAII to save and restore the state of a graphics context. | |||
| On construction, this calls Graphics::saveState(), and on destruction it calls | |||
| Graphics::restoreState() on the Graphics object that you supply. | |||
| */ | |||
| class ScopedSaveState | |||
| { | |||
| public: | |||
| ScopedSaveState (Graphics&); | |||
| ~ScopedSaveState(); | |||
| private: | |||
| Graphics& context; | |||
| JUCE_DECLARE_NON_COPYABLE (ScopedSaveState) | |||
| }; | |||
| //============================================================================== | |||
| /** Begins rendering to an off-screen bitmap which will later be flattened onto the current | |||
| context with the given opacity. | |||
| The context uses an internal stack of temporary image layers to do this. When you've | |||
| finished drawing to the layer, call endTransparencyLayer() to complete the operation and | |||
| composite the finished layer. Every call to beginTransparencyLayer() MUST be matched | |||
| by a corresponding call to endTransparencyLayer()! | |||
| This call also saves the current state, and endTransparencyLayer() restores it. | |||
| */ | |||
| void beginTransparencyLayer (float layerOpacity); | |||
| /** Completes a drawing operation to a temporary semi-transparent buffer. | |||
| See beginTransparencyLayer() for more details. | |||
| */ | |||
| void endTransparencyLayer(); | |||
| /** Moves the position of the context's origin. | |||
| This changes the position that the context considers to be (0, 0) to | |||
| the specified position. | |||
| So if you call setOrigin with (100, 100), then the position that was previously | |||
| referred to as (100, 100) will subsequently be considered to be (0, 0). | |||
| @see reduceClipRegion, addTransform | |||
| */ | |||
| void setOrigin (Point<int> newOrigin); | |||
| /** Moves the position of the context's origin. | |||
| This changes the position that the context considers to be (0, 0) to | |||
| the specified position. | |||
| So if you call setOrigin (100, 100), then the position that was previously | |||
| referred to as (100, 100) will subsequently be considered to be (0, 0). | |||
| @see reduceClipRegion, addTransform | |||
| */ | |||
| void setOrigin (int newOriginX, int newOriginY); | |||
| /** Adds a transformation which will be performed on all the graphics operations that | |||
| the context subsequently performs. | |||
| After calling this, all the coordinates that are passed into the context will be | |||
| transformed by this matrix. | |||
| @see setOrigin | |||
| */ | |||
| void addTransform (const AffineTransform& transform); | |||
| /** Resets the current colour, brush, and font to default settings. */ | |||
| void resetToDefaultState(); | |||
| /** Returns true if this context is drawing to a vector-based device, such as a printer. */ | |||
| bool isVectorDevice() const; | |||
| //============================================================================== | |||
| /** Create a graphics that draws with a given low-level renderer. | |||
| This method is intended for use only by people who know what they're doing. | |||
| Note that the LowLevelGraphicsContext will NOT be deleted by this object. | |||
| */ | |||
| Graphics (LowLevelGraphicsContext&) noexcept; | |||
| /** @internal */ | |||
| LowLevelGraphicsContext& getInternalContext() const noexcept { return context; } | |||
| private: | |||
| //============================================================================== | |||
| LowLevelGraphicsContext& context; | |||
| ScopedPointer<LowLevelGraphicsContext> contextToDelete; | |||
| bool saveStatePending = false; | |||
| void saveStateIfPending(); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Graphics) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,101 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Interface class for graphics context objects, used internally by the Graphics class. | |||
| Users are not supposed to create instances of this class directly - do your drawing | |||
| via the Graphics object instead. | |||
| It's a base class for different types of graphics context, that may perform software-based | |||
| or OS-accelerated rendering. | |||
| E.g. the LowLevelGraphicsSoftwareRenderer renders onto an image in memory, but other | |||
| subclasses could render directly to a windows HDC, a Quartz context, or an OpenGL | |||
| context. | |||
| */ | |||
| class JUCE_API LowLevelGraphicsContext | |||
| { | |||
| protected: | |||
| //============================================================================== | |||
| LowLevelGraphicsContext(); | |||
| public: | |||
| virtual ~LowLevelGraphicsContext(); | |||
| /** Returns true if this device is vector-based, e.g. a printer. */ | |||
| virtual bool isVectorDevice() const = 0; | |||
| //============================================================================== | |||
| /** Moves the origin to a new position. | |||
| The coordinates are relative to the current origin, and indicate the new position | |||
| of (0, 0). | |||
| */ | |||
| virtual void setOrigin (Point<int>) = 0; | |||
| virtual void addTransform (const AffineTransform&) = 0; | |||
| virtual float getPhysicalPixelScaleFactor() = 0; | |||
| virtual bool clipToRectangle (const Rectangle<int>&) = 0; | |||
| virtual bool clipToRectangleList (const RectangleList<int>&) = 0; | |||
| virtual void excludeClipRectangle (const Rectangle<int>&) = 0; | |||
| virtual void clipToPath (const Path&, const AffineTransform&) = 0; | |||
| virtual void clipToImageAlpha (const Image&, const AffineTransform&) = 0; | |||
| virtual bool clipRegionIntersects (const Rectangle<int>&) = 0; | |||
| virtual Rectangle<int> getClipBounds() const = 0; | |||
| virtual bool isClipEmpty() const = 0; | |||
| virtual void saveState() = 0; | |||
| virtual void restoreState() = 0; | |||
| virtual void beginTransparencyLayer (float opacity) = 0; | |||
| virtual void endTransparencyLayer() = 0; | |||
| //============================================================================== | |||
| virtual void setFill (const FillType&) = 0; | |||
| virtual void setOpacity (float) = 0; | |||
| virtual void setInterpolationQuality (Graphics::ResamplingQuality) = 0; | |||
| //============================================================================== | |||
| virtual void fillRect (const Rectangle<int>&, bool replaceExistingContents) = 0; | |||
| virtual void fillRect (const Rectangle<float>&) = 0; | |||
| virtual void fillRectList (const RectangleList<float>&) = 0; | |||
| virtual void fillPath (const Path&, const AffineTransform&) = 0; | |||
| virtual void drawImage (const Image&, const AffineTransform&) = 0; | |||
| virtual void drawLine (const Line<float>&) = 0; | |||
| virtual void setFont (const Font&) = 0; | |||
| virtual const Font& getFont() = 0; | |||
| virtual void drawGlyph (int glyphNumber, const AffineTransform&) = 0; | |||
| virtual bool drawTextLayout (const AttributedString&, const Rectangle<float>&) { return false; } | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,540 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| // this will throw an assertion if you try to draw something that's not | |||
| // possible in postscript | |||
| #define WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS 0 | |||
| //============================================================================== | |||
| #if JUCE_DEBUG && WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS | |||
| #define notPossibleInPostscriptAssert jassertfalse | |||
| #else | |||
| #define notPossibleInPostscriptAssert | |||
| #endif | |||
| //============================================================================== | |||
| LowLevelGraphicsPostScriptRenderer::LowLevelGraphicsPostScriptRenderer (OutputStream& resultingPostScript, | |||
| const String& documentTitle, | |||
| const int totalWidth_, | |||
| const int totalHeight_) | |||
| : out (resultingPostScript), | |||
| totalWidth (totalWidth_), | |||
| totalHeight (totalHeight_), | |||
| needToClip (true) | |||
| { | |||
| stateStack.add (new SavedState()); | |||
| stateStack.getLast()->clip = Rectangle<int> (totalWidth_, totalHeight_); | |||
| const float scale = jmin ((520.0f / totalWidth_), (750.0f / totalHeight)); | |||
| out << "%!PS-Adobe-3.0 EPSF-3.0" | |||
| "\n%%BoundingBox: 0 0 600 824" | |||
| "\n%%Pages: 0" | |||
| "\n%%Creator: ROLI Ltd. JUCE" | |||
| "\n%%Title: " << documentTitle << | |||
| "\n%%CreationDate: none" | |||
| "\n%%LanguageLevel: 2" | |||
| "\n%%EndComments" | |||
| "\n%%BeginProlog" | |||
| "\n%%BeginResource: JRes" | |||
| "\n/bd {bind def} bind def" | |||
| "\n/c {setrgbcolor} bd" | |||
| "\n/m {moveto} bd" | |||
| "\n/l {lineto} bd" | |||
| "\n/rl {rlineto} bd" | |||
| "\n/ct {curveto} bd" | |||
| "\n/cp {closepath} bd" | |||
| "\n/pr {3 index 3 index moveto 1 index 0 rlineto 0 1 index rlineto pop neg 0 rlineto pop pop closepath} bd" | |||
| "\n/doclip {initclip newpath} bd" | |||
| "\n/endclip {clip newpath} bd" | |||
| "\n%%EndResource" | |||
| "\n%%EndProlog" | |||
| "\n%%BeginSetup" | |||
| "\n%%EndSetup" | |||
| "\n%%Page: 1 1" | |||
| "\n%%BeginPageSetup" | |||
| "\n%%EndPageSetup\n\n" | |||
| << "40 800 translate\n" | |||
| << scale << ' ' << scale << " scale\n\n"; | |||
| } | |||
| LowLevelGraphicsPostScriptRenderer::~LowLevelGraphicsPostScriptRenderer() | |||
| { | |||
| } | |||
| //============================================================================== | |||
| bool LowLevelGraphicsPostScriptRenderer::isVectorDevice() const | |||
| { | |||
| return true; | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::setOrigin (Point<int> o) | |||
| { | |||
| if (! o.isOrigin()) | |||
| { | |||
| stateStack.getLast()->xOffset += o.x; | |||
| stateStack.getLast()->yOffset += o.y; | |||
| needToClip = true; | |||
| } | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::addTransform (const AffineTransform& /*transform*/) | |||
| { | |||
| //xxx | |||
| jassertfalse; | |||
| } | |||
| float LowLevelGraphicsPostScriptRenderer::getPhysicalPixelScaleFactor() { return 1.0f; } | |||
| bool LowLevelGraphicsPostScriptRenderer::clipToRectangle (const Rectangle<int>& r) | |||
| { | |||
| needToClip = true; | |||
| return stateStack.getLast()->clip.clipTo (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); | |||
| } | |||
| bool LowLevelGraphicsPostScriptRenderer::clipToRectangleList (const RectangleList<int>& clipRegion) | |||
| { | |||
| needToClip = true; | |||
| return stateStack.getLast()->clip.clipTo (clipRegion); | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::excludeClipRectangle (const Rectangle<int>& r) | |||
| { | |||
| needToClip = true; | |||
| stateStack.getLast()->clip.subtract (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::clipToPath (const Path& path, const AffineTransform& transform) | |||
| { | |||
| writeClip(); | |||
| Path p (path); | |||
| p.applyTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset)); | |||
| writePath (p); | |||
| out << "clip\n"; | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::clipToImageAlpha (const Image& /*sourceImage*/, const AffineTransform& /*transform*/) | |||
| { | |||
| needToClip = true; | |||
| jassertfalse; // xxx | |||
| } | |||
| bool LowLevelGraphicsPostScriptRenderer::clipRegionIntersects (const Rectangle<int>& r) | |||
| { | |||
| return stateStack.getLast()->clip.intersectsRectangle (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); | |||
| } | |||
| Rectangle<int> LowLevelGraphicsPostScriptRenderer::getClipBounds() const | |||
| { | |||
| return stateStack.getLast()->clip.getBounds().translated (-stateStack.getLast()->xOffset, | |||
| -stateStack.getLast()->yOffset); | |||
| } | |||
| bool LowLevelGraphicsPostScriptRenderer::isClipEmpty() const | |||
| { | |||
| return stateStack.getLast()->clip.isEmpty(); | |||
| } | |||
| //============================================================================== | |||
| LowLevelGraphicsPostScriptRenderer::SavedState::SavedState() | |||
| : xOffset (0), | |||
| yOffset (0) | |||
| { | |||
| } | |||
| LowLevelGraphicsPostScriptRenderer::SavedState::~SavedState() | |||
| { | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::saveState() | |||
| { | |||
| stateStack.add (new SavedState (*stateStack.getLast())); | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::restoreState() | |||
| { | |||
| jassert (stateStack.size() > 0); | |||
| if (stateStack.size() > 0) | |||
| stateStack.removeLast(); | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::beginTransparencyLayer (float) | |||
| { | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::endTransparencyLayer() | |||
| { | |||
| } | |||
| //============================================================================== | |||
| void LowLevelGraphicsPostScriptRenderer::writeClip() | |||
| { | |||
| if (needToClip) | |||
| { | |||
| needToClip = false; | |||
| out << "doclip "; | |||
| int itemsOnLine = 0; | |||
| for (auto& i : stateStack.getLast()->clip) | |||
| { | |||
| if (++itemsOnLine == 6) | |||
| { | |||
| itemsOnLine = 0; | |||
| out << '\n'; | |||
| } | |||
| out << i.getX() << ' ' << -i.getY() << ' ' | |||
| << i.getWidth() << ' ' << -i.getHeight() << " pr "; | |||
| } | |||
| out << "endclip\n"; | |||
| } | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::writeColour (Colour colour) | |||
| { | |||
| Colour c (Colours::white.overlaidWith (colour)); | |||
| if (lastColour != c) | |||
| { | |||
| lastColour = c; | |||
| out << String (c.getFloatRed(), 3) << ' ' | |||
| << String (c.getFloatGreen(), 3) << ' ' | |||
| << String (c.getFloatBlue(), 3) << " c\n"; | |||
| } | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::writeXY (const float x, const float y) const | |||
| { | |||
| out << String (x, 2) << ' ' | |||
| << String (-y, 2) << ' '; | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::writePath (const Path& path) const | |||
| { | |||
| out << "newpath "; | |||
| float lastX = 0.0f; | |||
| float lastY = 0.0f; | |||
| int itemsOnLine = 0; | |||
| Path::Iterator i (path); | |||
| while (i.next()) | |||
| { | |||
| if (++itemsOnLine == 4) | |||
| { | |||
| itemsOnLine = 0; | |||
| out << '\n'; | |||
| } | |||
| switch (i.elementType) | |||
| { | |||
| case Path::Iterator::startNewSubPath: | |||
| writeXY (i.x1, i.y1); | |||
| lastX = i.x1; | |||
| lastY = i.y1; | |||
| out << "m "; | |||
| break; | |||
| case Path::Iterator::lineTo: | |||
| writeXY (i.x1, i.y1); | |||
| lastX = i.x1; | |||
| lastY = i.y1; | |||
| out << "l "; | |||
| break; | |||
| case Path::Iterator::quadraticTo: | |||
| { | |||
| const float cp1x = lastX + (i.x1 - lastX) * 2.0f / 3.0f; | |||
| const float cp1y = lastY + (i.y1 - lastY) * 2.0f / 3.0f; | |||
| const float cp2x = cp1x + (i.x2 - lastX) / 3.0f; | |||
| const float cp2y = cp1y + (i.y2 - lastY) / 3.0f; | |||
| writeXY (cp1x, cp1y); | |||
| writeXY (cp2x, cp2y); | |||
| writeXY (i.x2, i.y2); | |||
| out << "ct "; | |||
| lastX = i.x2; | |||
| lastY = i.y2; | |||
| } | |||
| break; | |||
| case Path::Iterator::cubicTo: | |||
| writeXY (i.x1, i.y1); | |||
| writeXY (i.x2, i.y2); | |||
| writeXY (i.x3, i.y3); | |||
| out << "ct "; | |||
| lastX = i.x3; | |||
| lastY = i.y3; | |||
| break; | |||
| case Path::Iterator::closePath: | |||
| out << "cp "; | |||
| break; | |||
| default: | |||
| jassertfalse; | |||
| break; | |||
| } | |||
| } | |||
| out << '\n'; | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::writeTransform (const AffineTransform& trans) const | |||
| { | |||
| out << "[ " | |||
| << trans.mat00 << ' ' | |||
| << trans.mat10 << ' ' | |||
| << trans.mat01 << ' ' | |||
| << trans.mat11 << ' ' | |||
| << trans.mat02 << ' ' | |||
| << trans.mat12 << " ] concat "; | |||
| } | |||
| //============================================================================== | |||
| void LowLevelGraphicsPostScriptRenderer::setFill (const FillType& fillType) | |||
| { | |||
| stateStack.getLast()->fillType = fillType; | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::setOpacity (float /*opacity*/) | |||
| { | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::setInterpolationQuality (Graphics::ResamplingQuality /*quality*/) | |||
| { | |||
| } | |||
| //============================================================================== | |||
| void LowLevelGraphicsPostScriptRenderer::fillRect (const Rectangle<int>& r, const bool /*replaceExistingContents*/) | |||
| { | |||
| fillRect (r.toFloat()); | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::fillRect (const Rectangle<float>& r) | |||
| { | |||
| if (stateStack.getLast()->fillType.isColour()) | |||
| { | |||
| writeClip(); | |||
| writeColour (stateStack.getLast()->fillType.colour); | |||
| Rectangle<float> r2 (r.translated ((float) stateStack.getLast()->xOffset, | |||
| (float) stateStack.getLast()->yOffset)); | |||
| out << r2.getX() << ' ' << -r2.getBottom() << ' ' << r2.getWidth() << ' ' << r2.getHeight() << " rectfill\n"; | |||
| } | |||
| else | |||
| { | |||
| Path p; | |||
| p.addRectangle (r); | |||
| fillPath (p, AffineTransform()); | |||
| } | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::fillRectList (const RectangleList<float>& list) | |||
| { | |||
| fillPath (list.toPath(), AffineTransform()); | |||
| } | |||
| //============================================================================== | |||
| void LowLevelGraphicsPostScriptRenderer::fillPath (const Path& path, const AffineTransform& t) | |||
| { | |||
| if (stateStack.getLast()->fillType.isColour()) | |||
| { | |||
| writeClip(); | |||
| Path p (path); | |||
| p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset, | |||
| (float) stateStack.getLast()->yOffset)); | |||
| writePath (p); | |||
| writeColour (stateStack.getLast()->fillType.colour); | |||
| out << "fill\n"; | |||
| } | |||
| else if (stateStack.getLast()->fillType.isGradient()) | |||
| { | |||
| // this doesn't work correctly yet - it could be improved to handle solid gradients, but | |||
| // postscript can't do semi-transparent ones. | |||
| notPossibleInPostscriptAssert; // you can disable this warning by setting the WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS flag at the top of this file | |||
| writeClip(); | |||
| out << "gsave "; | |||
| { | |||
| Path p (path); | |||
| p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset)); | |||
| writePath (p); | |||
| out << "clip\n"; | |||
| } | |||
| const Rectangle<int> bounds (stateStack.getLast()->clip.getBounds()); | |||
| // ideally this would draw lots of lines or ellipses to approximate the gradient, but for the | |||
| // time-being, this just fills it with the average colour.. | |||
| writeColour (stateStack.getLast()->fillType.gradient->getColourAtPosition (0.5f)); | |||
| out << bounds.getX() << ' ' << -bounds.getBottom() << ' ' << bounds.getWidth() << ' ' << bounds.getHeight() << " rectfill\n"; | |||
| out << "grestore\n"; | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void LowLevelGraphicsPostScriptRenderer::writeImage (const Image& im, | |||
| const int sx, const int sy, | |||
| const int maxW, const int maxH) const | |||
| { | |||
| out << "{<\n"; | |||
| const int w = jmin (maxW, im.getWidth()); | |||
| const int h = jmin (maxH, im.getHeight()); | |||
| int charsOnLine = 0; | |||
| const Image::BitmapData srcData (im, 0, 0, w, h); | |||
| Colour pixel; | |||
| for (int y = h; --y >= 0;) | |||
| { | |||
| for (int x = 0; x < w; ++x) | |||
| { | |||
| const uint8* pixelData = srcData.getPixelPointer (x, y); | |||
| if (x >= sx && y >= sy) | |||
| { | |||
| if (im.isARGB()) | |||
| { | |||
| PixelARGB p (*(const PixelARGB*) pixelData); | |||
| p.unpremultiply(); | |||
| pixel = Colours::white.overlaidWith (Colour (p)); | |||
| } | |||
| else if (im.isRGB()) | |||
| { | |||
| pixel = Colour (*((const PixelRGB*) pixelData)); | |||
| } | |||
| else | |||
| { | |||
| pixel = Colour ((uint8) 0, (uint8) 0, (uint8) 0, *pixelData); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| pixel = Colours::transparentWhite; | |||
| } | |||
| const uint8 pixelValues[3] = { pixel.getRed(), pixel.getGreen(), pixel.getBlue() }; | |||
| out << String::toHexString (pixelValues, 3, 0); | |||
| charsOnLine += 3; | |||
| if (charsOnLine > 100) | |||
| { | |||
| out << '\n'; | |||
| charsOnLine = 0; | |||
| } | |||
| } | |||
| } | |||
| out << "\n>}\n"; | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::drawImage (const Image& sourceImage, const AffineTransform& transform) | |||
| { | |||
| const int w = sourceImage.getWidth(); | |||
| const int h = sourceImage.getHeight(); | |||
| writeClip(); | |||
| out << "gsave "; | |||
| writeTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset) | |||
| .scaled (1.0f, -1.0f)); | |||
| RectangleList<int> imageClip; | |||
| sourceImage.createSolidAreaMask (imageClip, 0.5f); | |||
| out << "newpath "; | |||
| int itemsOnLine = 0; | |||
| for (auto& i : imageClip) | |||
| { | |||
| if (++itemsOnLine == 6) | |||
| { | |||
| out << '\n'; | |||
| itemsOnLine = 0; | |||
| } | |||
| out << i.getX() << ' ' << i.getY() << ' ' << i.getWidth() << ' ' << i.getHeight() << " pr "; | |||
| } | |||
| out << " clip newpath\n"; | |||
| out << w << ' ' << h << " scale\n"; | |||
| out << w << ' ' << h << " 8 [" << w << " 0 0 -" << h << ' ' << (int) 0 << ' ' << h << " ]\n"; | |||
| writeImage (sourceImage, 0, 0, w, h); | |||
| out << "false 3 colorimage grestore\n"; | |||
| needToClip = true; | |||
| } | |||
| //============================================================================== | |||
| void LowLevelGraphicsPostScriptRenderer::drawLine (const Line <float>& line) | |||
| { | |||
| Path p; | |||
| p.addLineSegment (line, 1.0f); | |||
| fillPath (p, AffineTransform()); | |||
| } | |||
| //============================================================================== | |||
| void LowLevelGraphicsPostScriptRenderer::setFont (const Font& newFont) | |||
| { | |||
| stateStack.getLast()->font = newFont; | |||
| } | |||
| const Font& LowLevelGraphicsPostScriptRenderer::getFont() | |||
| { | |||
| return stateStack.getLast()->font; | |||
| } | |||
| void LowLevelGraphicsPostScriptRenderer::drawGlyph (int glyphNumber, const AffineTransform& transform) | |||
| { | |||
| Path p; | |||
| Font& font = stateStack.getLast()->font; | |||
| font.getTypeface()->getOutlineForGlyph (glyphNumber, p); | |||
| fillPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()).followedBy (transform)); | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,120 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| An implementation of LowLevelGraphicsContext that turns the drawing operations | |||
| into a PostScript document. | |||
| */ | |||
| class JUCE_API LowLevelGraphicsPostScriptRenderer : public LowLevelGraphicsContext | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| LowLevelGraphicsPostScriptRenderer (OutputStream& resultingPostScript, | |||
| const String& documentTitle, | |||
| int totalWidth, | |||
| int totalHeight); | |||
| ~LowLevelGraphicsPostScriptRenderer(); | |||
| //============================================================================== | |||
| bool isVectorDevice() const override; | |||
| void setOrigin (Point<int>) override; | |||
| void addTransform (const AffineTransform&) override; | |||
| float getPhysicalPixelScaleFactor() override; | |||
| bool clipToRectangle (const Rectangle<int>&) override; | |||
| bool clipToRectangleList (const RectangleList<int>&) override; | |||
| void excludeClipRectangle (const Rectangle<int>&) override; | |||
| void clipToPath (const Path&, const AffineTransform&) override; | |||
| void clipToImageAlpha (const Image&, const AffineTransform&) override; | |||
| void saveState() override; | |||
| void restoreState() override; | |||
| void beginTransparencyLayer (float) override; | |||
| void endTransparencyLayer() override; | |||
| bool clipRegionIntersects (const Rectangle<int>&) override; | |||
| Rectangle<int> getClipBounds() const override; | |||
| bool isClipEmpty() const override; | |||
| //============================================================================== | |||
| void setFill (const FillType&) override; | |||
| void setOpacity (float) override; | |||
| void setInterpolationQuality (Graphics::ResamplingQuality) override; | |||
| //============================================================================== | |||
| void fillRect (const Rectangle<int>&, bool replaceExistingContents) override; | |||
| void fillRect (const Rectangle<float>&) override; | |||
| void fillRectList (const RectangleList<float>&) override; | |||
| void fillPath (const Path&, const AffineTransform&) override; | |||
| void drawImage (const Image&, const AffineTransform&) override; | |||
| void drawLine (const Line <float>&) override; | |||
| //============================================================================== | |||
| const Font& getFont() override; | |||
| void setFont (const Font&) override; | |||
| void drawGlyph (int glyphNumber, const AffineTransform&) override; | |||
| protected: | |||
| //============================================================================== | |||
| OutputStream& out; | |||
| int totalWidth, totalHeight; | |||
| bool needToClip; | |||
| Colour lastColour; | |||
| struct SavedState | |||
| { | |||
| SavedState(); | |||
| ~SavedState(); | |||
| RectangleList<int> clip; | |||
| int xOffset, yOffset; | |||
| FillType fillType; | |||
| Font font; | |||
| private: | |||
| SavedState& operator= (const SavedState&); | |||
| }; | |||
| OwnedArray <SavedState> stateStack; | |||
| void writeClip(); | |||
| void writeColour (Colour colour); | |||
| void writePath (const Path&) const; | |||
| void writeXY (float x, float y) const; | |||
| void writeTransform (const AffineTransform&) const; | |||
| void writeImage (const Image&, int sx, int sy, int maxW, int maxH) const; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LowLevelGraphicsPostScriptRenderer) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,45 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& image) | |||
| : RenderingHelpers::StackBasedLowLevelGraphicsContext<RenderingHelpers::SoftwareRendererSavedState> | |||
| (new RenderingHelpers::SoftwareRendererSavedState (image, image.getBounds())) | |||
| { | |||
| } | |||
| LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& image, Point<int> origin, | |||
| const RectangleList<int>& initialClip) | |||
| : RenderingHelpers::StackBasedLowLevelGraphicsContext<RenderingHelpers::SoftwareRendererSavedState> | |||
| (new RenderingHelpers::SoftwareRendererSavedState (image, initialClip, origin)) | |||
| { | |||
| } | |||
| LowLevelGraphicsSoftwareRenderer::~LowLevelGraphicsSoftwareRenderer() {} | |||
| } // namespace juce | |||
| @@ -0,0 +1,56 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| A lowest-common-denominator implementation of LowLevelGraphicsContext that does all | |||
| its rendering in memory. | |||
| User code is not supposed to create instances of this class directly - do all your | |||
| rendering via the Graphics class instead. | |||
| */ | |||
| class JUCE_API LowLevelGraphicsSoftwareRenderer : public RenderingHelpers::StackBasedLowLevelGraphicsContext<RenderingHelpers::SoftwareRendererSavedState> | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a context to render into an image. */ | |||
| LowLevelGraphicsSoftwareRenderer (const Image& imageToRenderOnto); | |||
| /** Creates a context to render into a clipped subsection of an image. */ | |||
| LowLevelGraphicsSoftwareRenderer (const Image& imageToRenderOnto, Point<int> origin, | |||
| const RectangleList<int>& initialClip); | |||
| /** Destructor. */ | |||
| ~LowLevelGraphicsSoftwareRenderer(); | |||
| private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LowLevelGraphicsSoftwareRenderer) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,189 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| static inline void blurDataTriplets (uint8* d, int num, const int delta) noexcept | |||
| { | |||
| uint32 last = d[0]; | |||
| d[0] = (uint8) ((d[0] + d[delta] + 1) / 3); | |||
| d += delta; | |||
| num -= 2; | |||
| do | |||
| { | |||
| const uint32 newLast = d[0]; | |||
| d[0] = (uint8) ((last + d[0] + d[delta] + 1) / 3); | |||
| d += delta; | |||
| last = newLast; | |||
| } | |||
| while (--num > 0); | |||
| d[0] = (uint8) ((last + d[0] + 1) / 3); | |||
| } | |||
| static void blurSingleChannelImage (uint8* const data, const int width, const int height, | |||
| const int lineStride, const int repetitions) noexcept | |||
| { | |||
| jassert (width > 2 && height > 2); | |||
| for (int y = 0; y < height; ++y) | |||
| for (int i = repetitions; --i >= 0;) | |||
| blurDataTriplets (data + lineStride * y, width, 1); | |||
| for (int x = 0; x < width; ++x) | |||
| for (int i = repetitions; --i >= 0;) | |||
| blurDataTriplets (data + x, height, lineStride); | |||
| } | |||
| static void blurSingleChannelImage (Image& image, int radius) | |||
| { | |||
| const Image::BitmapData bm (image, Image::BitmapData::readWrite); | |||
| blurSingleChannelImage (bm.data, bm.width, bm.height, bm.lineStride, 2 * radius); | |||
| } | |||
| //============================================================================== | |||
| DropShadow::DropShadow() noexcept | |||
| : colour (0x90000000), radius (4) | |||
| { | |||
| } | |||
| DropShadow::DropShadow (Colour shadowColour, const int r, Point<int> o) noexcept | |||
| : colour (shadowColour), radius (r), offset (o) | |||
| { | |||
| jassert (radius > 0); | |||
| } | |||
| void DropShadow::drawForImage (Graphics& g, const Image& srcImage) const | |||
| { | |||
| jassert (radius > 0); | |||
| if (srcImage.isValid()) | |||
| { | |||
| Image shadowImage (srcImage.convertedToFormat (Image::SingleChannel)); | |||
| shadowImage.duplicateIfShared(); | |||
| blurSingleChannelImage (shadowImage, radius); | |||
| g.setColour (colour); | |||
| g.drawImageAt (shadowImage, offset.x, offset.y, true); | |||
| } | |||
| } | |||
| void DropShadow::drawForPath (Graphics& g, const Path& path) const | |||
| { | |||
| jassert (radius > 0); | |||
| const Rectangle<int> area ((path.getBounds().getSmallestIntegerContainer() + offset) | |||
| .expanded (radius + 1) | |||
| .getIntersection (g.getClipBounds().expanded (radius + 1))); | |||
| if (area.getWidth() > 2 && area.getHeight() > 2) | |||
| { | |||
| Image renderedPath (Image::SingleChannel, area.getWidth(), area.getHeight(), true); | |||
| { | |||
| Graphics g2 (renderedPath); | |||
| g2.setColour (Colours::white); | |||
| g2.fillPath (path, AffineTransform::translation ((float) (offset.x - area.getX()), | |||
| (float) (offset.y - area.getY()))); | |||
| } | |||
| blurSingleChannelImage (renderedPath, radius); | |||
| g.setColour (colour); | |||
| g.drawImageAt (renderedPath, area.getX(), area.getY(), true); | |||
| } | |||
| } | |||
| static void drawShadowSection (Graphics& g, ColourGradient& cg, Rectangle<float> area, | |||
| bool isCorner, float centreX, float centreY, float edgeX, float edgeY) | |||
| { | |||
| cg.point1 = area.getRelativePoint (centreX, centreY); | |||
| cg.point2 = area.getRelativePoint (edgeX, edgeY); | |||
| cg.isRadial = isCorner; | |||
| g.setGradientFill (cg); | |||
| g.fillRect (area); | |||
| } | |||
| void DropShadow::drawForRectangle (Graphics& g, const Rectangle<int>& targetArea) const | |||
| { | |||
| ColourGradient cg (colour, 0, 0, colour.withAlpha (0.0f), 0, 0, false); | |||
| for (float i = 0.05f; i < 1.0f; i += 0.1f) | |||
| cg.addColour (1.0 - i, colour.withMultipliedAlpha (i * i)); | |||
| const float radiusInset = (radius + 1) / 2.0f; | |||
| const float expandedRadius = radius + radiusInset; | |||
| const Rectangle<float> area (targetArea.toFloat().reduced (radiusInset) + offset.toFloat()); | |||
| Rectangle<float> r (area.expanded (expandedRadius)); | |||
| Rectangle<float> top (r.removeFromTop (expandedRadius)); | |||
| Rectangle<float> bottom (r.removeFromBottom (expandedRadius)); | |||
| drawShadowSection (g, cg, top.removeFromLeft (expandedRadius), true, 1.0f, 1.0f, 0, 1.0f); | |||
| drawShadowSection (g, cg, top.removeFromRight (expandedRadius), true, 0, 1.0f, 1.0f, 1.0f); | |||
| drawShadowSection (g, cg, top, false, 0, 1.0f, 0, 0); | |||
| drawShadowSection (g, cg, bottom.removeFromLeft (expandedRadius), true, 1.0f, 0, 0, 0); | |||
| drawShadowSection (g, cg, bottom.removeFromRight (expandedRadius), true, 0, 0, 1.0f, 0); | |||
| drawShadowSection (g, cg, bottom, false, 0, 0, 0, 1.0f); | |||
| drawShadowSection (g, cg, r.removeFromLeft (expandedRadius), false, 1.0f, 0, 0, 0); | |||
| drawShadowSection (g, cg, r.removeFromRight (expandedRadius), false, 0, 0, 1.0f, 0); | |||
| g.setColour (colour); | |||
| g.fillRect (area); | |||
| } | |||
| //============================================================================== | |||
| DropShadowEffect::DropShadowEffect() {} | |||
| DropShadowEffect::~DropShadowEffect() {} | |||
| void DropShadowEffect::setShadowProperties (const DropShadow& newShadow) | |||
| { | |||
| shadow = newShadow; | |||
| } | |||
| void DropShadowEffect::applyEffect (Image& image, Graphics& g, float scaleFactor, float alpha) | |||
| { | |||
| DropShadow s (shadow); | |||
| s.radius = roundToInt (s.radius * scaleFactor); | |||
| s.colour = s.colour.withMultipliedAlpha (alpha); | |||
| s.offset.x = roundToInt (s.offset.x * scaleFactor); | |||
| s.offset.y = roundToInt (s.offset.y * scaleFactor); | |||
| s.drawForImage (g, image); | |||
| g.setOpacity (alpha); | |||
| g.drawImageAt (image, 0, 0); | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,110 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Defines a drop-shadow effect. | |||
| */ | |||
| struct JUCE_API DropShadow | |||
| { | |||
| /** Creates a default drop-shadow effect. */ | |||
| DropShadow() noexcept; | |||
| /** Creates a drop-shadow object with the given parameters. */ | |||
| DropShadow (Colour shadowColour, int radius, Point<int> offset) noexcept; | |||
| /** Renders a drop-shadow based on the alpha-channel of the given image. */ | |||
| void drawForImage (Graphics& g, const Image& srcImage) const; | |||
| /** Renders a drop-shadow based on the shape of a path. */ | |||
| void drawForPath (Graphics& g, const Path& path) const; | |||
| /** Renders a drop-shadow for a rectangle. | |||
| Note that for speed, this approximates the shadow using gradients. | |||
| */ | |||
| void drawForRectangle (Graphics& g, const Rectangle<int>& area) const; | |||
| /** The colour with which to render the shadow. | |||
| In most cases you'll probably want to leave this as black with an alpha | |||
| value of around 0.5 | |||
| */ | |||
| Colour colour; | |||
| /** The approximate spread of the shadow. */ | |||
| int radius; | |||
| /** The offset of the shadow. */ | |||
| Point<int> offset; | |||
| }; | |||
| //============================================================================== | |||
| /** | |||
| An effect filter that adds a drop-shadow behind the image's content. | |||
| (This will only work on images/components that aren't opaque, of course). | |||
| When added to a component, this effect will draw a soft-edged | |||
| shadow based on what gets drawn inside it. The shadow will also | |||
| be applied to the component's children. | |||
| For speed, this doesn't use a proper gaussian blur, but cheats by | |||
| using a simple bilinear filter. If you need a really high-quality | |||
| shadow, check out ImageConvolutionKernel::createGaussianBlur() | |||
| @see Component::setComponentEffect | |||
| */ | |||
| class JUCE_API DropShadowEffect : public ImageEffectFilter | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a default drop-shadow effect. | |||
| To customise the shadow's appearance, use the setShadowProperties() method. | |||
| */ | |||
| DropShadowEffect(); | |||
| /** Destructor. */ | |||
| ~DropShadowEffect(); | |||
| //============================================================================== | |||
| /** Sets up parameters affecting the shadow's appearance. */ | |||
| void setShadowProperties (const DropShadow& newShadow); | |||
| //============================================================================== | |||
| /** @internal */ | |||
| void applyEffect (Image& sourceImage, Graphics& destContext, float scaleFactor, float alpha) override; | |||
| private: | |||
| //============================================================================== | |||
| DropShadow shadow; | |||
| JUCE_LEAK_DETECTOR (DropShadowEffect) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,58 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| GlowEffect::GlowEffect() {} | |||
| GlowEffect::~GlowEffect() {} | |||
| void GlowEffect::setGlowProperties (float newRadius, Colour newColour, Point<int> pos) | |||
| { | |||
| radius = newRadius; | |||
| colour = newColour; | |||
| offset = pos; | |||
| } | |||
| void GlowEffect::applyEffect (Image& image, Graphics& g, float scaleFactor, float alpha) | |||
| { | |||
| Image temp (image.getFormat(), image.getWidth(), image.getHeight(), true); | |||
| ImageConvolutionKernel blurKernel (roundToInt (radius * scaleFactor * 2.0f)); | |||
| blurKernel.createGaussianBlur (radius); | |||
| blurKernel.rescaleAllValues (radius); | |||
| blurKernel.applyToImage (temp, image, image.getBounds()); | |||
| g.setColour (colour.withMultipliedAlpha (alpha)); | |||
| g.drawImageAt (temp, offset.x, offset.y, true); | |||
| g.setOpacity (alpha); | |||
| g.drawImageAt (image, offset.x, offset.y, false); | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,75 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| A component effect that adds a coloured blur around the component's contents. | |||
| (This will only work on non-opaque components). | |||
| @see Component::setComponentEffect, DropShadowEffect | |||
| */ | |||
| class JUCE_API GlowEffect : public ImageEffectFilter | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a default 'glow' effect. | |||
| To customise its appearance, use the setGlowProperties() method. | |||
| */ | |||
| GlowEffect(); | |||
| /** Destructor. */ | |||
| ~GlowEffect(); | |||
| //============================================================================== | |||
| /** Sets the glow's radius and colour. | |||
| The radius is how large the blur should be, and the colour is | |||
| used to render it (for a less intense glow, lower the colour's | |||
| opacity). | |||
| */ | |||
| void setGlowProperties (float newRadius, | |||
| Colour newColour, | |||
| Point<int> offset = {}); | |||
| //============================================================================== | |||
| /** @internal */ | |||
| void applyEffect (Image&, Graphics&, float scaleFactor, float alpha) override; | |||
| private: | |||
| //============================================================================== | |||
| float radius = 2.0f; | |||
| Colour colour { Colours::white }; | |||
| Point<int> offset; | |||
| JUCE_LEAK_DETECTOR (GlowEffect) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,70 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| A graphical effect filter that can be applied to components. | |||
| An ImageEffectFilter can be applied to the image that a component | |||
| paints before it hits the screen. | |||
| This is used for adding effects like shadows, blurs, etc. | |||
| @see Component::setComponentEffect | |||
| */ | |||
| class JUCE_API ImageEffectFilter | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Overridden to render the effect. | |||
| The implementation of this method must use the image that is passed in | |||
| as its source, and should render its output to the graphics context passed in. | |||
| @param sourceImage the image that the source component has just rendered with | |||
| its paint() method. The image may or may not have an alpha | |||
| channel, depending on whether the component is opaque. | |||
| @param destContext the graphics context to use to draw the resultant image. | |||
| @param scaleFactor a scale factor that has been applied to the image - e.g. if | |||
| this is 2, then the image is actually scaled-up to twice the | |||
| original resolution | |||
| @param alpha the alpha with which to draw the resultant image to the | |||
| target context | |||
| */ | |||
| virtual void applyEffect (Image& sourceImage, | |||
| Graphics& destContext, | |||
| float scaleFactor, | |||
| float alpha) = 0; | |||
| /** Destructor. */ | |||
| virtual ~ImageEffectFilter() {} | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,356 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| namespace | |||
| { | |||
| int getLength (const Array<AttributedString::Attribute>& atts) noexcept | |||
| { | |||
| return atts.size() != 0 ? atts.getReference (atts.size() - 1).range.getEnd() : 0; | |||
| } | |||
| void splitAttributeRanges (Array<AttributedString::Attribute>& atts, int position) | |||
| { | |||
| for (int i = atts.size(); --i >= 0;) | |||
| { | |||
| const AttributedString::Attribute& att = atts.getReference (i); | |||
| const int offset = position - att.range.getStart(); | |||
| if (offset >= 0) | |||
| { | |||
| if (offset > 0 && position < att.range.getEnd()) | |||
| { | |||
| atts.insert (i + 1, att); | |||
| atts.getReference (i).range.setEnd (position); | |||
| atts.getReference (i + 1).range.setStart (position); | |||
| } | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| Range<int> splitAttributeRanges (Array<AttributedString::Attribute>& atts, Range<int> newRange) | |||
| { | |||
| newRange = newRange.getIntersectionWith (Range<int> (0, getLength (atts))); | |||
| if (! newRange.isEmpty()) | |||
| { | |||
| splitAttributeRanges (atts, newRange.getStart()); | |||
| splitAttributeRanges (atts, newRange.getEnd()); | |||
| } | |||
| return newRange; | |||
| } | |||
| void mergeAdjacentRanges (Array<AttributedString::Attribute>& atts) | |||
| { | |||
| for (int i = atts.size() - 1; --i >= 0;) | |||
| { | |||
| AttributedString::Attribute& a1 = atts.getReference (i); | |||
| AttributedString::Attribute& a2 = atts.getReference (i + 1); | |||
| if (a1.colour == a2.colour && a1.font == a2.font) | |||
| { | |||
| a1.range.setEnd (a2.range.getEnd()); | |||
| atts.remove (i + 1); | |||
| if (i < atts.size() - 1) | |||
| ++i; | |||
| } | |||
| } | |||
| } | |||
| void appendRange (Array<AttributedString::Attribute>& atts, | |||
| int length, const Font* f, const Colour* c) | |||
| { | |||
| if (atts.size() == 0) | |||
| { | |||
| atts.add (AttributedString::Attribute (Range<int> (0, length), | |||
| f != nullptr ? *f : Font(), | |||
| c != nullptr ? *c : Colour (0xff000000))); | |||
| } | |||
| else | |||
| { | |||
| const int start = getLength (atts); | |||
| atts.add (AttributedString::Attribute (Range<int> (start, start + length), | |||
| f != nullptr ? *f : atts.getReference (atts.size() - 1).font, | |||
| c != nullptr ? *c : atts.getReference (atts.size() - 1).colour)); | |||
| mergeAdjacentRanges (atts); | |||
| } | |||
| } | |||
| void applyFontAndColour (Array<AttributedString::Attribute>& atts, | |||
| Range<int> range, const Font* f, const Colour* c) | |||
| { | |||
| range = splitAttributeRanges (atts, range); | |||
| for (int i = 0; i < atts.size(); ++i) | |||
| { | |||
| AttributedString::Attribute& att = atts.getReference (i); | |||
| if (range.getStart() < att.range.getEnd()) | |||
| { | |||
| if (range.getEnd() <= att.range.getStart()) | |||
| break; | |||
| if (c != nullptr) att.colour = *c; | |||
| if (f != nullptr) att.font = *f; | |||
| } | |||
| } | |||
| mergeAdjacentRanges (atts); | |||
| } | |||
| void truncate (Array<AttributedString::Attribute>& atts, int newLength) | |||
| { | |||
| splitAttributeRanges (atts, newLength); | |||
| for (int i = atts.size(); --i >= 0;) | |||
| if (atts.getReference (i).range.getStart() >= newLength) | |||
| atts.remove (i); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| AttributedString::Attribute::Attribute() noexcept : colour (0xff000000) {} | |||
| AttributedString::Attribute::~Attribute() noexcept {} | |||
| AttributedString::Attribute::Attribute (Attribute&& other) noexcept | |||
| : range (other.range), | |||
| font (static_cast<Font&&> (other.font)), | |||
| colour (other.colour) | |||
| { | |||
| } | |||
| AttributedString::Attribute& AttributedString::Attribute::operator= (Attribute&& other) noexcept | |||
| { | |||
| range = other.range; | |||
| font = static_cast<Font&&> (other.font); | |||
| colour = other.colour; | |||
| return *this; | |||
| } | |||
| AttributedString::Attribute::Attribute (const Attribute& other) noexcept | |||
| : range (other.range), | |||
| font (other.font), | |||
| colour (other.colour) | |||
| { | |||
| } | |||
| AttributedString::Attribute& AttributedString::Attribute::operator= (const Attribute& other) noexcept | |||
| { | |||
| range = other.range; | |||
| font = other.font; | |||
| colour = other.colour; | |||
| return *this; | |||
| } | |||
| AttributedString::Attribute::Attribute (Range<int> r, const Font& f, Colour c) noexcept | |||
| : range (r), font (f), colour (c) | |||
| { | |||
| } | |||
| //============================================================================== | |||
| AttributedString::AttributedString() | |||
| : lineSpacing (0.0f), | |||
| justification (Justification::left), | |||
| wordWrap (AttributedString::byWord), | |||
| readingDirection (AttributedString::natural) | |||
| { | |||
| } | |||
| AttributedString::AttributedString (const String& newString) | |||
| : lineSpacing (0.0f), | |||
| justification (Justification::left), | |||
| wordWrap (AttributedString::byWord), | |||
| readingDirection (AttributedString::natural) | |||
| { | |||
| setText (newString); | |||
| } | |||
| AttributedString::AttributedString (const AttributedString& other) | |||
| : text (other.text), | |||
| lineSpacing (other.lineSpacing), | |||
| justification (other.justification), | |||
| wordWrap (other.wordWrap), | |||
| readingDirection (other.readingDirection), | |||
| attributes (other.attributes) | |||
| { | |||
| } | |||
| AttributedString& AttributedString::operator= (const AttributedString& other) | |||
| { | |||
| if (this != &other) | |||
| { | |||
| text = other.text; | |||
| lineSpacing = other.lineSpacing; | |||
| justification = other.justification; | |||
| wordWrap = other.wordWrap; | |||
| readingDirection = other.readingDirection; | |||
| attributes = other.attributes; | |||
| } | |||
| return *this; | |||
| } | |||
| AttributedString::AttributedString (AttributedString&& other) noexcept | |||
| : text (static_cast<String&&> (other.text)), | |||
| lineSpacing (other.lineSpacing), | |||
| justification (other.justification), | |||
| wordWrap (other.wordWrap), | |||
| readingDirection (other.readingDirection), | |||
| attributes (static_cast<Array<Attribute>&&> (other.attributes)) | |||
| { | |||
| } | |||
| AttributedString& AttributedString::operator= (AttributedString&& other) noexcept | |||
| { | |||
| text = static_cast<String&&> (other.text); | |||
| lineSpacing = other.lineSpacing; | |||
| justification = other.justification; | |||
| wordWrap = other.wordWrap; | |||
| readingDirection = other.readingDirection; | |||
| attributes = static_cast<Array<Attribute>&&> (other.attributes); | |||
| return *this; | |||
| } | |||
| AttributedString::~AttributedString() noexcept {} | |||
| void AttributedString::setText (const String& newText) | |||
| { | |||
| const int newLength = newText.length(); | |||
| const int oldLength = getLength (attributes); | |||
| if (newLength > oldLength) | |||
| appendRange (attributes, newLength - oldLength, nullptr, nullptr); | |||
| else if (newLength < oldLength) | |||
| truncate (attributes, newLength); | |||
| text = newText; | |||
| } | |||
| void AttributedString::append (const String& textToAppend) | |||
| { | |||
| text += textToAppend; | |||
| appendRange (attributes, textToAppend.length(), nullptr, nullptr); | |||
| } | |||
| void AttributedString::append (const String& textToAppend, const Font& font) | |||
| { | |||
| text += textToAppend; | |||
| appendRange (attributes, textToAppend.length(), &font, nullptr); | |||
| } | |||
| void AttributedString::append (const String& textToAppend, Colour colour) | |||
| { | |||
| text += textToAppend; | |||
| appendRange (attributes, textToAppend.length(), nullptr, &colour); | |||
| } | |||
| void AttributedString::append (const String& textToAppend, const Font& font, Colour colour) | |||
| { | |||
| text += textToAppend; | |||
| appendRange (attributes, textToAppend.length(), &font, &colour); | |||
| } | |||
| void AttributedString::append (const AttributedString& other) | |||
| { | |||
| const int originalLength = getLength (attributes); | |||
| const int originalNumAtts = attributes.size(); | |||
| text += other.text; | |||
| attributes.addArray (other.attributes); | |||
| for (int i = originalNumAtts; i < attributes.size(); ++i) | |||
| attributes.getReference (i).range += originalLength; | |||
| mergeAdjacentRanges (attributes); | |||
| } | |||
| void AttributedString::clear() | |||
| { | |||
| text.clear(); | |||
| attributes.clear(); | |||
| } | |||
| void AttributedString::setJustification (Justification newJustification) noexcept | |||
| { | |||
| justification = newJustification; | |||
| } | |||
| void AttributedString::setWordWrap (WordWrap newWordWrap) noexcept | |||
| { | |||
| wordWrap = newWordWrap; | |||
| } | |||
| void AttributedString::setReadingDirection (ReadingDirection newReadingDirection) noexcept | |||
| { | |||
| readingDirection = newReadingDirection; | |||
| } | |||
| void AttributedString::setLineSpacing (const float newLineSpacing) noexcept | |||
| { | |||
| lineSpacing = newLineSpacing; | |||
| } | |||
| void AttributedString::setColour (Range<int> range, Colour colour) | |||
| { | |||
| applyFontAndColour (attributes, range, nullptr, &colour); | |||
| } | |||
| void AttributedString::setFont (Range<int> range, const Font& font) | |||
| { | |||
| applyFontAndColour (attributes, range, &font, nullptr); | |||
| } | |||
| void AttributedString::setColour (Colour colour) | |||
| { | |||
| setColour (Range<int> (0, getLength (attributes)), colour); | |||
| } | |||
| void AttributedString::setFont (const Font& font) | |||
| { | |||
| setFont (Range<int> (0, getLength (attributes)), font); | |||
| } | |||
| void AttributedString::draw (Graphics& g, const Rectangle<float>& area) const | |||
| { | |||
| if (text.isNotEmpty() && g.clipRegionIntersects (area.getSmallestIntegerContainer())) | |||
| { | |||
| jassert (text.length() == getLength (attributes)); | |||
| if (! g.getInternalContext().drawTextLayout (*this, area)) | |||
| { | |||
| TextLayout layout; | |||
| layout.createLayout (*this, area.getWidth()); | |||
| layout.draw (g, area); | |||
| } | |||
| } | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,207 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| A text string with a set of colour/font settings that are associated with sub-ranges | |||
| of the text. | |||
| An attributed string lets you create a string with varied fonts, colours, word-wrapping, | |||
| layout, etc., and draw it using AttributedString::draw(). | |||
| @see TextLayout | |||
| */ | |||
| class JUCE_API AttributedString | |||
| { | |||
| public: | |||
| /** Creates an empty attributed string. */ | |||
| AttributedString(); | |||
| /** Creates an attributed string with the given text. */ | |||
| explicit AttributedString (const String& text); | |||
| AttributedString (const AttributedString&); | |||
| AttributedString& operator= (const AttributedString&); | |||
| AttributedString (AttributedString&&) noexcept; | |||
| AttributedString& operator= (AttributedString&&) noexcept; | |||
| /** Destructor. */ | |||
| ~AttributedString() noexcept; | |||
| //============================================================================== | |||
| /** Returns the complete text of this attributed string. */ | |||
| const String& getText() const noexcept { return text; } | |||
| /** Replaces all the text. | |||
| This will change the text, but won't affect any of the colour or font attributes | |||
| that have been added. | |||
| */ | |||
| void setText (const String& newText); | |||
| /** Appends some text (with a default font and colour). */ | |||
| void append (const String& textToAppend); | |||
| /** Appends some text, with a specified font, and the default colour (black). */ | |||
| void append (const String& textToAppend, const Font& font); | |||
| /** Appends some text, with a specified colour, and the default font. */ | |||
| void append (const String& textToAppend, Colour colour); | |||
| /** Appends some text, with a specified font and colour. */ | |||
| void append (const String& textToAppend, const Font& font, Colour colour); | |||
| /** Appends another AttributedString to this one. | |||
| Note that this will only append the text, fonts, and colours - it won't copy any | |||
| other properties such as justification, line-spacing, etc from the other object. | |||
| */ | |||
| void append (const AttributedString& other); | |||
| /** Resets the string, clearing all text and attributes. | |||
| Note that this won't affect global settings like the justification type, | |||
| word-wrap mode, etc. | |||
| */ | |||
| void clear(); | |||
| //============================================================================== | |||
| /** Draws this string within the given area. | |||
| The layout of the string within the rectangle is controlled by the justification | |||
| value passed to setJustification(). | |||
| */ | |||
| void draw (Graphics& g, const Rectangle<float>& area) const; | |||
| //============================================================================== | |||
| /** Returns the justification that should be used for laying-out the text. | |||
| This may include both vertical and horizontal flags. | |||
| */ | |||
| Justification getJustification() const noexcept { return justification; } | |||
| /** Sets the justification that should be used for laying-out the text. | |||
| This may include both vertical and horizontal flags. | |||
| */ | |||
| void setJustification (Justification newJustification) noexcept; | |||
| //============================================================================== | |||
| /** Types of word-wrap behaviour. | |||
| @see getWordWrap, setWordWrap | |||
| */ | |||
| enum WordWrap | |||
| { | |||
| none, /**< No word-wrapping: lines extend indefinitely. */ | |||
| byWord, /**< Lines are wrapped on a word boundary. */ | |||
| byChar, /**< Lines are wrapped on a character boundary. */ | |||
| }; | |||
| /** Returns the word-wrapping behaviour. */ | |||
| WordWrap getWordWrap() const noexcept { return wordWrap; } | |||
| /** Sets the word-wrapping behaviour. */ | |||
| void setWordWrap (WordWrap newWordWrap) noexcept; | |||
| //============================================================================== | |||
| /** Types of reading direction that can be used. | |||
| @see getReadingDirection, setReadingDirection | |||
| */ | |||
| enum ReadingDirection | |||
| { | |||
| natural, | |||
| leftToRight, | |||
| rightToLeft, | |||
| }; | |||
| /** Returns the reading direction for the text. */ | |||
| ReadingDirection getReadingDirection() const noexcept { return readingDirection; } | |||
| /** Sets the reading direction that should be used for the text. */ | |||
| void setReadingDirection (ReadingDirection newReadingDirection) noexcept; | |||
| //============================================================================== | |||
| /** Returns the extra line-spacing distance. */ | |||
| float getLineSpacing() const noexcept { return lineSpacing; } | |||
| /** Sets an extra line-spacing distance. */ | |||
| void setLineSpacing (float newLineSpacing) noexcept; | |||
| //============================================================================== | |||
| /** An attribute that has been applied to a range of characters in an AttributedString. */ | |||
| class JUCE_API Attribute | |||
| { | |||
| public: | |||
| Attribute() noexcept; | |||
| ~Attribute() noexcept; | |||
| Attribute (const Attribute&) noexcept; | |||
| Attribute& operator= (const Attribute&) noexcept; | |||
| Attribute (Attribute&&) noexcept; | |||
| Attribute& operator= (Attribute&&) noexcept; | |||
| /** Creates an attribute that specifies the font and colour for a range of characters. */ | |||
| Attribute (Range<int> range, const Font& font, Colour colour) noexcept; | |||
| /** The range of characters to which this attribute will be applied. */ | |||
| Range<int> range; | |||
| /** The font for this range of characters. */ | |||
| Font font; | |||
| /** The colour for this range of characters. */ | |||
| Colour colour; | |||
| private: | |||
| JUCE_LEAK_DETECTOR (Attribute) | |||
| }; | |||
| /** Returns the number of attributes that have been added to this string. */ | |||
| int getNumAttributes() const noexcept { return attributes.size(); } | |||
| /** Returns one of the string's attributes. | |||
| The index provided must be less than getNumAttributes(), and >= 0. | |||
| */ | |||
| const Attribute& getAttribute (int index) const noexcept { return attributes.getReference (index); } | |||
| //============================================================================== | |||
| /** Adds a colour attribute for the specified range. */ | |||
| void setColour (Range<int> range, Colour colour); | |||
| /** Removes all existing colour attributes, and applies this colour to the whole string. */ | |||
| void setColour (Colour colour); | |||
| /** Adds a font attribute for the specified range. */ | |||
| void setFont (Range<int> range, const Font& font); | |||
| /** Removes all existing font attributes, and applies this font to the whole string. */ | |||
| void setFont (const Font& font); | |||
| private: | |||
| String text; | |||
| float lineSpacing; | |||
| Justification justification; | |||
| WordWrap wordWrap; | |||
| ReadingDirection readingDirection; | |||
| Array<Attribute> attributes; | |||
| JUCE_LEAK_DETECTOR (AttributedString) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,413 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| class CustomTypeface::GlyphInfo | |||
| { | |||
| public: | |||
| GlyphInfo (const juce_wchar c, const Path& p, const float w) noexcept | |||
| : character (c), path (p), width (w) | |||
| { | |||
| } | |||
| struct KerningPair | |||
| { | |||
| juce_wchar character2; | |||
| float kerningAmount; | |||
| }; | |||
| void addKerningPair (const juce_wchar subsequentCharacter, | |||
| const float extraKerningAmount) noexcept | |||
| { | |||
| KerningPair kp; | |||
| kp.character2 = subsequentCharacter; | |||
| kp.kerningAmount = extraKerningAmount; | |||
| kerningPairs.add (kp); | |||
| } | |||
| float getHorizontalSpacing (const juce_wchar subsequentCharacter) const noexcept | |||
| { | |||
| if (subsequentCharacter != 0) | |||
| for (int i = kerningPairs.size(); --i >= 0;) | |||
| if (kerningPairs.getReference(i).character2 == subsequentCharacter) | |||
| return width + kerningPairs.getReference(i).kerningAmount; | |||
| return width; | |||
| } | |||
| const juce_wchar character; | |||
| const Path path; | |||
| float width; | |||
| Array <KerningPair> kerningPairs; | |||
| private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GlyphInfo) | |||
| }; | |||
| //============================================================================== | |||
| namespace CustomTypefaceHelpers | |||
| { | |||
| static juce_wchar readChar (InputStream& in) | |||
| { | |||
| uint32 n = (uint32) (uint16) in.readShort(); | |||
| if (n >= 0xd800 && n <= 0xdfff) | |||
| { | |||
| const uint32 nextWord = (uint32) (uint16) in.readShort(); | |||
| jassert (nextWord >= 0xdc00); // illegal unicode character! | |||
| n = 0x10000 + (((n - 0xd800) << 10) | (nextWord - 0xdc00)); | |||
| } | |||
| return (juce_wchar) n; | |||
| } | |||
| static void writeChar (OutputStream& out, juce_wchar charToWrite) | |||
| { | |||
| if (charToWrite >= 0x10000) | |||
| { | |||
| charToWrite -= 0x10000; | |||
| out.writeShort ((short) (uint16) (0xd800 + (charToWrite >> 10))); | |||
| out.writeShort ((short) (uint16) (0xdc00 + (charToWrite & 0x3ff))); | |||
| } | |||
| else | |||
| { | |||
| out.writeShort ((short) (uint16) charToWrite); | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| CustomTypeface::CustomTypeface() | |||
| : Typeface (String(), String()) | |||
| { | |||
| clear(); | |||
| } | |||
| CustomTypeface::CustomTypeface (InputStream& serialisedTypefaceStream) | |||
| : Typeface (String(), String()) | |||
| { | |||
| clear(); | |||
| GZIPDecompressorInputStream gzin (serialisedTypefaceStream); | |||
| BufferedInputStream in (gzin, 32768); | |||
| name = in.readString(); | |||
| const bool isBold = in.readBool(); | |||
| const bool isItalic = in.readBool(); | |||
| style = FontStyleHelpers::getStyleName (isBold, isItalic); | |||
| ascent = in.readFloat(); | |||
| defaultCharacter = CustomTypefaceHelpers::readChar (in); | |||
| int numChars = in.readInt(); | |||
| for (int i = 0; i < numChars; ++i) | |||
| { | |||
| const juce_wchar c = CustomTypefaceHelpers::readChar (in); | |||
| const float width = in.readFloat(); | |||
| Path p; | |||
| p.loadPathFromStream (in); | |||
| addGlyph (c, p, width); | |||
| } | |||
| const int numKerningPairs = in.readInt(); | |||
| for (int i = 0; i < numKerningPairs; ++i) | |||
| { | |||
| const juce_wchar char1 = CustomTypefaceHelpers::readChar (in); | |||
| const juce_wchar char2 = CustomTypefaceHelpers::readChar (in); | |||
| addKerningPair (char1, char2, in.readFloat()); | |||
| } | |||
| } | |||
| CustomTypeface::~CustomTypeface() | |||
| { | |||
| } | |||
| //============================================================================== | |||
| void CustomTypeface::clear() | |||
| { | |||
| defaultCharacter = 0; | |||
| ascent = 1.0f; | |||
| style = "Regular"; | |||
| zeromem (lookupTable, sizeof (lookupTable)); | |||
| glyphs.clear(); | |||
| } | |||
| void CustomTypeface::setCharacteristics (const String& newName, const float newAscent, const bool isBold, | |||
| const bool isItalic, const juce_wchar newDefaultCharacter) noexcept | |||
| { | |||
| name = newName; | |||
| defaultCharacter = newDefaultCharacter; | |||
| ascent = newAscent; | |||
| style = FontStyleHelpers::getStyleName (isBold, isItalic); | |||
| } | |||
| void CustomTypeface::setCharacteristics (const String& newName, const String& newStyle, const float newAscent, | |||
| const juce_wchar newDefaultCharacter) noexcept | |||
| { | |||
| name = newName; | |||
| style = newStyle; | |||
| defaultCharacter = newDefaultCharacter; | |||
| ascent = newAscent; | |||
| } | |||
| void CustomTypeface::addGlyph (const juce_wchar character, const Path& path, const float width) noexcept | |||
| { | |||
| // Check that you're not trying to add the same character twice.. | |||
| jassert (findGlyph (character, false) == nullptr); | |||
| if (isPositiveAndBelow ((int) character, numElementsInArray (lookupTable))) | |||
| lookupTable [character] = (short) glyphs.size(); | |||
| glyphs.add (new GlyphInfo (character, path, width)); | |||
| } | |||
| void CustomTypeface::addKerningPair (const juce_wchar char1, const juce_wchar char2, const float extraAmount) noexcept | |||
| { | |||
| if (extraAmount != 0.0f) | |||
| { | |||
| if (auto* g = findGlyph (char1, true)) | |||
| g->addKerningPair (char2, extraAmount); | |||
| else | |||
| jassertfalse; // can only add kerning pairs for characters that exist! | |||
| } | |||
| } | |||
| CustomTypeface::GlyphInfo* CustomTypeface::findGlyph (const juce_wchar character, const bool loadIfNeeded) noexcept | |||
| { | |||
| if (isPositiveAndBelow ((int) character, numElementsInArray (lookupTable)) && lookupTable [character] > 0) | |||
| return glyphs [(int) lookupTable [(int) character]]; | |||
| for (int i = 0; i < glyphs.size(); ++i) | |||
| { | |||
| GlyphInfo* const g = glyphs.getUnchecked(i); | |||
| if (g->character == character) | |||
| return g; | |||
| } | |||
| if (loadIfNeeded && loadGlyphIfPossible (character)) | |||
| return findGlyph (character, false); | |||
| return nullptr; | |||
| } | |||
| bool CustomTypeface::loadGlyphIfPossible (const juce_wchar /*characterNeeded*/) | |||
| { | |||
| return false; | |||
| } | |||
| void CustomTypeface::addGlyphsFromOtherTypeface (Typeface& typefaceToCopy, juce_wchar characterStartIndex, int numCharacters) noexcept | |||
| { | |||
| setCharacteristics (name, style, typefaceToCopy.getAscent(), defaultCharacter); | |||
| for (int i = 0; i < numCharacters; ++i) | |||
| { | |||
| const juce_wchar c = (juce_wchar) (characterStartIndex + static_cast<juce_wchar> (i)); | |||
| Array <int> glyphIndexes; | |||
| Array <float> offsets; | |||
| typefaceToCopy.getGlyphPositions (String::charToString (c), glyphIndexes, offsets); | |||
| const int glyphIndex = glyphIndexes.getFirst(); | |||
| if (glyphIndex >= 0 && glyphIndexes.size() > 0) | |||
| { | |||
| const float glyphWidth = offsets[1]; | |||
| Path p; | |||
| typefaceToCopy.getOutlineForGlyph (glyphIndex, p); | |||
| addGlyph (c, p, glyphWidth); | |||
| for (int j = glyphs.size() - 1; --j >= 0;) | |||
| { | |||
| const juce_wchar char2 = glyphs.getUnchecked (j)->character; | |||
| glyphIndexes.clearQuick(); | |||
| offsets.clearQuick(); | |||
| typefaceToCopy.getGlyphPositions (String::charToString (c) + String::charToString (char2), glyphIndexes, offsets); | |||
| if (offsets.size() > 1) | |||
| addKerningPair (c, char2, offsets[1] - glyphWidth); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| bool CustomTypeface::writeToStream (OutputStream& outputStream) | |||
| { | |||
| GZIPCompressorOutputStream out (&outputStream); | |||
| out.writeString (name); | |||
| out.writeBool (FontStyleHelpers::isBold (style)); | |||
| out.writeBool (FontStyleHelpers::isItalic (style)); | |||
| out.writeFloat (ascent); | |||
| CustomTypefaceHelpers::writeChar (out, defaultCharacter); | |||
| out.writeInt (glyphs.size()); | |||
| int numKerningPairs = 0; | |||
| for (int i = 0; i < glyphs.size(); ++i) | |||
| { | |||
| const GlyphInfo* const g = glyphs.getUnchecked (i); | |||
| CustomTypefaceHelpers::writeChar (out, g->character); | |||
| out.writeFloat (g->width); | |||
| g->path.writePathToStream (out); | |||
| numKerningPairs += g->kerningPairs.size(); | |||
| } | |||
| out.writeInt (numKerningPairs); | |||
| for (int i = 0; i < glyphs.size(); ++i) | |||
| { | |||
| const GlyphInfo* const g = glyphs.getUnchecked (i); | |||
| for (int j = 0; j < g->kerningPairs.size(); ++j) | |||
| { | |||
| const GlyphInfo::KerningPair& p = g->kerningPairs.getReference (j); | |||
| CustomTypefaceHelpers::writeChar (out, g->character); | |||
| CustomTypefaceHelpers::writeChar (out, p.character2); | |||
| out.writeFloat (p.kerningAmount); | |||
| } | |||
| } | |||
| return true; | |||
| } | |||
| //============================================================================== | |||
| float CustomTypeface::getAscent() const { return ascent; } | |||
| float CustomTypeface::getDescent() const { return 1.0f - ascent; } | |||
| float CustomTypeface::getHeightToPointsFactor() const { return ascent; } | |||
| float CustomTypeface::getStringWidth (const String& text) | |||
| { | |||
| float x = 0; | |||
| for (String::CharPointerType t (text.getCharPointer()); ! t.isEmpty();) | |||
| { | |||
| const juce_wchar c = t.getAndAdvance(); | |||
| if (const GlyphInfo* const glyph = findGlyph (c, true)) | |||
| { | |||
| x += glyph->getHorizontalSpacing (*t); | |||
| } | |||
| else | |||
| { | |||
| const Typeface::Ptr fallbackTypeface (Typeface::getFallbackTypeface()); | |||
| if (fallbackTypeface != nullptr && fallbackTypeface != this) | |||
| x += fallbackTypeface->getStringWidth (String::charToString (c)); | |||
| } | |||
| } | |||
| return x; | |||
| } | |||
| void CustomTypeface::getGlyphPositions (const String& text, Array <int>& resultGlyphs, Array<float>& xOffsets) | |||
| { | |||
| xOffsets.add (0); | |||
| float x = 0; | |||
| for (String::CharPointerType t (text.getCharPointer()); ! t.isEmpty();) | |||
| { | |||
| float width = 0.0f; | |||
| int glyphChar = 0; | |||
| const juce_wchar c = t.getAndAdvance(); | |||
| if (const GlyphInfo* const glyph = findGlyph (c, true)) | |||
| { | |||
| width = glyph->getHorizontalSpacing (*t); | |||
| glyphChar = (int) glyph->character; | |||
| } | |||
| else | |||
| { | |||
| const Typeface::Ptr fallbackTypeface (getFallbackTypeface()); | |||
| if (fallbackTypeface != nullptr && fallbackTypeface != this) | |||
| { | |||
| Array <int> subGlyphs; | |||
| Array <float> subOffsets; | |||
| fallbackTypeface->getGlyphPositions (String::charToString (c), subGlyphs, subOffsets); | |||
| if (subGlyphs.size() > 0) | |||
| { | |||
| glyphChar = subGlyphs.getFirst(); | |||
| width = subOffsets[1]; | |||
| } | |||
| } | |||
| } | |||
| x += width; | |||
| resultGlyphs.add (glyphChar); | |||
| xOffsets.add (x); | |||
| } | |||
| } | |||
| bool CustomTypeface::getOutlineForGlyph (int glyphNumber, Path& path) | |||
| { | |||
| if (const GlyphInfo* const glyph = findGlyph ((juce_wchar) glyphNumber, true)) | |||
| { | |||
| path = glyph->path; | |||
| return true; | |||
| } | |||
| const Typeface::Ptr fallbackTypeface (getFallbackTypeface()); | |||
| if (fallbackTypeface != nullptr && fallbackTypeface != this) | |||
| return fallbackTypeface->getOutlineForGlyph (glyphNumber, path); | |||
| return false; | |||
| } | |||
| EdgeTable* CustomTypeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight) | |||
| { | |||
| if (const GlyphInfo* const glyph = findGlyph ((juce_wchar) glyphNumber, true)) | |||
| { | |||
| if (! glyph->path.isEmpty()) | |||
| return new EdgeTable (glyph->path.getBoundsTransformed (transform) | |||
| .getSmallestIntegerContainer().expanded (1, 0), | |||
| glyph->path, transform); | |||
| } | |||
| else | |||
| { | |||
| const Typeface::Ptr fallbackTypeface (getFallbackTypeface()); | |||
| if (fallbackTypeface != nullptr && fallbackTypeface != this) | |||
| return fallbackTypeface->getEdgeTableForGlyph (glyphNumber, transform, fontHeight); | |||
| } | |||
| return nullptr; | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,165 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| A typeface that can be populated with custom glyphs. | |||
| You can create a CustomTypeface if you need one that contains your own glyphs, | |||
| or if you need to load a typeface from a Juce-formatted binary stream. | |||
| If you want to create a copy of a native face, you can use addGlyphsFromOtherTypeface() | |||
| to copy glyphs into this face. | |||
| NOTE! For most people this class is almost certainly NOT the right tool to use! | |||
| If what you want to do is to embed a font into your exe, then your best plan is | |||
| probably to embed your TTF/OTF font file into your binary using the Projucer, | |||
| and then call Typeface::createSystemTypefaceFor() to load it from memory. | |||
| @see Typeface, Font | |||
| */ | |||
| class JUCE_API CustomTypeface : public Typeface | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a new, empty typeface. */ | |||
| CustomTypeface(); | |||
| /** Loads a typeface from a previously saved stream. | |||
| The stream must have been created by writeToStream(). | |||
| NOTE! Since this class was written, support was added for loading real font files from | |||
| memory, so for most people, using Typeface::createSystemTypefaceFor() to load a real font | |||
| is more appropriate than using this class to store it in a proprietary format. | |||
| @see writeToStream | |||
| */ | |||
| explicit CustomTypeface (InputStream& serialisedTypefaceStream); | |||
| /** Destructor. */ | |||
| ~CustomTypeface(); | |||
| //============================================================================== | |||
| /** Resets this typeface, deleting all its glyphs and settings. */ | |||
| void clear(); | |||
| /** Sets the vital statistics for the typeface. | |||
| @param fontFamily the typeface's font family | |||
| @param ascent the ascent - this is normalised to a height of 1.0 and this is | |||
| the value that will be returned by Typeface::getAscent(). The | |||
| descent is assumed to be (1.0 - ascent) | |||
| @param isBold should be true if the typeface is bold | |||
| @param isItalic should be true if the typeface is italic | |||
| @param defaultCharacter the character to be used as a replacement if there's | |||
| no glyph available for the character that's being drawn | |||
| */ | |||
| void setCharacteristics (const String& fontFamily, float ascent, | |||
| bool isBold, bool isItalic, | |||
| juce_wchar defaultCharacter) noexcept; | |||
| /** Sets the vital statistics for the typeface. | |||
| @param fontFamily the typeface's font family | |||
| @param fontStyle the typeface's font style | |||
| @param ascent the ascent - this is normalised to a height of 1.0 and this is | |||
| the value that will be returned by Typeface::getAscent(). The | |||
| descent is assumed to be (1.0 - ascent) | |||
| @param defaultCharacter the character to be used as a replacement if there's | |||
| no glyph available for the character that's being drawn | |||
| */ | |||
| void setCharacteristics (const String& fontFamily, const String& fontStyle, | |||
| float ascent, juce_wchar defaultCharacter) noexcept; | |||
| /** Adds a glyph to the typeface. | |||
| The path that is passed in is normalised so that the font height is 1.0, and its | |||
| origin is the anchor point of the character on its baseline. | |||
| The width is the nominal width of the character, and any extra kerning values that | |||
| are specified will be added to this width. | |||
| */ | |||
| void addGlyph (juce_wchar character, const Path& path, float width) noexcept; | |||
| /** Specifies an extra kerning amount to be used between a pair of characters. | |||
| The amount will be added to the nominal width of the first character when laying out a string. | |||
| */ | |||
| void addKerningPair (juce_wchar char1, juce_wchar char2, float extraAmount) noexcept; | |||
| /** Adds a range of glyphs from another typeface. | |||
| This will attempt to pull in the paths and kerning information from another typeface and | |||
| add it to this one. | |||
| */ | |||
| void addGlyphsFromOtherTypeface (Typeface& typefaceToCopy, juce_wchar characterStartIndex, int numCharacters) noexcept; | |||
| /** Saves this typeface as a Juce-formatted font file. | |||
| A CustomTypeface can be created to reload the data that is written - see the CustomTypeface | |||
| constructor. | |||
| NOTE! Since this class was written, support was added for loading real font files from | |||
| memory, so for most people, using Typeface::createSystemTypefaceFor() to load a real font | |||
| is more appropriate than using this class to store it in a proprietary format. | |||
| */ | |||
| bool writeToStream (OutputStream& outputStream); | |||
| //============================================================================== | |||
| // The following methods implement the basic Typeface behaviour. | |||
| float getAscent() const override; | |||
| float getDescent() const override; | |||
| float getHeightToPointsFactor() const override; | |||
| float getStringWidth (const String&) override; | |||
| void getGlyphPositions (const String&, Array <int>& glyphs, Array<float>& xOffsets) override; | |||
| bool getOutlineForGlyph (int glyphNumber, Path&) override; | |||
| EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform&, float fontHeight) override; | |||
| protected: | |||
| //============================================================================== | |||
| juce_wchar defaultCharacter; | |||
| float ascent; | |||
| //============================================================================== | |||
| /** If a subclass overrides this, it can load glyphs into the font on-demand. | |||
| When methods such as getGlyphPositions() or getOutlineForGlyph() are asked for a | |||
| particular character and there's no corresponding glyph, they'll call this | |||
| method so that a subclass can try to add that glyph, returning true if it | |||
| manages to do so. | |||
| */ | |||
| virtual bool loadGlyphIfPossible (juce_wchar characterNeeded); | |||
| private: | |||
| //============================================================================== | |||
| class GlyphInfo; | |||
| friend struct ContainerDeletePolicy<GlyphInfo>; | |||
| OwnedArray<GlyphInfo> glyphs; | |||
| short lookupTable [128]; | |||
| GlyphInfo* findGlyph (const juce_wchar character, bool loadIfNeeded) noexcept; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomTypeface) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,722 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| namespace FontValues | |||
| { | |||
| static float limitFontHeight (const float height) noexcept | |||
| { | |||
| return jlimit (0.1f, 10000.0f, height); | |||
| } | |||
| const float defaultFontHeight = 14.0f; | |||
| float minimumHorizontalScale = 0.7f; | |||
| String fallbackFont; | |||
| String fallbackFontStyle; | |||
| } | |||
| typedef Typeface::Ptr (*GetTypefaceForFont) (const Font&); | |||
| GetTypefaceForFont juce_getTypefaceForFont = nullptr; | |||
| float Font::getDefaultMinimumHorizontalScaleFactor() noexcept { return FontValues::minimumHorizontalScale; } | |||
| void Font::setDefaultMinimumHorizontalScaleFactor (float newValue) noexcept { FontValues::minimumHorizontalScale = newValue; } | |||
| //============================================================================== | |||
| class TypefaceCache : private DeletedAtShutdown | |||
| { | |||
| public: | |||
| TypefaceCache() | |||
| { | |||
| setSize (10); | |||
| } | |||
| ~TypefaceCache() | |||
| { | |||
| clearSingletonInstance(); | |||
| } | |||
| juce_DeclareSingleton (TypefaceCache, false) | |||
| void setSize (const int numToCache) | |||
| { | |||
| const ScopedWriteLock sl (lock); | |||
| faces.clear(); | |||
| faces.insertMultiple (-1, CachedFace(), numToCache); | |||
| } | |||
| void clear() | |||
| { | |||
| const ScopedWriteLock sl (lock); | |||
| setSize (faces.size()); | |||
| defaultFace = nullptr; | |||
| } | |||
| Typeface::Ptr findTypefaceFor (const Font& font) | |||
| { | |||
| const ScopedReadLock slr (lock); | |||
| auto faceName = font.getTypefaceName(); | |||
| auto faceStyle = font.getTypefaceStyle(); | |||
| jassert (faceName.isNotEmpty()); | |||
| for (int i = faces.size(); --i >= 0;) | |||
| { | |||
| CachedFace& face = faces.getReference(i); | |||
| if (face.typefaceName == faceName | |||
| && face.typefaceStyle == faceStyle | |||
| && face.typeface != nullptr | |||
| && face.typeface->isSuitableForFont (font)) | |||
| { | |||
| face.lastUsageCount = ++counter; | |||
| return face.typeface; | |||
| } | |||
| } | |||
| const ScopedWriteLock slw (lock); | |||
| int replaceIndex = 0; | |||
| auto bestLastUsageCount = std::numeric_limits<size_t>::max(); | |||
| for (int i = faces.size(); --i >= 0;) | |||
| { | |||
| auto lu = faces.getReference(i).lastUsageCount; | |||
| if (bestLastUsageCount > lu) | |||
| { | |||
| bestLastUsageCount = lu; | |||
| replaceIndex = i; | |||
| } | |||
| } | |||
| auto& face = faces.getReference (replaceIndex); | |||
| face.typefaceName = faceName; | |||
| face.typefaceStyle = faceStyle; | |||
| face.lastUsageCount = ++counter; | |||
| if (juce_getTypefaceForFont == nullptr) | |||
| face.typeface = Font::getDefaultTypefaceForFont (font); | |||
| else | |||
| face.typeface = juce_getTypefaceForFont (font); | |||
| jassert (face.typeface != nullptr); // the look and feel must return a typeface! | |||
| if (defaultFace == nullptr && font == Font()) | |||
| defaultFace = face.typeface; | |||
| return face.typeface; | |||
| } | |||
| Typeface::Ptr defaultFace; | |||
| private: | |||
| struct CachedFace | |||
| { | |||
| CachedFace() noexcept : lastUsageCount (0) {} | |||
| // Although it seems a bit wacky to store the name here, it's because it may be a | |||
| // placeholder rather than a real one, e.g. "<Sans-Serif>" vs the actual typeface name. | |||
| // Since the typeface itself doesn't know that it may have this alias, the name under | |||
| // which it was fetched needs to be stored separately. | |||
| String typefaceName, typefaceStyle; | |||
| size_t lastUsageCount; | |||
| Typeface::Ptr typeface; | |||
| }; | |||
| ReadWriteLock lock; | |||
| Array<CachedFace> faces; | |||
| size_t counter = 0; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TypefaceCache) | |||
| }; | |||
| juce_ImplementSingleton (TypefaceCache) | |||
| void Typeface::setTypefaceCacheSize (int numFontsToCache) | |||
| { | |||
| TypefaceCache::getInstance()->setSize (numFontsToCache); | |||
| } | |||
| void (*clearOpenGLGlyphCache)() = nullptr; | |||
| void Typeface::clearTypefaceCache() | |||
| { | |||
| TypefaceCache::getInstance()->clear(); | |||
| RenderingHelpers::SoftwareRendererSavedState::clearGlyphCache(); | |||
| if (clearOpenGLGlyphCache != nullptr) | |||
| clearOpenGLGlyphCache(); | |||
| } | |||
| //============================================================================== | |||
| class Font::SharedFontInternal : public ReferenceCountedObject | |||
| { | |||
| public: | |||
| SharedFontInternal() noexcept | |||
| : typeface (TypefaceCache::getInstance()->defaultFace), | |||
| typefaceName (Font::getDefaultSansSerifFontName()), | |||
| typefaceStyle (Font::getDefaultStyle()), | |||
| height (FontValues::defaultFontHeight) | |||
| { | |||
| } | |||
| SharedFontInternal (int styleFlags, float fontHeight) noexcept | |||
| : typefaceName (Font::getDefaultSansSerifFontName()), | |||
| typefaceStyle (FontStyleHelpers::getStyleName (styleFlags)), | |||
| height (fontHeight), | |||
| underline ((styleFlags & underlined) != 0) | |||
| { | |||
| if (styleFlags == plain) | |||
| typeface = TypefaceCache::getInstance()->defaultFace; | |||
| } | |||
| SharedFontInternal (const String& name, int styleFlags, float fontHeight) noexcept | |||
| : typefaceName (name), | |||
| typefaceStyle (FontStyleHelpers::getStyleName (styleFlags)), | |||
| height (fontHeight), | |||
| underline ((styleFlags & underlined) != 0) | |||
| { | |||
| if (styleFlags == plain && typefaceName.isEmpty()) | |||
| typeface = TypefaceCache::getInstance()->defaultFace; | |||
| } | |||
| SharedFontInternal (const String& name, const String& style, float fontHeight) noexcept | |||
| : typefaceName (name), typefaceStyle (style), height (fontHeight) | |||
| { | |||
| if (typefaceName.isEmpty()) | |||
| typefaceName = Font::getDefaultSansSerifFontName(); | |||
| } | |||
| explicit SharedFontInternal (const Typeface::Ptr& face) noexcept | |||
| : typeface (face), | |||
| typefaceName (face->getName()), | |||
| typefaceStyle (face->getStyle()), | |||
| height (FontValues::defaultFontHeight) | |||
| { | |||
| jassert (typefaceName.isNotEmpty()); | |||
| } | |||
| SharedFontInternal (const SharedFontInternal& other) noexcept | |||
| : ReferenceCountedObject(), | |||
| typeface (other.typeface), | |||
| typefaceName (other.typefaceName), | |||
| typefaceStyle (other.typefaceStyle), | |||
| height (other.height), | |||
| horizontalScale (other.horizontalScale), | |||
| kerning (other.kerning), | |||
| ascent (other.ascent), | |||
| underline (other.underline) | |||
| { | |||
| } | |||
| bool operator== (const SharedFontInternal& other) const noexcept | |||
| { | |||
| return height == other.height | |||
| && underline == other.underline | |||
| && horizontalScale == other.horizontalScale | |||
| && kerning == other.kerning | |||
| && typefaceName == other.typefaceName | |||
| && typefaceStyle == other.typefaceStyle; | |||
| } | |||
| Typeface::Ptr typeface; | |||
| String typefaceName, typefaceStyle; | |||
| float height, horizontalScale = 1.0f, kerning = 0, ascent = 0; | |||
| bool underline = false; | |||
| }; | |||
| //============================================================================== | |||
| Font::Font() : font (new SharedFontInternal()) {} | |||
| Font::Font (const Typeface::Ptr& typeface) : font (new SharedFontInternal (typeface)) {} | |||
| Font::Font (const Font& other) noexcept : font (other.font) {} | |||
| Font::Font (float fontHeight, int styleFlags) | |||
| : font (new SharedFontInternal (styleFlags, FontValues::limitFontHeight (fontHeight))) | |||
| { | |||
| } | |||
| Font::Font (const String& typefaceName, float fontHeight, int styleFlags) | |||
| : font (new SharedFontInternal (typefaceName, styleFlags, FontValues::limitFontHeight (fontHeight))) | |||
| { | |||
| } | |||
| Font::Font (const String& typefaceName, const String& typefaceStyle, float fontHeight) | |||
| : font (new SharedFontInternal (typefaceName, typefaceStyle, FontValues::limitFontHeight (fontHeight))) | |||
| { | |||
| } | |||
| Font& Font::operator= (const Font& other) noexcept | |||
| { | |||
| font = other.font; | |||
| return *this; | |||
| } | |||
| Font::Font (Font&& other) noexcept | |||
| : font (static_cast<ReferenceCountedObjectPtr<SharedFontInternal>&&> (other.font)) | |||
| { | |||
| } | |||
| Font& Font::operator= (Font&& other) noexcept | |||
| { | |||
| font = static_cast<ReferenceCountedObjectPtr<SharedFontInternal>&&> (other.font); | |||
| return *this; | |||
| } | |||
| Font::~Font() noexcept | |||
| { | |||
| } | |||
| bool Font::operator== (const Font& other) const noexcept | |||
| { | |||
| return font == other.font | |||
| || *font == *other.font; | |||
| } | |||
| bool Font::operator!= (const Font& other) const noexcept | |||
| { | |||
| return ! operator== (other); | |||
| } | |||
| void Font::dupeInternalIfShared() | |||
| { | |||
| if (font->getReferenceCount() > 1) | |||
| font = new SharedFontInternal (*font); | |||
| } | |||
| void Font::checkTypefaceSuitability() | |||
| { | |||
| if (font->typeface != nullptr && ! font->typeface->isSuitableForFont (*this)) | |||
| font->typeface = nullptr; | |||
| } | |||
| //============================================================================== | |||
| struct FontPlaceholderNames | |||
| { | |||
| String sans { "<Sans-Serif>" }, | |||
| serif { "<Serif>" }, | |||
| mono { "<Monospaced>" }, | |||
| regular { "<Regular>" }; | |||
| }; | |||
| const FontPlaceholderNames& getFontPlaceholderNames() | |||
| { | |||
| static FontPlaceholderNames names; | |||
| return names; | |||
| } | |||
| #if JUCE_MSVC | |||
| // This is a workaround for the lack of thread-safety in MSVC's handling of function-local | |||
| // statics - if multiple threads all try to create the first Font object at the same time, | |||
| // it can cause a race-condition in creating these placeholder strings. | |||
| struct FontNamePreloader { FontNamePreloader() { getFontPlaceholderNames(); } }; | |||
| static FontNamePreloader fnp; | |||
| #endif | |||
| const String& Font::getDefaultSansSerifFontName() { return getFontPlaceholderNames().sans; } | |||
| const String& Font::getDefaultSerifFontName() { return getFontPlaceholderNames().serif; } | |||
| const String& Font::getDefaultMonospacedFontName() { return getFontPlaceholderNames().mono; } | |||
| const String& Font::getDefaultStyle() { return getFontPlaceholderNames().regular; } | |||
| const String& Font::getTypefaceName() const noexcept { return font->typefaceName; } | |||
| const String& Font::getTypefaceStyle() const noexcept { return font->typefaceStyle; } | |||
| void Font::setTypefaceName (const String& faceName) | |||
| { | |||
| if (faceName != font->typefaceName) | |||
| { | |||
| jassert (faceName.isNotEmpty()); | |||
| dupeInternalIfShared(); | |||
| font->typefaceName = faceName; | |||
| font->typeface = nullptr; | |||
| font->ascent = 0; | |||
| } | |||
| } | |||
| void Font::setTypefaceStyle (const String& typefaceStyle) | |||
| { | |||
| if (typefaceStyle != font->typefaceStyle) | |||
| { | |||
| dupeInternalIfShared(); | |||
| font->typefaceStyle = typefaceStyle; | |||
| font->typeface = nullptr; | |||
| font->ascent = 0; | |||
| } | |||
| } | |||
| Font Font::withTypefaceStyle (const String& newStyle) const | |||
| { | |||
| Font f (*this); | |||
| f.setTypefaceStyle (newStyle); | |||
| return f; | |||
| } | |||
| StringArray Font::getAvailableStyles() const | |||
| { | |||
| return findAllTypefaceStyles (getTypeface()->getName()); | |||
| } | |||
| Typeface* Font::getTypeface() const | |||
| { | |||
| if (font->typeface == nullptr) | |||
| { | |||
| font->typeface = TypefaceCache::getInstance()->findTypefaceFor (*this); | |||
| jassert (font->typeface != nullptr); | |||
| } | |||
| return font->typeface; | |||
| } | |||
| //============================================================================== | |||
| const String& Font::getFallbackFontName() | |||
| { | |||
| return FontValues::fallbackFont; | |||
| } | |||
| void Font::setFallbackFontName (const String& name) | |||
| { | |||
| FontValues::fallbackFont = name; | |||
| #if JUCE_MAC || JUCE_IOS | |||
| jassertfalse; // Note that use of a fallback font isn't currently implemented in OSX.. | |||
| #endif | |||
| } | |||
| const String& Font::getFallbackFontStyle() | |||
| { | |||
| return FontValues::fallbackFontStyle; | |||
| } | |||
| void Font::setFallbackFontStyle (const String& style) | |||
| { | |||
| FontValues::fallbackFontStyle = style; | |||
| #if JUCE_MAC || JUCE_IOS | |||
| jassertfalse; // Note that use of a fallback font isn't currently implemented in OSX.. | |||
| #endif | |||
| } | |||
| //============================================================================== | |||
| Font Font::withHeight (const float newHeight) const | |||
| { | |||
| Font f (*this); | |||
| f.setHeight (newHeight); | |||
| return f; | |||
| } | |||
| float Font::getHeightToPointsFactor() const | |||
| { | |||
| return getTypeface()->getHeightToPointsFactor(); | |||
| } | |||
| Font Font::withPointHeight (float heightInPoints) const | |||
| { | |||
| Font f (*this); | |||
| f.setHeight (heightInPoints / getHeightToPointsFactor()); | |||
| return f; | |||
| } | |||
| void Font::setHeight (float newHeight) | |||
| { | |||
| newHeight = FontValues::limitFontHeight (newHeight); | |||
| if (font->height != newHeight) | |||
| { | |||
| dupeInternalIfShared(); | |||
| font->height = newHeight; | |||
| checkTypefaceSuitability(); | |||
| } | |||
| } | |||
| void Font::setHeightWithoutChangingWidth (float newHeight) | |||
| { | |||
| newHeight = FontValues::limitFontHeight (newHeight); | |||
| if (font->height != newHeight) | |||
| { | |||
| dupeInternalIfShared(); | |||
| font->horizontalScale *= (font->height / newHeight); | |||
| font->height = newHeight; | |||
| checkTypefaceSuitability(); | |||
| } | |||
| } | |||
| int Font::getStyleFlags() const noexcept | |||
| { | |||
| int styleFlags = font->underline ? underlined : plain; | |||
| if (isBold()) styleFlags |= bold; | |||
| if (isItalic()) styleFlags |= italic; | |||
| return styleFlags; | |||
| } | |||
| Font Font::withStyle (const int newFlags) const | |||
| { | |||
| Font f (*this); | |||
| f.setStyleFlags (newFlags); | |||
| return f; | |||
| } | |||
| void Font::setStyleFlags (const int newFlags) | |||
| { | |||
| if (getStyleFlags() != newFlags) | |||
| { | |||
| dupeInternalIfShared(); | |||
| font->typeface = nullptr; | |||
| font->typefaceStyle = FontStyleHelpers::getStyleName (newFlags); | |||
| font->underline = (newFlags & underlined) != 0; | |||
| font->ascent = 0; | |||
| } | |||
| } | |||
| void Font::setSizeAndStyle (float newHeight, | |||
| const int newStyleFlags, | |||
| const float newHorizontalScale, | |||
| const float newKerningAmount) | |||
| { | |||
| newHeight = FontValues::limitFontHeight (newHeight); | |||
| if (font->height != newHeight | |||
| || font->horizontalScale != newHorizontalScale | |||
| || font->kerning != newKerningAmount) | |||
| { | |||
| dupeInternalIfShared(); | |||
| font->height = newHeight; | |||
| font->horizontalScale = newHorizontalScale; | |||
| font->kerning = newKerningAmount; | |||
| checkTypefaceSuitability(); | |||
| } | |||
| setStyleFlags (newStyleFlags); | |||
| } | |||
| void Font::setSizeAndStyle (float newHeight, | |||
| const String& newStyle, | |||
| const float newHorizontalScale, | |||
| const float newKerningAmount) | |||
| { | |||
| newHeight = FontValues::limitFontHeight (newHeight); | |||
| if (font->height != newHeight | |||
| || font->horizontalScale != newHorizontalScale | |||
| || font->kerning != newKerningAmount) | |||
| { | |||
| dupeInternalIfShared(); | |||
| font->height = newHeight; | |||
| font->horizontalScale = newHorizontalScale; | |||
| font->kerning = newKerningAmount; | |||
| checkTypefaceSuitability(); | |||
| } | |||
| setTypefaceStyle (newStyle); | |||
| } | |||
| Font Font::withHorizontalScale (const float newHorizontalScale) const | |||
| { | |||
| Font f (*this); | |||
| f.setHorizontalScale (newHorizontalScale); | |||
| return f; | |||
| } | |||
| void Font::setHorizontalScale (const float scaleFactor) | |||
| { | |||
| dupeInternalIfShared(); | |||
| font->horizontalScale = scaleFactor; | |||
| checkTypefaceSuitability(); | |||
| } | |||
| float Font::getHorizontalScale() const noexcept | |||
| { | |||
| return font->horizontalScale; | |||
| } | |||
| float Font::getExtraKerningFactor() const noexcept | |||
| { | |||
| return font->kerning; | |||
| } | |||
| Font Font::withExtraKerningFactor (const float extraKerning) const | |||
| { | |||
| Font f (*this); | |||
| f.setExtraKerningFactor (extraKerning); | |||
| return f; | |||
| } | |||
| void Font::setExtraKerningFactor (const float extraKerning) | |||
| { | |||
| dupeInternalIfShared(); | |||
| font->kerning = extraKerning; | |||
| checkTypefaceSuitability(); | |||
| } | |||
| Font Font::boldened() const { return withStyle (getStyleFlags() | bold); } | |||
| Font Font::italicised() const { return withStyle (getStyleFlags() | italic); } | |||
| bool Font::isBold() const noexcept { return FontStyleHelpers::isBold (font->typefaceStyle); } | |||
| bool Font::isItalic() const noexcept { return FontStyleHelpers::isItalic (font->typefaceStyle); } | |||
| bool Font::isUnderlined() const noexcept { return font->underline; } | |||
| void Font::setBold (const bool shouldBeBold) | |||
| { | |||
| auto flags = getStyleFlags(); | |||
| setStyleFlags (shouldBeBold ? (flags | bold) | |||
| : (flags & ~bold)); | |||
| } | |||
| void Font::setItalic (const bool shouldBeItalic) | |||
| { | |||
| auto flags = getStyleFlags(); | |||
| setStyleFlags (shouldBeItalic ? (flags | italic) | |||
| : (flags & ~italic)); | |||
| } | |||
| void Font::setUnderline (const bool shouldBeUnderlined) | |||
| { | |||
| dupeInternalIfShared(); | |||
| font->underline = shouldBeUnderlined; | |||
| checkTypefaceSuitability(); | |||
| } | |||
| float Font::getAscent() const | |||
| { | |||
| if (font->ascent == 0.0f) | |||
| font->ascent = getTypeface()->getAscent(); | |||
| return font->height * font->ascent; | |||
| } | |||
| float Font::getHeight() const noexcept { return font->height; } | |||
| float Font::getDescent() const { return font->height - getAscent(); } | |||
| float Font::getHeightInPoints() const { return getHeight() * getHeightToPointsFactor(); } | |||
| float Font::getAscentInPoints() const { return getAscent() * getHeightToPointsFactor(); } | |||
| float Font::getDescentInPoints() const { return getDescent() * getHeightToPointsFactor(); } | |||
| int Font::getStringWidth (const String& text) const | |||
| { | |||
| return (int) std::ceil (getStringWidthFloat (text)); | |||
| } | |||
| float Font::getStringWidthFloat (const String& text) const | |||
| { | |||
| // This call isn't thread-safe when there's a message thread running | |||
| jassert (MessageManager::getInstanceWithoutCreating() == nullptr | |||
| || MessageManager::getInstanceWithoutCreating()->currentThreadHasLockedMessageManager()); | |||
| auto w = getTypeface()->getStringWidth (text); | |||
| if (font->kerning != 0.0f) | |||
| w += font->kerning * text.length(); | |||
| return w * font->height * font->horizontalScale; | |||
| } | |||
| void Font::getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets) const | |||
| { | |||
| // This call isn't thread-safe when there's a message thread running | |||
| jassert (MessageManager::getInstanceWithoutCreating() == nullptr | |||
| || MessageManager::getInstanceWithoutCreating()->currentThreadHasLockedMessageManager()); | |||
| getTypeface()->getGlyphPositions (text, glyphs, xOffsets); | |||
| if (auto num = xOffsets.size()) | |||
| { | |||
| auto scale = font->height * font->horizontalScale; | |||
| auto* x = xOffsets.getRawDataPointer(); | |||
| if (font->kerning != 0.0f) | |||
| { | |||
| for (int i = 0; i < num; ++i) | |||
| x[i] = (x[i] + i * font->kerning) * scale; | |||
| } | |||
| else | |||
| { | |||
| for (int i = 0; i < num; ++i) | |||
| x[i] *= scale; | |||
| } | |||
| } | |||
| } | |||
| void Font::findFonts (Array<Font>& destArray) | |||
| { | |||
| for (auto& name : findAllTypefaceNames()) | |||
| { | |||
| auto styles = findAllTypefaceStyles (name); | |||
| String style ("Regular"); | |||
| if (! styles.contains (style, true)) | |||
| style = styles[0]; | |||
| destArray.add (Font (name, style, FontValues::defaultFontHeight)); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| String Font::toString() const | |||
| { | |||
| String s; | |||
| if (getTypefaceName() != getDefaultSansSerifFontName()) | |||
| s << getTypefaceName() << "; "; | |||
| s << String (getHeight(), 1); | |||
| if (getTypefaceStyle() != getDefaultStyle()) | |||
| s << ' ' << getTypefaceStyle(); | |||
| return s; | |||
| } | |||
| Font Font::fromString (const String& fontDescription) | |||
| { | |||
| const int separator = fontDescription.indexOfChar (';'); | |||
| String name; | |||
| if (separator > 0) | |||
| name = fontDescription.substring (0, separator).trim(); | |||
| if (name.isEmpty()) | |||
| name = getDefaultSansSerifFontName(); | |||
| String sizeAndStyle (fontDescription.substring (separator + 1).trimStart()); | |||
| float height = sizeAndStyle.getFloatValue(); | |||
| if (height <= 0) | |||
| height = 10.0f; | |||
| const String style (sizeAndStyle.fromFirstOccurrenceOf (" ", false, false)); | |||
| return Font (name, style, height); | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,478 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Represents a particular font, including its size, style, etc. | |||
| Apart from the typeface to be used, a Font object also dictates whether | |||
| the font is bold, italic, underlined, how big it is, and its kerning and | |||
| horizontal scale factor. | |||
| @see Typeface | |||
| */ | |||
| class JUCE_API Font | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** A combination of these values is used by the constructor to specify the | |||
| style of font to use. | |||
| */ | |||
| enum FontStyleFlags | |||
| { | |||
| plain = 0, /**< indicates a plain, non-bold, non-italic version of the font. @see setStyleFlags */ | |||
| bold = 1, /**< boldens the font. @see setStyleFlags */ | |||
| italic = 2, /**< finds an italic version of the font. @see setStyleFlags */ | |||
| underlined = 4 /**< underlines the font. @see setStyleFlags */ | |||
| }; | |||
| //============================================================================== | |||
| /** Creates a sans-serif font in a given size. | |||
| @param fontHeight the height in pixels (can be fractional) | |||
| @param styleFlags the style to use - this can be a combination of the | |||
| Font::bold, Font::italic and Font::underlined, or | |||
| just Font::plain for the normal style. | |||
| @see FontStyleFlags, getDefaultSansSerifFontName | |||
| */ | |||
| Font (float fontHeight, int styleFlags = plain); | |||
| /** Creates a font with a given typeface and parameters. | |||
| @param typefaceName the font family of the typeface to use | |||
| @param fontHeight the height in pixels (can be fractional) | |||
| @param styleFlags the style to use - this can be a combination of the | |||
| Font::bold, Font::italic and Font::underlined, or | |||
| just Font::plain for the normal style. | |||
| @see FontStyleFlags, getDefaultSansSerifFontName | |||
| */ | |||
| Font (const String& typefaceName, float fontHeight, int styleFlags); | |||
| /** Creates a font with a given typeface and parameters. | |||
| @param typefaceName the font family of the typeface to use | |||
| @param typefaceStyle the font style of the typeface to use | |||
| @param fontHeight the height in pixels (can be fractional) | |||
| */ | |||
| Font (const String& typefaceName, const String& typefaceStyle, float fontHeight); | |||
| /** Creates a copy of another Font object. */ | |||
| Font (const Font& other) noexcept; | |||
| /** Creates a font for a typeface. */ | |||
| Font (const Typeface::Ptr& typeface); | |||
| /** Creates a basic sans-serif font at a default height. | |||
| You should use one of the other constructors for creating a font that you're planning | |||
| on drawing with - this constructor is here to help initialise objects before changing | |||
| the font's settings later. | |||
| */ | |||
| Font(); | |||
| /** Move constructor */ | |||
| Font (Font&& other) noexcept; | |||
| /** Move assignment operator */ | |||
| Font& operator= (Font&& other) noexcept; | |||
| /** Copies this font from another one. */ | |||
| Font& operator= (const Font& other) noexcept; | |||
| bool operator== (const Font& other) const noexcept; | |||
| bool operator!= (const Font& other) const noexcept; | |||
| /** Destructor. */ | |||
| ~Font() noexcept; | |||
| //============================================================================== | |||
| /** Changes the font family of the typeface. | |||
| e.g. "Arial", "Courier", etc. | |||
| This may also be set to Font::getDefaultSansSerifFontName(), Font::getDefaultSerifFontName(), | |||
| or Font::getDefaultMonospacedFontName(), which are not actual platform-specific font family names, | |||
| but are generic font family names that are used to represent the various default fonts. | |||
| If you need to know the exact typeface font family being used, you can call | |||
| Font::getTypeface()->getName(), which will give you the platform-specific font family. | |||
| If a suitable font isn't found on the machine, it'll just use a default instead. | |||
| */ | |||
| void setTypefaceName (const String& faceName); | |||
| /** Returns the font family of the typeface that this font uses. | |||
| e.g. "Arial", "Courier", etc. | |||
| This may also be set to Font::getDefaultSansSerifFontName(), Font::getDefaultSerifFontName(), | |||
| or Font::getDefaultMonospacedFontName(), which are not actual platform-specific font family names, | |||
| but are generic font familiy names that are used to represent the various default fonts. | |||
| If you need to know the exact typeface font family being used, you can call | |||
| Font::getTypeface()->getName(), which will give you the platform-specific font family. | |||
| */ | |||
| const String& getTypefaceName() const noexcept; | |||
| //============================================================================== | |||
| /** Returns the font style of the typeface that this font uses. | |||
| @see withTypefaceStyle, getAvailableStyles() | |||
| */ | |||
| const String& getTypefaceStyle() const noexcept; | |||
| /** Changes the font style of the typeface. | |||
| @see getAvailableStyles() | |||
| */ | |||
| void setTypefaceStyle (const String& newStyle); | |||
| /** Returns a copy of this font with a new typeface style. | |||
| @see getAvailableStyles() | |||
| */ | |||
| Font withTypefaceStyle (const String& newStyle) const; | |||
| /** Returns a list of the styles that this font can use. */ | |||
| StringArray getAvailableStyles() const; | |||
| //============================================================================== | |||
| /** Returns a typeface font family that represents the default sans-serif font. | |||
| This is also the typeface that will be used when a font is created without | |||
| specifying any typeface details. | |||
| Note that this method just returns a generic placeholder string that means "the default | |||
| sans-serif font" - it's not the actual font family of this font. | |||
| @see setTypefaceName, getDefaultSerifFontName, getDefaultMonospacedFontName | |||
| */ | |||
| static const String& getDefaultSansSerifFontName(); | |||
| /** Returns a typeface font family that represents the default serif font. | |||
| Note that this method just returns a generic placeholder string that means "the default | |||
| serif font" - it's not the actual font family of this font. | |||
| @see setTypefaceName, getDefaultSansSerifFontName, getDefaultMonospacedFontName | |||
| */ | |||
| static const String& getDefaultSerifFontName(); | |||
| /** Returns a typeface font family that represents the default monospaced font. | |||
| Note that this method just returns a generic placeholder string that means "the default | |||
| monospaced font" - it's not the actual font family of this font. | |||
| @see setTypefaceName, getDefaultSansSerifFontName, getDefaultSerifFontName | |||
| */ | |||
| static const String& getDefaultMonospacedFontName(); | |||
| /** Returns a font style name that represents the default style. | |||
| Note that this method just returns a generic placeholder string that means "the default | |||
| font style" - it's not the actual name of the font style of any particular font. | |||
| @see setTypefaceStyle | |||
| */ | |||
| static const String& getDefaultStyle(); | |||
| /** Returns the default system typeface for the given font. */ | |||
| static Typeface::Ptr getDefaultTypefaceForFont (const Font& font); | |||
| //============================================================================== | |||
| /** Returns a copy of this font with a new height. */ | |||
| Font withHeight (float height) const; | |||
| /** Returns a copy of this font with a new height, specified in points. */ | |||
| Font withPointHeight (float heightInPoints) const; | |||
| /** Changes the font's height. | |||
| @see getHeight, withHeight, setHeightWithoutChangingWidth | |||
| */ | |||
| void setHeight (float newHeight); | |||
| /** Changes the font's height without changing its width. | |||
| This alters the horizontal scale to compensate for the change in height. | |||
| */ | |||
| void setHeightWithoutChangingWidth (float newHeight); | |||
| /** Returns the total height of this font, in pixels. | |||
| This is the maximum height, from the top of the ascent to the bottom of the | |||
| descenders. | |||
| @see withHeight, setHeightWithoutChangingWidth, getAscent | |||
| */ | |||
| float getHeight() const noexcept; | |||
| /** Returns the total height of this font, in points. | |||
| This is the maximum height, from the top of the ascent to the bottom of the | |||
| descenders. | |||
| @see withPointHeight, getHeight | |||
| */ | |||
| float getHeightInPoints() const; | |||
| /** Returns the height of the font above its baseline, in pixels. | |||
| This is the maximum height from the baseline to the top. | |||
| @see getHeight, getDescent | |||
| */ | |||
| float getAscent() const; | |||
| /** Returns the height of the font above its baseline, in points. | |||
| This is the maximum height from the baseline to the top. | |||
| @see getHeight, getDescent | |||
| */ | |||
| float getAscentInPoints() const; | |||
| /** Returns the amount that the font descends below its baseline, in pixels. | |||
| This is calculated as (getHeight() - getAscent()). | |||
| @see getAscent, getHeight | |||
| */ | |||
| float getDescent() const; | |||
| /** Returns the amount that the font descends below its baseline, in points. | |||
| This is calculated as (getHeight() - getAscent()). | |||
| @see getAscent, getHeight | |||
| */ | |||
| float getDescentInPoints() const; | |||
| //============================================================================== | |||
| /** Returns the font's style flags. | |||
| This will return a bitwise-or'ed combination of values from the FontStyleFlags | |||
| enum, to describe whether the font is bold, italic, etc. | |||
| @see FontStyleFlags, withStyle | |||
| */ | |||
| int getStyleFlags() const noexcept; | |||
| /** Returns a copy of this font with the given set of style flags. | |||
| @param styleFlags a bitwise-or'ed combination of values from the FontStyleFlags enum. | |||
| @see FontStyleFlags, getStyleFlags | |||
| */ | |||
| Font withStyle (int styleFlags) const; | |||
| /** Changes the font's style. | |||
| @param newFlags a bitwise-or'ed combination of values from the FontStyleFlags enum. | |||
| @see FontStyleFlags, withStyle | |||
| */ | |||
| void setStyleFlags (int newFlags); | |||
| //============================================================================== | |||
| /** Makes the font bold or non-bold. */ | |||
| void setBold (bool shouldBeBold); | |||
| /** Returns a copy of this font with the bold attribute set. | |||
| If the font does not have a bold version, this will return the default font. | |||
| */ | |||
| Font boldened() const; | |||
| /** Returns true if the font is bold. */ | |||
| bool isBold() const noexcept; | |||
| /** Makes the font italic or non-italic. */ | |||
| void setItalic (bool shouldBeItalic); | |||
| /** Returns a copy of this font with the italic attribute set. */ | |||
| Font italicised() const; | |||
| /** Returns true if the font is italic. */ | |||
| bool isItalic() const noexcept; | |||
| /** Makes the font underlined or non-underlined. */ | |||
| void setUnderline (bool shouldBeUnderlined); | |||
| /** Returns true if the font is underlined. */ | |||
| bool isUnderlined() const noexcept; | |||
| //============================================================================== | |||
| /** Returns the font's horizontal scale. | |||
| A value of 1.0 is the normal scale, less than this will be narrower, greater | |||
| than 1.0 will be stretched out. | |||
| @see withHorizontalScale | |||
| */ | |||
| float getHorizontalScale() const noexcept; | |||
| /** Returns a copy of this font with a new horizontal scale. | |||
| @param scaleFactor a value of 1.0 is the normal scale, less than this will be | |||
| narrower, greater than 1.0 will be stretched out. | |||
| @see getHorizontalScale | |||
| */ | |||
| Font withHorizontalScale (float scaleFactor) const; | |||
| /** Changes the font's horizontal scale factor. | |||
| @param scaleFactor a value of 1.0 is the normal scale, less than this will be | |||
| narrower, greater than 1.0 will be stretched out. | |||
| */ | |||
| void setHorizontalScale (float scaleFactor); | |||
| /** Returns the minimum horizontal scale to which fonts may be squashed when trying to | |||
| create a layout. | |||
| @see setDefaultMinimumHorizontalScaleFactor | |||
| */ | |||
| static float getDefaultMinimumHorizontalScaleFactor() noexcept; | |||
| /** Sets the minimum horizontal scale to which fonts may be squashed when trying to | |||
| create a text layout. | |||
| @see getDefaultMinimumHorizontalScaleFactor | |||
| */ | |||
| static void setDefaultMinimumHorizontalScaleFactor (float newMinimumScaleFactor) noexcept; | |||
| /** Returns the font's kerning. | |||
| This is the extra space added between adjacent characters, as a proportion | |||
| of the font's height. | |||
| A value of zero is normal spacing, positive values will spread the letters | |||
| out more, and negative values make them closer together. | |||
| */ | |||
| float getExtraKerningFactor() const noexcept; | |||
| /** Returns a copy of this font with a new kerning factor. | |||
| @param extraKerning a multiple of the font's height that will be added | |||
| to space between the characters. So a value of zero is | |||
| normal spacing, positive values spread the letters out, | |||
| negative values make them closer together. | |||
| */ | |||
| Font withExtraKerningFactor (float extraKerning) const; | |||
| /** Changes the font's kerning. | |||
| @param extraKerning a multiple of the font's height that will be added | |||
| to space between the characters. So a value of zero is | |||
| normal spacing, positive values spread the letters out, | |||
| negative values make them closer together. | |||
| */ | |||
| void setExtraKerningFactor (float extraKerning); | |||
| //============================================================================== | |||
| /** Changes all the font's characteristics with one call. */ | |||
| void setSizeAndStyle (float newHeight, | |||
| int newStyleFlags, | |||
| float newHorizontalScale, | |||
| float newKerningAmount); | |||
| /** Changes all the font's characteristics with one call. */ | |||
| void setSizeAndStyle (float newHeight, | |||
| const String& newStyle, | |||
| float newHorizontalScale, | |||
| float newKerningAmount); | |||
| //============================================================================== | |||
| /** Returns the total width of a string as it would be drawn using this font. | |||
| For a more accurate floating-point result, use getStringWidthFloat(). | |||
| */ | |||
| int getStringWidth (const String& text) const; | |||
| /** Returns the total width of a string as it would be drawn using this font. | |||
| @see getStringWidth | |||
| */ | |||
| float getStringWidthFloat (const String& text) const; | |||
| /** Returns the series of glyph numbers and their x offsets needed to represent a string. | |||
| An extra x offset is added at the end of the run, to indicate where the right hand | |||
| edge of the last character is. | |||
| */ | |||
| void getGlyphPositions (const String& text, Array <int>& glyphs, Array <float>& xOffsets) const; | |||
| //============================================================================== | |||
| /** Returns the typeface used by this font. | |||
| Note that the object returned may go out of scope if this font is deleted | |||
| or has its style changed. | |||
| */ | |||
| Typeface* getTypeface() const; | |||
| /** Creates an array of Font objects to represent all the fonts on the system. | |||
| If you just need the font family names of the typefaces, you can also use | |||
| findAllTypefaceNames() instead. | |||
| @param results the array to which new Font objects will be added. | |||
| */ | |||
| static void findFonts (Array<Font>& results); | |||
| /** Returns a list of all the available typeface font families. | |||
| The names returned can be passed into setTypefaceName(). | |||
| You can use this instead of findFonts() if you only need their font family names, | |||
| and not font objects. | |||
| */ | |||
| static StringArray findAllTypefaceNames(); | |||
| /** Returns a list of all the available typeface font styles. | |||
| The names returned can be passed into setTypefaceStyle(). | |||
| You can use this instead of findFonts() if you only need their styles, and not | |||
| font objects. | |||
| */ | |||
| static StringArray findAllTypefaceStyles (const String& family); | |||
| //============================================================================== | |||
| /** Returns the font family of the typeface to be used for rendering glyphs that aren't | |||
| found in the requested typeface. | |||
| */ | |||
| static const String& getFallbackFontName(); | |||
| /** Sets the (platform-specific) font family of the typeface to use to find glyphs that | |||
| aren't available in whatever font you're trying to use. | |||
| */ | |||
| static void setFallbackFontName (const String& name); | |||
| /** Returns the font style of the typeface to be used for rendering glyphs that aren't | |||
| found in the requested typeface. | |||
| */ | |||
| static const String& getFallbackFontStyle(); | |||
| /** Sets the (platform-specific) font style of the typeface to use to find glyphs that | |||
| aren't available in whatever font you're trying to use. | |||
| */ | |||
| static void setFallbackFontStyle (const String& style); | |||
| //============================================================================== | |||
| /** Creates a string to describe this font. | |||
| The string will contain information to describe the font's typeface, size, and | |||
| style. To recreate the font from this string, use fromString(). | |||
| */ | |||
| String toString() const; | |||
| /** Recreates a font from its stringified encoding. | |||
| This method takes a string that was created by toString(), and recreates the | |||
| original font. | |||
| */ | |||
| static Font fromString (const String& fontDescription); | |||
| private: | |||
| //============================================================================== | |||
| class SharedFontInternal; | |||
| ReferenceCountedObjectPtr<SharedFontInternal> font; | |||
| void dupeInternalIfShared(); | |||
| void checkTypefaceSuitability(); | |||
| float getHeightToPointsFactor() const; | |||
| JUCE_LEAK_DETECTOR (Font) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,820 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| PositionedGlyph::PositionedGlyph() noexcept | |||
| : character (0), glyph (0), x (0), y (0), w (0), whitespace (false) | |||
| { | |||
| } | |||
| PositionedGlyph::PositionedGlyph (const Font& font_, const juce_wchar character_, const int glyph_, | |||
| const float x_, const float y_, const float w_, const bool whitespace_) | |||
| : font (font_), character (character_), glyph (glyph_), | |||
| x (x_), y (y_), w (w_), whitespace (whitespace_) | |||
| { | |||
| } | |||
| PositionedGlyph::PositionedGlyph (const PositionedGlyph& other) | |||
| : font (other.font), character (other.character), glyph (other.glyph), | |||
| x (other.x), y (other.y), w (other.w), whitespace (other.whitespace) | |||
| { | |||
| } | |||
| PositionedGlyph::PositionedGlyph (PositionedGlyph&& other) noexcept | |||
| : font (static_cast<Font&&> (other.font)), | |||
| character (other.character), glyph (other.glyph), | |||
| x (other.x), y (other.y), w (other.w), whitespace (other.whitespace) | |||
| { | |||
| } | |||
| PositionedGlyph& PositionedGlyph::operator= (PositionedGlyph&& other) noexcept | |||
| { | |||
| font = static_cast<Font&&> (other.font); | |||
| character = other.character; | |||
| glyph = other.glyph; | |||
| x = other.x; | |||
| y = other.y; | |||
| w = other.w; | |||
| whitespace = other.whitespace; | |||
| return *this; | |||
| } | |||
| PositionedGlyph::~PositionedGlyph() {} | |||
| PositionedGlyph& PositionedGlyph::operator= (const PositionedGlyph& other) | |||
| { | |||
| font = other.font; | |||
| character = other.character; | |||
| glyph = other.glyph; | |||
| x = other.x; | |||
| y = other.y; | |||
| w = other.w; | |||
| whitespace = other.whitespace; | |||
| return *this; | |||
| } | |||
| static inline void drawGlyphWithFont (Graphics& g, int glyph, const Font& font, const AffineTransform& t) | |||
| { | |||
| LowLevelGraphicsContext& context = g.getInternalContext(); | |||
| context.setFont (font); | |||
| context.drawGlyph (glyph, t); | |||
| } | |||
| void PositionedGlyph::draw (Graphics& g) const | |||
| { | |||
| if (! isWhitespace()) | |||
| drawGlyphWithFont (g, glyph, font, AffineTransform::translation (x, y)); | |||
| } | |||
| void PositionedGlyph::draw (Graphics& g, const AffineTransform& transform) const | |||
| { | |||
| if (! isWhitespace()) | |||
| drawGlyphWithFont (g, glyph, font, AffineTransform::translation (x, y).followedBy (transform)); | |||
| } | |||
| void PositionedGlyph::createPath (Path& path) const | |||
| { | |||
| if (! isWhitespace()) | |||
| { | |||
| if (auto* t = font.getTypeface()) | |||
| { | |||
| Path p; | |||
| t->getOutlineForGlyph (glyph, p); | |||
| path.addPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()) | |||
| .translated (x, y)); | |||
| } | |||
| } | |||
| } | |||
| bool PositionedGlyph::hitTest (float px, float py) const | |||
| { | |||
| if (getBounds().contains (px, py) && ! isWhitespace()) | |||
| { | |||
| if (auto* t = font.getTypeface()) | |||
| { | |||
| Path p; | |||
| t->getOutlineForGlyph (glyph, p); | |||
| AffineTransform::translation (-x, -y) | |||
| .scaled (1.0f / (font.getHeight() * font.getHorizontalScale()), 1.0f / font.getHeight()) | |||
| .transformPoint (px, py); | |||
| return p.contains (px, py); | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| void PositionedGlyph::moveBy (const float deltaX, | |||
| const float deltaY) | |||
| { | |||
| x += deltaX; | |||
| y += deltaY; | |||
| } | |||
| //============================================================================== | |||
| GlyphArrangement::GlyphArrangement() | |||
| { | |||
| glyphs.ensureStorageAllocated (128); | |||
| } | |||
| GlyphArrangement::GlyphArrangement (const GlyphArrangement& other) | |||
| : glyphs (other.glyphs) | |||
| { | |||
| } | |||
| GlyphArrangement& GlyphArrangement::operator= (const GlyphArrangement& other) | |||
| { | |||
| glyphs = other.glyphs; | |||
| return *this; | |||
| } | |||
| GlyphArrangement::~GlyphArrangement() | |||
| { | |||
| } | |||
| //============================================================================== | |||
| void GlyphArrangement::clear() | |||
| { | |||
| glyphs.clear(); | |||
| } | |||
| PositionedGlyph& GlyphArrangement::getGlyph (const int index) const noexcept | |||
| { | |||
| return glyphs.getReference (index); | |||
| } | |||
| //============================================================================== | |||
| void GlyphArrangement::addGlyphArrangement (const GlyphArrangement& other) | |||
| { | |||
| glyphs.addArray (other.glyphs); | |||
| } | |||
| void GlyphArrangement::addGlyph (const PositionedGlyph& glyph) | |||
| { | |||
| glyphs.add (glyph); | |||
| } | |||
| void GlyphArrangement::removeRangeOfGlyphs (int startIndex, const int num) | |||
| { | |||
| glyphs.removeRange (startIndex, num < 0 ? glyphs.size() : num); | |||
| } | |||
| //============================================================================== | |||
| void GlyphArrangement::addLineOfText (const Font& font, | |||
| const String& text, | |||
| const float xOffset, | |||
| const float yOffset) | |||
| { | |||
| addCurtailedLineOfText (font, text, xOffset, yOffset, 1.0e10f, false); | |||
| } | |||
| void GlyphArrangement::addCurtailedLineOfText (const Font& font, | |||
| const String& text, | |||
| const float xOffset, | |||
| const float yOffset, | |||
| const float maxWidthPixels, | |||
| const bool useEllipsis) | |||
| { | |||
| if (text.isNotEmpty()) | |||
| { | |||
| Array<int> newGlyphs; | |||
| Array<float> xOffsets; | |||
| font.getGlyphPositions (text, newGlyphs, xOffsets); | |||
| const int textLen = newGlyphs.size(); | |||
| glyphs.ensureStorageAllocated (glyphs.size() + textLen); | |||
| auto t = text.getCharPointer(); | |||
| for (int i = 0; i < textLen; ++i) | |||
| { | |||
| const float nextX = xOffsets.getUnchecked (i + 1); | |||
| if (nextX > maxWidthPixels + 1.0f) | |||
| { | |||
| // curtail the string if it's too wide.. | |||
| if (useEllipsis && textLen > 3 && glyphs.size() >= 3) | |||
| insertEllipsis (font, xOffset + maxWidthPixels, 0, glyphs.size()); | |||
| break; | |||
| } | |||
| const float thisX = xOffsets.getUnchecked (i); | |||
| const bool isWhitespace = t.isWhitespace(); | |||
| glyphs.add (PositionedGlyph (font, t.getAndAdvance(), | |||
| newGlyphs.getUnchecked(i), | |||
| xOffset + thisX, yOffset, | |||
| nextX - thisX, isWhitespace)); | |||
| } | |||
| } | |||
| } | |||
| int GlyphArrangement::insertEllipsis (const Font& font, const float maxXPos, | |||
| const int startIndex, int endIndex) | |||
| { | |||
| int numDeleted = 0; | |||
| if (glyphs.size() > 0) | |||
| { | |||
| Array<int> dotGlyphs; | |||
| Array<float> dotXs; | |||
| font.getGlyphPositions ("..", dotGlyphs, dotXs); | |||
| const float dx = dotXs[1]; | |||
| float xOffset = 0.0f, yOffset = 0.0f; | |||
| while (endIndex > startIndex) | |||
| { | |||
| auto& pg = glyphs.getReference (--endIndex); | |||
| xOffset = pg.x; | |||
| yOffset = pg.y; | |||
| glyphs.remove (endIndex); | |||
| ++numDeleted; | |||
| if (xOffset + dx * 3 <= maxXPos) | |||
| break; | |||
| } | |||
| for (int i = 3; --i >= 0;) | |||
| { | |||
| glyphs.insert (endIndex++, PositionedGlyph (font, '.', dotGlyphs.getFirst(), | |||
| xOffset, yOffset, dx, false)); | |||
| --numDeleted; | |||
| xOffset += dx; | |||
| if (xOffset > maxXPos) | |||
| break; | |||
| } | |||
| } | |||
| return numDeleted; | |||
| } | |||
| void GlyphArrangement::addJustifiedText (const Font& font, | |||
| const String& text, | |||
| float x, float y, | |||
| const float maxLineWidth, | |||
| Justification horizontalLayout) | |||
| { | |||
| int lineStartIndex = glyphs.size(); | |||
| addLineOfText (font, text, x, y); | |||
| const float originalY = y; | |||
| while (lineStartIndex < glyphs.size()) | |||
| { | |||
| int i = lineStartIndex; | |||
| if (glyphs.getReference(i).getCharacter() != '\n' | |||
| && glyphs.getReference(i).getCharacter() != '\r') | |||
| ++i; | |||
| const float lineMaxX = glyphs.getReference (lineStartIndex).getLeft() + maxLineWidth; | |||
| int lastWordBreakIndex = -1; | |||
| while (i < glyphs.size()) | |||
| { | |||
| auto& pg = glyphs.getReference (i); | |||
| const juce_wchar c = pg.getCharacter(); | |||
| if (c == '\r' || c == '\n') | |||
| { | |||
| ++i; | |||
| if (c == '\r' && i < glyphs.size() | |||
| && glyphs.getReference(i).getCharacter() == '\n') | |||
| ++i; | |||
| break; | |||
| } | |||
| if (pg.isWhitespace()) | |||
| { | |||
| lastWordBreakIndex = i + 1; | |||
| } | |||
| else if (pg.getRight() - 0.0001f >= lineMaxX) | |||
| { | |||
| if (lastWordBreakIndex >= 0) | |||
| i = lastWordBreakIndex; | |||
| break; | |||
| } | |||
| ++i; | |||
| } | |||
| const float currentLineStartX = glyphs.getReference (lineStartIndex).getLeft(); | |||
| float currentLineEndX = currentLineStartX; | |||
| for (int j = i; --j >= lineStartIndex;) | |||
| { | |||
| if (! glyphs.getReference (j).isWhitespace()) | |||
| { | |||
| currentLineEndX = glyphs.getReference (j).getRight(); | |||
| break; | |||
| } | |||
| } | |||
| float deltaX = 0.0f; | |||
| if (horizontalLayout.testFlags (Justification::horizontallyJustified)) | |||
| spreadOutLine (lineStartIndex, i - lineStartIndex, maxLineWidth); | |||
| else if (horizontalLayout.testFlags (Justification::horizontallyCentred)) | |||
| deltaX = (maxLineWidth - (currentLineEndX - currentLineStartX)) * 0.5f; | |||
| else if (horizontalLayout.testFlags (Justification::right)) | |||
| deltaX = maxLineWidth - (currentLineEndX - currentLineStartX); | |||
| moveRangeOfGlyphs (lineStartIndex, i - lineStartIndex, | |||
| x + deltaX - currentLineStartX, y - originalY); | |||
| lineStartIndex = i; | |||
| y += font.getHeight(); | |||
| } | |||
| } | |||
| void GlyphArrangement::addFittedText (const Font& f, | |||
| const String& text, | |||
| const float x, const float y, | |||
| const float width, const float height, | |||
| Justification layout, | |||
| int maximumLines, | |||
| float minimumHorizontalScale) | |||
| { | |||
| if (minimumHorizontalScale == 0.0f) | |||
| minimumHorizontalScale = Font::getDefaultMinimumHorizontalScaleFactor(); | |||
| // doesn't make much sense if this is outside a sensible range of 0.5 to 1.0 | |||
| jassert (minimumHorizontalScale > 0 && minimumHorizontalScale <= 1.0f); | |||
| if (text.containsAnyOf ("\r\n")) | |||
| { | |||
| addLinesWithLineBreaks (text, f, x, y, width, height, layout); | |||
| } | |||
| else | |||
| { | |||
| const int startIndex = glyphs.size(); | |||
| const String trimmed (text.trim()); | |||
| addLineOfText (f, trimmed, x, y); | |||
| const int numGlyphs = glyphs.size() - startIndex; | |||
| if (numGlyphs > 0) | |||
| { | |||
| const float lineWidth = glyphs.getReference (glyphs.size() - 1).getRight() | |||
| - glyphs.getReference (startIndex).getLeft(); | |||
| if (lineWidth > 0) | |||
| { | |||
| if (lineWidth * minimumHorizontalScale < width) | |||
| { | |||
| if (lineWidth > width) | |||
| stretchRangeOfGlyphs (startIndex, numGlyphs, width / lineWidth); | |||
| justifyGlyphs (startIndex, numGlyphs, x, y, width, height, layout); | |||
| } | |||
| else if (maximumLines <= 1) | |||
| { | |||
| fitLineIntoSpace (startIndex, numGlyphs, x, y, width, height, | |||
| f, layout, minimumHorizontalScale); | |||
| } | |||
| else | |||
| { | |||
| splitLines (trimmed, f, startIndex, x, y, width, height, | |||
| maximumLines, lineWidth, layout, minimumHorizontalScale); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void GlyphArrangement::moveRangeOfGlyphs (int startIndex, int num, const float dx, const float dy) | |||
| { | |||
| jassert (startIndex >= 0); | |||
| if (dx != 0.0f || dy != 0.0f) | |||
| { | |||
| if (num < 0 || startIndex + num > glyphs.size()) | |||
| num = glyphs.size() - startIndex; | |||
| while (--num >= 0) | |||
| glyphs.getReference (startIndex++).moveBy (dx, dy); | |||
| } | |||
| } | |||
| void GlyphArrangement::addLinesWithLineBreaks (const String& text, const Font& f, | |||
| float x, float y, float width, float height, Justification layout) | |||
| { | |||
| GlyphArrangement ga; | |||
| ga.addJustifiedText (f, text, x, y, width, layout); | |||
| auto bb = ga.getBoundingBox (0, -1, false); | |||
| float dy = y - bb.getY(); | |||
| if (layout.testFlags (Justification::verticallyCentred)) dy += (height - bb.getHeight()) * 0.5f; | |||
| else if (layout.testFlags (Justification::bottom)) dy += (height - bb.getHeight()); | |||
| ga.moveRangeOfGlyphs (0, -1, 0.0f, dy); | |||
| glyphs.addArray (ga.glyphs); | |||
| } | |||
| int GlyphArrangement::fitLineIntoSpace (int start, int numGlyphs, float x, float y, float w, float h, const Font& font, | |||
| Justification justification, float minimumHorizontalScale) | |||
| { | |||
| int numDeleted = 0; | |||
| const float lineStartX = glyphs.getReference (start).getLeft(); | |||
| float lineWidth = glyphs.getReference (start + numGlyphs - 1).getRight() - lineStartX; | |||
| if (lineWidth > w) | |||
| { | |||
| if (minimumHorizontalScale < 1.0f) | |||
| { | |||
| stretchRangeOfGlyphs (start, numGlyphs, jmax (minimumHorizontalScale, w / lineWidth)); | |||
| lineWidth = glyphs.getReference (start + numGlyphs - 1).getRight() - lineStartX - 0.5f; | |||
| } | |||
| if (lineWidth > w) | |||
| { | |||
| numDeleted = insertEllipsis (font, lineStartX + w, start, start + numGlyphs); | |||
| numGlyphs -= numDeleted; | |||
| } | |||
| } | |||
| justifyGlyphs (start, numGlyphs, x, y, w, h, justification); | |||
| return numDeleted; | |||
| } | |||
| void GlyphArrangement::stretchRangeOfGlyphs (int startIndex, int num, | |||
| const float horizontalScaleFactor) | |||
| { | |||
| jassert (startIndex >= 0); | |||
| if (num < 0 || startIndex + num > glyphs.size()) | |||
| num = glyphs.size() - startIndex; | |||
| if (num > 0) | |||
| { | |||
| const float xAnchor = glyphs.getReference (startIndex).getLeft(); | |||
| while (--num >= 0) | |||
| { | |||
| auto& pg = glyphs.getReference (startIndex++); | |||
| pg.x = xAnchor + (pg.x - xAnchor) * horizontalScaleFactor; | |||
| pg.font.setHorizontalScale (pg.font.getHorizontalScale() * horizontalScaleFactor); | |||
| pg.w *= horizontalScaleFactor; | |||
| } | |||
| } | |||
| } | |||
| Rectangle<float> GlyphArrangement::getBoundingBox (int startIndex, int num, const bool includeWhitespace) const | |||
| { | |||
| jassert (startIndex >= 0); | |||
| if (num < 0 || startIndex + num > glyphs.size()) | |||
| num = glyphs.size() - startIndex; | |||
| Rectangle<float> result; | |||
| while (--num >= 0) | |||
| { | |||
| auto& pg = glyphs.getReference (startIndex++); | |||
| if (includeWhitespace || ! pg.isWhitespace()) | |||
| result = result.getUnion (pg.getBounds()); | |||
| } | |||
| return result; | |||
| } | |||
| void GlyphArrangement::justifyGlyphs (const int startIndex, const int num, | |||
| const float x, const float y, const float width, const float height, | |||
| Justification justification) | |||
| { | |||
| jassert (num >= 0 && startIndex >= 0); | |||
| if (glyphs.size() > 0 && num > 0) | |||
| { | |||
| auto bb = getBoundingBox (startIndex, num, ! justification.testFlags (Justification::horizontallyJustified | |||
| | Justification::horizontallyCentred)); | |||
| float deltaX = 0.0f, deltaY = 0.0f; | |||
| if (justification.testFlags (Justification::horizontallyJustified)) deltaX = x - bb.getX(); | |||
| else if (justification.testFlags (Justification::horizontallyCentred)) deltaX = x + (width - bb.getWidth()) * 0.5f - bb.getX(); | |||
| else if (justification.testFlags (Justification::right)) deltaX = x + width - bb.getRight(); | |||
| else deltaX = x - bb.getX(); | |||
| if (justification.testFlags (Justification::top)) deltaY = y - bb.getY(); | |||
| else if (justification.testFlags (Justification::bottom)) deltaY = y + height - bb.getBottom(); | |||
| else deltaY = y + (height - bb.getHeight()) * 0.5f - bb.getY(); | |||
| moveRangeOfGlyphs (startIndex, num, deltaX, deltaY); | |||
| if (justification.testFlags (Justification::horizontallyJustified)) | |||
| { | |||
| int lineStart = 0; | |||
| float baseY = glyphs.getReference (startIndex).getBaselineY(); | |||
| int i; | |||
| for (i = 0; i < num; ++i) | |||
| { | |||
| const float glyphY = glyphs.getReference (startIndex + i).getBaselineY(); | |||
| if (glyphY != baseY) | |||
| { | |||
| spreadOutLine (startIndex + lineStart, i - lineStart, width); | |||
| lineStart = i; | |||
| baseY = glyphY; | |||
| } | |||
| } | |||
| if (i > lineStart) | |||
| spreadOutLine (startIndex + lineStart, i - lineStart, width); | |||
| } | |||
| } | |||
| } | |||
| void GlyphArrangement::spreadOutLine (const int start, const int num, const float targetWidth) | |||
| { | |||
| if (start + num < glyphs.size() | |||
| && glyphs.getReference (start + num - 1).getCharacter() != '\r' | |||
| && glyphs.getReference (start + num - 1).getCharacter() != '\n') | |||
| { | |||
| int numSpaces = 0; | |||
| int spacesAtEnd = 0; | |||
| for (int i = 0; i < num; ++i) | |||
| { | |||
| if (glyphs.getReference (start + i).isWhitespace()) | |||
| { | |||
| ++spacesAtEnd; | |||
| ++numSpaces; | |||
| } | |||
| else | |||
| { | |||
| spacesAtEnd = 0; | |||
| } | |||
| } | |||
| numSpaces -= spacesAtEnd; | |||
| if (numSpaces > 0) | |||
| { | |||
| const float startX = glyphs.getReference (start).getLeft(); | |||
| const float endX = glyphs.getReference (start + num - 1 - spacesAtEnd).getRight(); | |||
| const float extraPaddingBetweenWords = (targetWidth - (endX - startX)) / (float) numSpaces; | |||
| float deltaX = 0.0f; | |||
| for (int i = 0; i < num; ++i) | |||
| { | |||
| glyphs.getReference (start + i).moveBy (deltaX, 0.0f); | |||
| if (glyphs.getReference (start + i).isWhitespace()) | |||
| deltaX += extraPaddingBetweenWords; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| void GlyphArrangement::splitLines (const String& text, Font font, int startIndex, | |||
| float x, float y, float width, float height, int maximumLines, | |||
| float lineWidth, Justification layout, float minimumHorizontalScale) | |||
| { | |||
| const int length = text.length(); | |||
| const int originalStartIndex = startIndex; | |||
| int numLines = 1; | |||
| if (length <= 12 && ! text.containsAnyOf (" -\t\r\n")) | |||
| maximumLines = 1; | |||
| maximumLines = jmin (maximumLines, length); | |||
| while (numLines < maximumLines) | |||
| { | |||
| ++numLines; | |||
| const float newFontHeight = height / (float) numLines; | |||
| if (newFontHeight < font.getHeight()) | |||
| { | |||
| font.setHeight (jmax (8.0f, newFontHeight)); | |||
| removeRangeOfGlyphs (startIndex, -1); | |||
| addLineOfText (font, text, x, y); | |||
| lineWidth = glyphs.getReference (glyphs.size() - 1).getRight() | |||
| - glyphs.getReference (startIndex).getLeft(); | |||
| } | |||
| // Try to estimate the point at which there are enough lines to fit the text, | |||
| // allowing for unevenness in the lengths due to differently sized words. | |||
| const float lineLengthUnevennessAllowance = 80.0f; | |||
| if (numLines > (lineWidth + lineLengthUnevennessAllowance) / width || newFontHeight < 8.0f) | |||
| break; | |||
| } | |||
| if (numLines < 1) | |||
| numLines = 1; | |||
| float lineY = y; | |||
| float widthPerLine = lineWidth / numLines; | |||
| while (lineY < y + height) | |||
| { | |||
| int i = startIndex; | |||
| const float lineStartX = glyphs.getReference (startIndex).getLeft(); | |||
| const float lineBottomY = lineY + font.getHeight(); | |||
| if (lineBottomY >= y + height) | |||
| { | |||
| widthPerLine = width; | |||
| i = glyphs.size(); | |||
| } | |||
| else | |||
| { | |||
| while (i < glyphs.size()) | |||
| { | |||
| lineWidth = (glyphs.getReference (i).getRight() - lineStartX); | |||
| if (lineWidth > widthPerLine) | |||
| { | |||
| // got to a point where the line's too long, so skip forward to find a | |||
| // good place to break it.. | |||
| const int searchStartIndex = i; | |||
| while (i < glyphs.size()) | |||
| { | |||
| if ((glyphs.getReference (i).getRight() - lineStartX) * minimumHorizontalScale < width) | |||
| { | |||
| if (glyphs.getReference (i).isWhitespace() | |||
| || glyphs.getReference (i).getCharacter() == '-') | |||
| { | |||
| ++i; | |||
| break; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| // can't find a suitable break, so try looking backwards.. | |||
| i = searchStartIndex; | |||
| for (int back = 1; back < jmin (7, i - startIndex - 1); ++back) | |||
| { | |||
| if (glyphs.getReference (i - back).isWhitespace() | |||
| || glyphs.getReference (i - back).getCharacter() == '-') | |||
| { | |||
| i -= back - 1; | |||
| break; | |||
| } | |||
| } | |||
| break; | |||
| } | |||
| ++i; | |||
| } | |||
| break; | |||
| } | |||
| ++i; | |||
| } | |||
| int wsStart = i; | |||
| while (wsStart > 0 && glyphs.getReference (wsStart - 1).isWhitespace()) | |||
| --wsStart; | |||
| int wsEnd = i; | |||
| while (wsEnd < glyphs.size() && glyphs.getReference (wsEnd).isWhitespace()) | |||
| ++wsEnd; | |||
| removeRangeOfGlyphs (wsStart, wsEnd - wsStart); | |||
| i = jmax (wsStart, startIndex + 1); | |||
| } | |||
| i -= fitLineIntoSpace (startIndex, i - startIndex, | |||
| x, lineY, width, font.getHeight(), font, | |||
| layout.getOnlyHorizontalFlags() | Justification::verticallyCentred, | |||
| minimumHorizontalScale); | |||
| startIndex = i; | |||
| lineY = lineBottomY; | |||
| if (startIndex >= glyphs.size()) | |||
| break; | |||
| } | |||
| justifyGlyphs (originalStartIndex, glyphs.size() - originalStartIndex, | |||
| x, y, width, height, layout.getFlags() & ~Justification::horizontallyJustified); | |||
| } | |||
| //============================================================================== | |||
| void GlyphArrangement::drawGlyphUnderline (const Graphics& g, const PositionedGlyph& pg, | |||
| const int i, const AffineTransform& transform) const | |||
| { | |||
| const float lineThickness = (pg.font.getDescent()) * 0.3f; | |||
| float nextX = pg.x + pg.w; | |||
| if (i < glyphs.size() - 1 && glyphs.getReference (i + 1).y == pg.y) | |||
| nextX = glyphs.getReference (i + 1).x; | |||
| Path p; | |||
| p.addRectangle (pg.x, pg.y + lineThickness * 2.0f, nextX - pg.x, lineThickness); | |||
| g.fillPath (p, transform); | |||
| } | |||
| void GlyphArrangement::draw (const Graphics& g) const | |||
| { | |||
| draw (g, AffineTransform()); | |||
| } | |||
| void GlyphArrangement::draw (const Graphics& g, const AffineTransform& transform) const | |||
| { | |||
| auto& context = g.getInternalContext(); | |||
| Font lastFont (context.getFont()); | |||
| bool needToRestore = false; | |||
| for (int i = 0; i < glyphs.size(); ++i) | |||
| { | |||
| auto& pg = glyphs.getReference(i); | |||
| if (pg.font.isUnderlined()) | |||
| drawGlyphUnderline (g, pg, i, transform); | |||
| if (! pg.isWhitespace()) | |||
| { | |||
| if (lastFont != pg.font) | |||
| { | |||
| lastFont = pg.font; | |||
| if (! needToRestore) | |||
| { | |||
| needToRestore = true; | |||
| context.saveState(); | |||
| } | |||
| context.setFont (lastFont); | |||
| } | |||
| context.drawGlyph (pg.glyph, AffineTransform::translation (pg.x, pg.y).followedBy (transform)); | |||
| } | |||
| } | |||
| if (needToRestore) | |||
| context.restoreState(); | |||
| } | |||
| void GlyphArrangement::createPath (Path& path) const | |||
| { | |||
| for (auto& g : glyphs) | |||
| g.createPath (path); | |||
| } | |||
| int GlyphArrangement::findGlyphIndexAt (const float x, const float y) const | |||
| { | |||
| for (int i = 0; i < glyphs.size(); ++i) | |||
| if (glyphs.getReference (i).hitTest (x, y)) | |||
| return i; | |||
| return -1; | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,327 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| A glyph from a particular font, with a particular size, style, | |||
| typeface and position. | |||
| You should rarely need to use this class directly - for most purposes, the | |||
| GlyphArrangement class will do what you need for text layout. | |||
| @see GlyphArrangement, Font | |||
| */ | |||
| class JUCE_API PositionedGlyph | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| PositionedGlyph() noexcept; | |||
| PositionedGlyph (const Font& font, juce_wchar character, int glyphNumber, | |||
| float anchorX, float baselineY, float width, bool isWhitespace); | |||
| PositionedGlyph (const PositionedGlyph&); | |||
| PositionedGlyph& operator= (const PositionedGlyph&); | |||
| /** Move constructor */ | |||
| PositionedGlyph (PositionedGlyph&&) noexcept; | |||
| /** Move assignment operator */ | |||
| PositionedGlyph& operator= (PositionedGlyph&&) noexcept; | |||
| ~PositionedGlyph(); | |||
| /** Returns the character the glyph represents. */ | |||
| juce_wchar getCharacter() const noexcept { return character; } | |||
| /** Checks whether the glyph is actually empty. */ | |||
| bool isWhitespace() const noexcept { return whitespace; } | |||
| /** Returns the position of the glyph's left-hand edge. */ | |||
| float getLeft() const noexcept { return x; } | |||
| /** Returns the position of the glyph's right-hand edge. */ | |||
| float getRight() const noexcept { return x + w; } | |||
| /** Returns the y position of the glyph's baseline. */ | |||
| float getBaselineY() const noexcept { return y; } | |||
| /** Returns the y position of the top of the glyph. */ | |||
| float getTop() const { return y - font.getAscent(); } | |||
| /** Returns the y position of the bottom of the glyph. */ | |||
| float getBottom() const { return y + font.getDescent(); } | |||
| /** Returns the bounds of the glyph. */ | |||
| Rectangle<float> getBounds() const { return Rectangle<float> (x, getTop(), w, font.getHeight()); } | |||
| //============================================================================== | |||
| /** Shifts the glyph's position by a relative amount. */ | |||
| void moveBy (float deltaX, float deltaY); | |||
| //============================================================================== | |||
| /** Draws the glyph into a graphics context. | |||
| (Note that this may change the context's currently selected font). | |||
| */ | |||
| void draw (Graphics& g) const; | |||
| /** Draws the glyph into a graphics context, with an extra transform applied to it. | |||
| (Note that this may change the context's currently selected font). | |||
| */ | |||
| void draw (Graphics& g, const AffineTransform& transform) const; | |||
| /** Returns the path for this glyph. | |||
| @param path the glyph's outline will be appended to this path | |||
| */ | |||
| void createPath (Path& path) const; | |||
| /** Checks to see if a point lies within this glyph. */ | |||
| bool hitTest (float x, float y) const; | |||
| private: | |||
| //============================================================================== | |||
| friend class GlyphArrangement; | |||
| Font font; | |||
| juce_wchar character; | |||
| int glyph; | |||
| float x, y, w; | |||
| bool whitespace; | |||
| JUCE_LEAK_DETECTOR (PositionedGlyph) | |||
| }; | |||
| //============================================================================== | |||
| /** | |||
| A set of glyphs, each with a position. | |||
| You can create a GlyphArrangement, text to it and then draw it onto a | |||
| graphics context. It's used internally by the text methods in the | |||
| Graphics class, but can be used directly if more control is needed. | |||
| @see Font, PositionedGlyph | |||
| */ | |||
| class JUCE_API GlyphArrangement | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates an empty arrangement. */ | |||
| GlyphArrangement(); | |||
| /** Takes a copy of another arrangement. */ | |||
| GlyphArrangement (const GlyphArrangement&); | |||
| /** Copies another arrangement onto this one. | |||
| To add another arrangement without clearing this one, use addGlyphArrangement(). | |||
| */ | |||
| GlyphArrangement& operator= (const GlyphArrangement&); | |||
| /** Destructor. */ | |||
| ~GlyphArrangement(); | |||
| //============================================================================== | |||
| /** Returns the total number of glyphs in the arrangement. */ | |||
| int getNumGlyphs() const noexcept { return glyphs.size(); } | |||
| /** Returns one of the glyphs from the arrangement. | |||
| @param index the glyph's index, from 0 to (getNumGlyphs() - 1). Be | |||
| careful not to pass an out-of-range index here, as it | |||
| doesn't do any bounds-checking. | |||
| */ | |||
| PositionedGlyph& getGlyph (int index) const noexcept; | |||
| //============================================================================== | |||
| /** Clears all text from the arrangement and resets it. */ | |||
| void clear(); | |||
| /** Appends a line of text to the arrangement. | |||
| This will add the text as a single line, where x is the left-hand edge of the | |||
| first character, and y is the position for the text's baseline. | |||
| If the text contains new-lines or carriage-returns, this will ignore them - use | |||
| addJustifiedText() to add multi-line arrangements. | |||
| */ | |||
| void addLineOfText (const Font& font, | |||
| const String& text, | |||
| float x, float y); | |||
| /** Adds a line of text, truncating it if it's wider than a specified size. | |||
| This is the same as addLineOfText(), but if the line's width exceeds the value | |||
| specified in maxWidthPixels, it will be truncated using either ellipsis (i.e. dots: "..."), | |||
| if useEllipsis is true, or if this is false, it will just drop any subsequent characters. | |||
| */ | |||
| void addCurtailedLineOfText (const Font& font, | |||
| const String& text, | |||
| float x, float y, | |||
| float maxWidthPixels, | |||
| bool useEllipsis); | |||
| /** Adds some multi-line text, breaking lines at word-boundaries if they are too wide. | |||
| This will add text to the arrangement, breaking it into new lines either where there | |||
| is a new-line or carriage-return character in the text, or where a line's width | |||
| exceeds the value set in maxLineWidth. | |||
| Each line that is added will be laid out using the flags set in horizontalLayout, so | |||
| the lines can be left- or right-justified, or centred horizontally in the space | |||
| between x and (x + maxLineWidth). | |||
| The y coordinate is the position of the baseline of the first line of text - subsequent | |||
| lines will be placed below it, separated by a distance of font.getHeight(). | |||
| */ | |||
| void addJustifiedText (const Font& font, | |||
| const String& text, | |||
| float x, float y, | |||
| float maxLineWidth, | |||
| Justification horizontalLayout); | |||
| /** Tries to fit some text within a given space. | |||
| This does its best to make the given text readable within the specified rectangle, | |||
| so it useful for labelling things. | |||
| If the text is too big, it'll be squashed horizontally or broken over multiple lines | |||
| if the maximumLinesToUse value allows this. If the text just won't fit into the space, | |||
| it'll cram as much as possible in there, and put some ellipsis at the end to show that | |||
| it's been truncated. | |||
| A Justification parameter lets you specify how the text is laid out within the rectangle, | |||
| both horizontally and vertically. | |||
| The minimumHorizontalScale parameter specifies how much the text can be squashed horizontally | |||
| to try to squeeze it into the space. If you don't want any horizontal scaling to occur, you | |||
| can set this value to 1.0f. Pass 0 if you want it to use the default value. | |||
| @see Graphics::drawFittedText | |||
| */ | |||
| void addFittedText (const Font& font, | |||
| const String& text, | |||
| float x, float y, float width, float height, | |||
| Justification layout, | |||
| int maximumLinesToUse, | |||
| float minimumHorizontalScale = 0.0f); | |||
| /** Appends another glyph arrangement to this one. */ | |||
| void addGlyphArrangement (const GlyphArrangement&); | |||
| /** Appends a custom glyph to the arrangement. */ | |||
| void addGlyph (const PositionedGlyph&); | |||
| //============================================================================== | |||
| /** Draws this glyph arrangement to a graphics context. | |||
| This uses cached bitmaps so is much faster than the draw (Graphics&, const AffineTransform&) | |||
| method, which renders the glyphs as filled vectors. | |||
| */ | |||
| void draw (const Graphics&) const; | |||
| /** Draws this glyph arrangement to a graphics context. | |||
| This renders the paths as filled vectors, so is far slower than the draw (Graphics&) | |||
| method for non-transformed arrangements. | |||
| */ | |||
| void draw (const Graphics&, const AffineTransform&) const; | |||
| /** Converts the set of glyphs into a path. | |||
| @param path the glyphs' outlines will be appended to this path | |||
| */ | |||
| void createPath (Path& path) const; | |||
| /** Looks for a glyph that contains the given coordinate. | |||
| @returns the index of the glyph, or -1 if none were found. | |||
| */ | |||
| int findGlyphIndexAt (float x, float y) const; | |||
| //============================================================================== | |||
| /** Finds the smallest rectangle that will enclose a subset of the glyphs. | |||
| @param startIndex the first glyph to test | |||
| @param numGlyphs the number of glyphs to include; if this is < 0, all glyphs after | |||
| startIndex will be included | |||
| @param includeWhitespace if true, the extent of any whitespace characters will also | |||
| be taken into account | |||
| */ | |||
| Rectangle<float> getBoundingBox (int startIndex, int numGlyphs, bool includeWhitespace) const; | |||
| /** Shifts a set of glyphs by a given amount. | |||
| @param startIndex the first glyph to transform | |||
| @param numGlyphs the number of glyphs to move; if this is < 0, all glyphs after | |||
| startIndex will be used | |||
| @param deltaX the amount to add to their x-positions | |||
| @param deltaY the amount to add to their y-positions | |||
| */ | |||
| void moveRangeOfGlyphs (int startIndex, int numGlyphs, | |||
| float deltaX, float deltaY); | |||
| /** Removes a set of glyphs from the arrangement. | |||
| @param startIndex the first glyph to remove | |||
| @param numGlyphs the number of glyphs to remove; if this is < 0, all glyphs after | |||
| startIndex will be deleted | |||
| */ | |||
| void removeRangeOfGlyphs (int startIndex, int numGlyphs); | |||
| /** Expands or compresses a set of glyphs horizontally. | |||
| @param startIndex the first glyph to transform | |||
| @param numGlyphs the number of glyphs to stretch; if this is < 0, all glyphs after | |||
| startIndex will be used | |||
| @param horizontalScaleFactor how much to scale their horizontal width by | |||
| */ | |||
| void stretchRangeOfGlyphs (int startIndex, int numGlyphs, | |||
| float horizontalScaleFactor); | |||
| /** Justifies a set of glyphs within a given space. | |||
| This moves the glyphs as a block so that the whole thing is located within the | |||
| given rectangle with the specified layout. | |||
| If the Justification::horizontallyJustified flag is specified, each line will | |||
| be stretched out to fill the specified width. | |||
| */ | |||
| void justifyGlyphs (int startIndex, int numGlyphs, | |||
| float x, float y, float width, float height, | |||
| Justification justification); | |||
| private: | |||
| //============================================================================== | |||
| Array<PositionedGlyph> glyphs; | |||
| int insertEllipsis (const Font&, float maxXPos, int startIndex, int endIndex); | |||
| int fitLineIntoSpace (int start, int numGlyphs, float x, float y, float w, float h, const Font&, | |||
| Justification, float minimumHorizontalScale); | |||
| void spreadOutLine (int start, int numGlyphs, float targetWidth); | |||
| void splitLines (const String&, Font, int start, float x, float y, float w, float h, int maxLines, | |||
| float lineWidth, Justification, float minimumHorizontalScale); | |||
| void addLinesWithLineBreaks (const String&, const Font&, float x, float y, float width, float height, Justification); | |||
| void drawGlyphUnderline (const Graphics&, const PositionedGlyph&, int, const AffineTransform&) const; | |||
| JUCE_LEAK_DETECTOR (GlyphArrangement) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,592 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| TextLayout::Glyph::Glyph (const int glyph, Point<float> anch, float w) noexcept | |||
| : glyphCode (glyph), anchor (anch), width (w) | |||
| { | |||
| } | |||
| TextLayout::Glyph::Glyph (const Glyph& other) noexcept | |||
| : glyphCode (other.glyphCode), anchor (other.anchor), width (other.width) | |||
| { | |||
| } | |||
| TextLayout::Glyph& TextLayout::Glyph::operator= (const Glyph& other) noexcept | |||
| { | |||
| glyphCode = other.glyphCode; | |||
| anchor = other.anchor; | |||
| width = other.width; | |||
| return *this; | |||
| } | |||
| TextLayout::Glyph::~Glyph() noexcept {} | |||
| //============================================================================== | |||
| TextLayout::Run::Run() noexcept | |||
| : colour (0xff000000) | |||
| { | |||
| } | |||
| TextLayout::Run::Run (Range<int> range, int numGlyphsToPreallocate) | |||
| : colour (0xff000000), stringRange (range) | |||
| { | |||
| glyphs.ensureStorageAllocated (numGlyphsToPreallocate); | |||
| } | |||
| TextLayout::Run::Run (const Run& other) | |||
| : font (other.font), | |||
| colour (other.colour), | |||
| glyphs (other.glyphs), | |||
| stringRange (other.stringRange) | |||
| { | |||
| } | |||
| TextLayout::Run::~Run() noexcept {} | |||
| //============================================================================== | |||
| TextLayout::Line::Line() noexcept | |||
| : ascent (0.0f), descent (0.0f), leading (0.0f) | |||
| { | |||
| } | |||
| TextLayout::Line::Line (Range<int> range, Point<float> o, float asc, float desc, | |||
| float lead, int numRunsToPreallocate) | |||
| : stringRange (range), lineOrigin (o), | |||
| ascent (asc), descent (desc), leading (lead) | |||
| { | |||
| runs.ensureStorageAllocated (numRunsToPreallocate); | |||
| } | |||
| TextLayout::Line::Line (const Line& other) | |||
| : stringRange (other.stringRange), lineOrigin (other.lineOrigin), | |||
| ascent (other.ascent), descent (other.descent), leading (other.leading) | |||
| { | |||
| runs.addCopiesOf (other.runs); | |||
| } | |||
| TextLayout::Line::~Line() noexcept | |||
| { | |||
| } | |||
| Range<float> TextLayout::Line::getLineBoundsX() const noexcept | |||
| { | |||
| Range<float> range; | |||
| bool isFirst = true; | |||
| for (auto* run : runs) | |||
| { | |||
| for (auto& glyph : run->glyphs) | |||
| { | |||
| Range<float> runRange (glyph.anchor.x, glyph.anchor.x + glyph.width); | |||
| if (isFirst) | |||
| { | |||
| isFirst = false; | |||
| range = runRange; | |||
| } | |||
| else | |||
| { | |||
| range = range.getUnionWith (runRange); | |||
| } | |||
| } | |||
| } | |||
| return range + lineOrigin.x; | |||
| } | |||
| Range<float> TextLayout::Line::getLineBoundsY() const noexcept | |||
| { | |||
| return { lineOrigin.y - ascent, | |||
| lineOrigin.y + descent }; | |||
| } | |||
| Rectangle<float> TextLayout::Line::getLineBounds() const noexcept | |||
| { | |||
| auto x = getLineBoundsX(); | |||
| auto y = getLineBoundsY(); | |||
| return { x.getStart(), y.getStart(), x.getLength(), y.getLength() }; | |||
| } | |||
| //============================================================================== | |||
| TextLayout::TextLayout() | |||
| : width (0), height (0), justification (Justification::topLeft) | |||
| { | |||
| } | |||
| TextLayout::TextLayout (const TextLayout& other) | |||
| : width (other.width), height (other.height), | |||
| justification (other.justification) | |||
| { | |||
| lines.addCopiesOf (other.lines); | |||
| } | |||
| TextLayout::TextLayout (TextLayout&& other) noexcept | |||
| : lines (static_cast<OwnedArray<Line>&&> (other.lines)), | |||
| width (other.width), height (other.height), | |||
| justification (other.justification) | |||
| { | |||
| } | |||
| TextLayout& TextLayout::operator= (TextLayout&& other) noexcept | |||
| { | |||
| lines = static_cast<OwnedArray<Line>&&> (other.lines); | |||
| width = other.width; | |||
| height = other.height; | |||
| justification = other.justification; | |||
| return *this; | |||
| } | |||
| TextLayout& TextLayout::operator= (const TextLayout& other) | |||
| { | |||
| width = other.width; | |||
| height = other.height; | |||
| justification = other.justification; | |||
| lines.clear(); | |||
| lines.addCopiesOf (other.lines); | |||
| return *this; | |||
| } | |||
| TextLayout::~TextLayout() | |||
| { | |||
| } | |||
| TextLayout::Line& TextLayout::getLine (const int index) const | |||
| { | |||
| return *lines.getUnchecked (index); | |||
| } | |||
| void TextLayout::ensureStorageAllocated (int numLinesNeeded) | |||
| { | |||
| lines.ensureStorageAllocated (numLinesNeeded); | |||
| } | |||
| void TextLayout::addLine (Line* line) | |||
| { | |||
| lines.add (line); | |||
| } | |||
| void TextLayout::draw (Graphics& g, Rectangle<float> area) const | |||
| { | |||
| auto origin = justification.appliedToRectangle (Rectangle<float> (width, getHeight()), area).getPosition(); | |||
| auto& context = g.getInternalContext(); | |||
| for (auto* line : lines) | |||
| { | |||
| auto lineOrigin = origin + line->lineOrigin; | |||
| for (auto* run : line->runs) | |||
| { | |||
| context.setFont (run->font); | |||
| context.setFill (run->colour); | |||
| for (auto& glyph : run->glyphs) | |||
| context.drawGlyph (glyph.glyphCode, AffineTransform::translation (lineOrigin.x + glyph.anchor.x, | |||
| lineOrigin.y + glyph.anchor.y)); | |||
| if (run->font.isUnderlined()) | |||
| { | |||
| Range<float> runExtent; | |||
| for (auto& glyph : run->glyphs) | |||
| { | |||
| Range<float> glyphRange (glyph.anchor.x, glyph.anchor.x + glyph.width); | |||
| runExtent = runExtent.isEmpty() ? glyphRange | |||
| : runExtent.getUnionWith (glyphRange); | |||
| } | |||
| auto lineThickness = run->font.getDescent() * 0.3f; | |||
| context.fillRect ({ runExtent.getStart() + lineOrigin.x, lineOrigin.y + lineThickness * 2.0f, | |||
| runExtent.getLength(), lineThickness }); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| void TextLayout::createLayout (const AttributedString& text, float maxWidth) | |||
| { | |||
| createLayout (text, maxWidth, 1.0e7f); | |||
| } | |||
| void TextLayout::createLayout (const AttributedString& text, float maxWidth, float maxHeight) | |||
| { | |||
| lines.clear(); | |||
| width = maxWidth; | |||
| height = maxHeight; | |||
| justification = text.getJustification(); | |||
| if (! createNativeLayout (text)) | |||
| createStandardLayout (text); | |||
| recalculateSize(); | |||
| } | |||
| void TextLayout::createLayoutWithBalancedLineLengths (const AttributedString& text, float maxWidth) | |||
| { | |||
| createLayoutWithBalancedLineLengths (text, maxWidth, 1.0e7f); | |||
| } | |||
| void TextLayout::createLayoutWithBalancedLineLengths (const AttributedString& text, float maxWidth, float maxHeight) | |||
| { | |||
| auto minimumWidth = maxWidth / 2.0f; | |||
| auto bestWidth = maxWidth; | |||
| float bestLineProportion = 0.0f; | |||
| while (maxWidth > minimumWidth) | |||
| { | |||
| createLayout (text, maxWidth, maxHeight); | |||
| if (getNumLines() < 2) | |||
| return; | |||
| auto line1 = lines.getUnchecked (lines.size() - 1)->getLineBoundsX().getLength(); | |||
| auto line2 = lines.getUnchecked (lines.size() - 2)->getLineBoundsX().getLength(); | |||
| auto shortest = jmin (line1, line2); | |||
| auto longest = jmax (line1, line2); | |||
| auto prop = shortest > 0 ? longest / shortest : 1.0f; | |||
| if (prop > 0.9f && prop < 1.1f) | |||
| return; | |||
| if (prop > bestLineProportion) | |||
| { | |||
| bestLineProportion = prop; | |||
| bestWidth = maxWidth; | |||
| } | |||
| maxWidth -= 10.0f; | |||
| } | |||
| if (bestWidth != maxWidth) | |||
| createLayout (text, bestWidth, maxHeight); | |||
| } | |||
| //============================================================================== | |||
| namespace TextLayoutHelpers | |||
| { | |||
| struct Token | |||
| { | |||
| Token (const String& t, const Font& f, Colour c, bool whitespace) | |||
| : text (t), font (f), colour (c), | |||
| area (font.getStringWidthFloat (t), f.getHeight()), | |||
| isWhitespace (whitespace), | |||
| isNewLine (t.containsChar ('\n') || t.containsChar ('\r')) | |||
| {} | |||
| const String text; | |||
| const Font font; | |||
| const Colour colour; | |||
| Rectangle<float> area; | |||
| int line; | |||
| float lineHeight; | |||
| const bool isWhitespace, isNewLine; | |||
| Token& operator= (const Token&) = delete; | |||
| }; | |||
| struct TokenList | |||
| { | |||
| TokenList() noexcept {} | |||
| void createLayout (const AttributedString& text, TextLayout& layout) | |||
| { | |||
| layout.ensureStorageAllocated (totalLines); | |||
| addTextRuns (text); | |||
| layoutRuns (layout.getWidth(), text.getLineSpacing(), text.getWordWrap()); | |||
| int charPosition = 0; | |||
| int lineStartPosition = 0; | |||
| int runStartPosition = 0; | |||
| ScopedPointer<TextLayout::Line> currentLine; | |||
| ScopedPointer<TextLayout::Run> currentRun; | |||
| bool needToSetLineOrigin = true; | |||
| for (int i = 0; i < tokens.size(); ++i) | |||
| { | |||
| auto& t = *tokens.getUnchecked (i); | |||
| Array<int> newGlyphs; | |||
| Array<float> xOffsets; | |||
| t.font.getGlyphPositions (getTrimmedEndIfNotAllWhitespace (t.text), newGlyphs, xOffsets); | |||
| if (currentRun == nullptr) currentRun = new TextLayout::Run(); | |||
| if (currentLine == nullptr) currentLine = new TextLayout::Line(); | |||
| if (newGlyphs.size() > 0) | |||
| { | |||
| currentRun->glyphs.ensureStorageAllocated (currentRun->glyphs.size() + newGlyphs.size()); | |||
| auto tokenOrigin = t.area.getPosition().translated (0, t.font.getAscent()); | |||
| if (needToSetLineOrigin) | |||
| { | |||
| needToSetLineOrigin = false; | |||
| currentLine->lineOrigin = tokenOrigin; | |||
| } | |||
| auto glyphOffset = tokenOrigin - currentLine->lineOrigin; | |||
| for (int j = 0; j < newGlyphs.size(); ++j) | |||
| { | |||
| auto x = xOffsets.getUnchecked (j); | |||
| currentRun->glyphs.add (TextLayout::Glyph (newGlyphs.getUnchecked(j), | |||
| glyphOffset.translated (x, 0), | |||
| xOffsets.getUnchecked (j + 1) - x)); | |||
| } | |||
| charPosition += newGlyphs.size(); | |||
| } | |||
| if (t.isWhitespace || t.isNewLine) | |||
| ++charPosition; | |||
| if (auto* nextToken = tokens[i + 1]) | |||
| { | |||
| if (t.font != nextToken->font || t.colour != nextToken->colour) | |||
| { | |||
| addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition); | |||
| runStartPosition = charPosition; | |||
| } | |||
| if (t.line != nextToken->line) | |||
| { | |||
| if (currentRun == nullptr) | |||
| currentRun = new TextLayout::Run(); | |||
| addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition); | |||
| currentLine->stringRange = Range<int> (lineStartPosition, charPosition); | |||
| if (! needToSetLineOrigin) | |||
| layout.addLine (currentLine.release()); | |||
| runStartPosition = charPosition; | |||
| lineStartPosition = charPosition; | |||
| needToSetLineOrigin = true; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition); | |||
| currentLine->stringRange = Range<int> (lineStartPosition, charPosition); | |||
| if (! needToSetLineOrigin) | |||
| layout.addLine (currentLine.release()); | |||
| needToSetLineOrigin = true; | |||
| } | |||
| } | |||
| if ((text.getJustification().getFlags() & (Justification::right | Justification::horizontallyCentred)) != 0) | |||
| { | |||
| auto totalW = layout.getWidth(); | |||
| bool isCentred = (text.getJustification().getFlags() & Justification::horizontallyCentred) != 0; | |||
| for (int i = 0; i < layout.getNumLines(); ++i) | |||
| { | |||
| auto dx = totalW - layout.getLine(i).getLineBoundsX().getLength(); | |||
| if (isCentred) | |||
| dx /= 2.0f; | |||
| layout.getLine(i).lineOrigin.x += dx; | |||
| } | |||
| } | |||
| } | |||
| private: | |||
| static void addRun (TextLayout::Line& glyphLine, TextLayout::Run* glyphRun, | |||
| const Token& t, const int start, const int end) | |||
| { | |||
| glyphRun->stringRange = { start, end }; | |||
| glyphRun->font = t.font; | |||
| glyphRun->colour = t.colour; | |||
| glyphLine.ascent = jmax (glyphLine.ascent, t.font.getAscent()); | |||
| glyphLine.descent = jmax (glyphLine.descent, t.font.getDescent()); | |||
| glyphLine.runs.add (glyphRun); | |||
| } | |||
| static int getCharacterType (const juce_wchar c) noexcept | |||
| { | |||
| if (c == '\r' || c == '\n') | |||
| return 0; | |||
| return CharacterFunctions::isWhitespace (c) ? 2 : 1; | |||
| } | |||
| void appendText (const String& stringText, const Font& font, Colour colour) | |||
| { | |||
| auto t = stringText.getCharPointer(); | |||
| String currentString; | |||
| int lastCharType = 0; | |||
| for (;;) | |||
| { | |||
| auto c = t.getAndAdvance(); | |||
| if (c == 0) | |||
| break; | |||
| auto charType = getCharacterType (c); | |||
| if (charType == 0 || charType != lastCharType) | |||
| { | |||
| if (currentString.isNotEmpty()) | |||
| tokens.add (new Token (currentString, font, colour, | |||
| lastCharType == 2 || lastCharType == 0)); | |||
| currentString = String::charToString (c); | |||
| if (c == '\r' && *t == '\n') | |||
| currentString += t.getAndAdvance(); | |||
| } | |||
| else | |||
| { | |||
| currentString += c; | |||
| } | |||
| lastCharType = charType; | |||
| } | |||
| if (currentString.isNotEmpty()) | |||
| tokens.add (new Token (currentString, font, colour, lastCharType == 2)); | |||
| } | |||
| void layoutRuns (const float maxWidth, const float extraLineSpacing, const AttributedString::WordWrap wordWrap) | |||
| { | |||
| float x = 0, y = 0, h = 0; | |||
| int i; | |||
| for (i = 0; i < tokens.size(); ++i) | |||
| { | |||
| auto& t = *tokens.getUnchecked(i); | |||
| t.area.setPosition (x, y); | |||
| t.line = totalLines; | |||
| x += t.area.getWidth(); | |||
| h = jmax (h, t.area.getHeight() + extraLineSpacing); | |||
| auto* nextTok = tokens[i + 1]; | |||
| if (nextTok == nullptr) | |||
| break; | |||
| const bool tokenTooLarge = (x + nextTok->area.getWidth() > maxWidth); | |||
| if (t.isNewLine || ((! nextTok->isWhitespace) && (tokenTooLarge && wordWrap != AttributedString::none))) | |||
| { | |||
| setLastLineHeight (i + 1, h); | |||
| x = 0; | |||
| y += h; | |||
| h = 0; | |||
| ++totalLines; | |||
| } | |||
| } | |||
| setLastLineHeight (jmin (i + 1, tokens.size()), h); | |||
| ++totalLines; | |||
| } | |||
| void setLastLineHeight (int i, const float height) noexcept | |||
| { | |||
| while (--i >= 0) | |||
| { | |||
| auto& tok = *tokens.getUnchecked (i); | |||
| if (tok.line == totalLines) | |||
| tok.lineHeight = height; | |||
| else | |||
| break; | |||
| } | |||
| } | |||
| void addTextRuns (const AttributedString& text) | |||
| { | |||
| auto numAttributes = text.getNumAttributes(); | |||
| tokens.ensureStorageAllocated (jmax (64, numAttributes)); | |||
| for (int i = 0; i < numAttributes; ++i) | |||
| { | |||
| auto& attr = text.getAttribute (i); | |||
| appendText (text.getText().substring (attr.range.getStart(), attr.range.getEnd()), | |||
| attr.font, attr.colour); | |||
| } | |||
| } | |||
| static String getTrimmedEndIfNotAllWhitespace (const String& s) | |||
| { | |||
| auto trimmed = s.trimEnd(); | |||
| if (trimmed.isEmpty() && s.isNotEmpty()) | |||
| trimmed = s.replaceCharacters ("\r\n\t", " "); | |||
| return trimmed; | |||
| } | |||
| OwnedArray<Token> tokens; | |||
| int totalLines = 0; | |||
| JUCE_DECLARE_NON_COPYABLE (TokenList) | |||
| }; | |||
| } | |||
| //============================================================================== | |||
| void TextLayout::createStandardLayout (const AttributedString& text) | |||
| { | |||
| TextLayoutHelpers::TokenList l; | |||
| l.createLayout (text, *this); | |||
| } | |||
| void TextLayout::recalculateSize() | |||
| { | |||
| if (! lines.isEmpty()) | |||
| { | |||
| auto bounds = lines.getFirst()->getLineBounds(); | |||
| for (auto* line : lines) | |||
| bounds = bounds.getUnion (line->getLineBounds()); | |||
| for (auto* line : lines) | |||
| line->lineOrigin.x -= bounds.getX(); | |||
| width = bounds.getWidth(); | |||
| height = bounds.getHeight(); | |||
| } | |||
| else | |||
| { | |||
| width = 0; | |||
| height = 0; | |||
| } | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,195 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| A Pre-formatted piece of text, which may contain multiple fonts and colours. | |||
| A TextLayout is created from an AttributedString, and once created can be | |||
| quickly drawn into a Graphics context. | |||
| @see AttributedString | |||
| */ | |||
| class JUCE_API TextLayout | |||
| { | |||
| public: | |||
| /** Creates an empty layout. | |||
| Having created a TextLayout, you can populate it using createLayout() or | |||
| createLayoutWithBalancedLineLengths(). | |||
| */ | |||
| TextLayout(); | |||
| TextLayout (const TextLayout&); | |||
| TextLayout& operator= (const TextLayout&); | |||
| TextLayout (TextLayout&&) noexcept; | |||
| TextLayout& operator= (TextLayout&&) noexcept; | |||
| /** Destructor. */ | |||
| ~TextLayout(); | |||
| //============================================================================== | |||
| /** Creates a layout from the given attributed string. | |||
| This will replace any data that is currently stored in the layout. | |||
| */ | |||
| void createLayout (const AttributedString&, float maxWidth); | |||
| /** Creates a layout from the given attributed string, given some size constraints. | |||
| This will replace any data that is currently stored in the layout. | |||
| */ | |||
| void createLayout (const AttributedString&, float maxWidth, float maxHeight); | |||
| /** Creates a layout, attempting to choose a width which results in lines | |||
| of a similar length. | |||
| This will be slower than the normal createLayout method, but produces a | |||
| tidier result. | |||
| */ | |||
| void createLayoutWithBalancedLineLengths (const AttributedString&, float maxWidth); | |||
| /** Creates a layout, attempting to choose a width which results in lines | |||
| of a similar length. | |||
| This will be slower than the normal createLayout method, but produces a | |||
| tidier result. | |||
| */ | |||
| void createLayoutWithBalancedLineLengths (const AttributedString&, float maxWidth, float maxHeight); | |||
| /** Draws the layout within the specified area. | |||
| The position of the text within the rectangle is controlled by the justification | |||
| flags set in the original AttributedString that was used to create this layout. | |||
| */ | |||
| void draw (Graphics&, Rectangle<float> area) const; | |||
| //============================================================================== | |||
| /** A positioned glyph. */ | |||
| class JUCE_API Glyph | |||
| { | |||
| public: | |||
| Glyph (int glyphCode, Point<float> anchor, float width) noexcept; | |||
| Glyph (const Glyph&) noexcept; | |||
| Glyph& operator= (const Glyph&) noexcept; | |||
| ~Glyph() noexcept; | |||
| /** The code number of this glyph. */ | |||
| int glyphCode; | |||
| /** The glyph's anchor point - this is relative to the line's origin. | |||
| @see TextLayout::Line::lineOrigin | |||
| */ | |||
| Point<float> anchor; | |||
| float width; | |||
| private: | |||
| JUCE_LEAK_DETECTOR (Glyph) | |||
| }; | |||
| //============================================================================== | |||
| /** A sequence of glyphs with a common font and colour. */ | |||
| class JUCE_API Run | |||
| { | |||
| public: | |||
| Run() noexcept; | |||
| Run (const Run&); | |||
| Run (Range<int> stringRange, int numGlyphsToPreallocate); | |||
| ~Run() noexcept; | |||
| Font font; /**< The run's font. */ | |||
| Colour colour; /**< The run's colour. */ | |||
| Array<Glyph> glyphs; /**< The glyphs in this run. */ | |||
| Range<int> stringRange; /**< The character range that this run represents in the | |||
| original string that was used to create it. */ | |||
| private: | |||
| Run& operator= (const Run&); | |||
| JUCE_LEAK_DETECTOR (Run) | |||
| }; | |||
| //============================================================================== | |||
| /** A line containing a sequence of glyph-runs. */ | |||
| class JUCE_API Line | |||
| { | |||
| public: | |||
| Line() noexcept; | |||
| Line (const Line&); | |||
| Line (Range<int> stringRange, Point<float> lineOrigin, | |||
| float ascent, float descent, float leading, int numRunsToPreallocate); | |||
| ~Line() noexcept; | |||
| /** Returns the X position range which contains all the glyphs in this line. */ | |||
| Range<float> getLineBoundsX() const noexcept; | |||
| /** Returns the Y position range which contains all the glyphs in this line. */ | |||
| Range<float> getLineBoundsY() const noexcept; | |||
| /** Returns the smallest rectangle which contains all the glyphs in this line. */ | |||
| Rectangle<float> getLineBounds() const noexcept; | |||
| OwnedArray<Run> runs; /**< The glyph-runs in this line. */ | |||
| Range<int> stringRange; /**< The character range that this line represents in the | |||
| original string that was used to create it. */ | |||
| Point<float> lineOrigin; /**< The line's baseline origin. */ | |||
| float ascent, descent, leading; | |||
| private: | |||
| Line& operator= (const Line&); | |||
| JUCE_LEAK_DETECTOR (Line) | |||
| }; | |||
| //============================================================================== | |||
| /** Returns the maximum width of the content. */ | |||
| float getWidth() const noexcept { return width; } | |||
| /** Returns the maximum height of the content. */ | |||
| float getHeight() const noexcept { return height; } | |||
| /** Returns the number of lines in the layout. */ | |||
| int getNumLines() const noexcept { return lines.size(); } | |||
| /** Returns one of the lines. */ | |||
| Line& getLine (int index) const; | |||
| /** Adds a line to the layout. The layout will take ownership of this line object | |||
| and will delete it when it is no longer needed. */ | |||
| void addLine (Line*); | |||
| /** Pre-allocates space for the specified number of lines. */ | |||
| void ensureStorageAllocated (int numLinesNeeded); | |||
| private: | |||
| OwnedArray<Line> lines; | |||
| float width, height; | |||
| Justification justification; | |||
| void createStandardLayout (const AttributedString&); | |||
| bool createNativeLayout (const AttributedString&); | |||
| void recalculateSize(); | |||
| JUCE_LEAK_DETECTOR (TextLayout) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,267 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| struct FontStyleHelpers | |||
| { | |||
| static const char* getStyleName (const bool bold, | |||
| const bool italic) noexcept | |||
| { | |||
| if (bold && italic) return "Bold Italic"; | |||
| if (bold) return "Bold"; | |||
| if (italic) return "Italic"; | |||
| return "Regular"; | |||
| } | |||
| static const char* getStyleName (const int styleFlags) noexcept | |||
| { | |||
| return getStyleName ((styleFlags & Font::bold) != 0, | |||
| (styleFlags & Font::italic) != 0); | |||
| } | |||
| static bool isBold (const String& style) noexcept | |||
| { | |||
| return style.containsWholeWordIgnoreCase ("Bold"); | |||
| } | |||
| static bool isItalic (const String& style) noexcept | |||
| { | |||
| return style.containsWholeWordIgnoreCase ("Italic") | |||
| || style.containsWholeWordIgnoreCase ("Oblique"); | |||
| } | |||
| static bool isPlaceholderFamilyName (const String& family) | |||
| { | |||
| return family == Font::getDefaultSansSerifFontName() | |||
| || family == Font::getDefaultSerifFontName() | |||
| || family == Font::getDefaultMonospacedFontName(); | |||
| } | |||
| struct ConcreteFamilyNames | |||
| { | |||
| ConcreteFamilyNames() | |||
| : sans (findName (Font::getDefaultSansSerifFontName())), | |||
| serif (findName (Font::getDefaultSerifFontName())), | |||
| mono (findName (Font::getDefaultMonospacedFontName())) | |||
| { | |||
| } | |||
| String lookUp (const String& placeholder) | |||
| { | |||
| if (placeholder == Font::getDefaultSansSerifFontName()) return sans; | |||
| if (placeholder == Font::getDefaultSerifFontName()) return serif; | |||
| if (placeholder == Font::getDefaultMonospacedFontName()) return mono; | |||
| return findName (placeholder); | |||
| } | |||
| private: | |||
| static String findName (const String& placeholder) | |||
| { | |||
| const Font f (placeholder, Font::getDefaultStyle(), 15.0f); | |||
| return Font::getDefaultTypefaceForFont (f)->getName(); | |||
| } | |||
| String sans, serif, mono; | |||
| }; | |||
| static String getConcreteFamilyNameFromPlaceholder (const String& placeholder) | |||
| { | |||
| static ConcreteFamilyNames names; | |||
| return names.lookUp (placeholder); | |||
| } | |||
| static String getConcreteFamilyName (const Font& font) | |||
| { | |||
| const String& family = font.getTypefaceName(); | |||
| return isPlaceholderFamilyName (family) ? getConcreteFamilyNameFromPlaceholder (family) | |||
| : family; | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| Typeface::Typeface (const String& faceName, const String& styleName) noexcept | |||
| : name (faceName), style (styleName) | |||
| { | |||
| } | |||
| Typeface::~Typeface() | |||
| { | |||
| } | |||
| Typeface::Ptr Typeface::getFallbackTypeface() | |||
| { | |||
| const Font fallbackFont (Font::getFallbackFontName(), Font::getFallbackFontStyle(), 10.0f); | |||
| return fallbackFont.getTypeface(); | |||
| } | |||
| EdgeTable* Typeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight) | |||
| { | |||
| Path path; | |||
| if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty()) | |||
| { | |||
| applyVerticalHintingTransform (fontHeight, path); | |||
| return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0), | |||
| path, transform); | |||
| } | |||
| return nullptr; | |||
| } | |||
| //============================================================================== | |||
| struct Typeface::HintingParams | |||
| { | |||
| HintingParams (Typeface& t) | |||
| : cachedSize (0), top (0), middle (0), bottom (0) | |||
| { | |||
| Font font (&t); | |||
| font = font.withHeight ((float) standardHeight); | |||
| top = getAverageY (font, "BDEFPRTZOQ", true); | |||
| middle = getAverageY (font, "acegmnopqrsuvwxy", true); | |||
| bottom = getAverageY (font, "BDELZOC", false); | |||
| } | |||
| void applyVerticalHintingTransform (float fontSize, Path& path) | |||
| { | |||
| if (cachedSize != fontSize) | |||
| { | |||
| cachedSize = fontSize; | |||
| cachedScale = Scaling (top, middle, bottom, fontSize); | |||
| } | |||
| if (bottom < top + 3.0f / fontSize) | |||
| return; | |||
| Path result; | |||
| for (Path::Iterator i (path); i.next();) | |||
| { | |||
| switch (i.elementType) | |||
| { | |||
| case Path::Iterator::startNewSubPath: result.startNewSubPath (i.x1, cachedScale.apply (i.y1)); break; | |||
| case Path::Iterator::lineTo: result.lineTo (i.x1, cachedScale.apply (i.y1)); break; | |||
| case Path::Iterator::quadraticTo: result.quadraticTo (i.x1, cachedScale.apply (i.y1), | |||
| i.x2, cachedScale.apply (i.y2)); break; | |||
| case Path::Iterator::cubicTo: result.cubicTo (i.x1, cachedScale.apply (i.y1), | |||
| i.x2, cachedScale.apply (i.y2), | |||
| i.x3, cachedScale.apply (i.y3)); break; | |||
| case Path::Iterator::closePath: result.closeSubPath(); break; | |||
| default: jassertfalse; break; | |||
| } | |||
| } | |||
| result.swapWithPath (path); | |||
| } | |||
| private: | |||
| struct Scaling | |||
| { | |||
| Scaling() noexcept : middle(), upperScale(), upperOffset(), lowerScale(), lowerOffset() {} | |||
| Scaling (float t, float m, float b, float fontSize) noexcept : middle (m) | |||
| { | |||
| const float newT = std::floor (fontSize * t + 0.5f) / fontSize; | |||
| const float newB = std::floor (fontSize * b + 0.5f) / fontSize; | |||
| const float newM = std::floor (fontSize * m + 0.3f) / fontSize; // this is slightly biased so that lower-case letters | |||
| // are more likely to become taller than shorter. | |||
| upperScale = jlimit (0.9f, 1.1f, (newM - newT) / (m - t)); | |||
| lowerScale = jlimit (0.9f, 1.1f, (newB - newM) / (b - m)); | |||
| upperOffset = newM - m * upperScale; | |||
| lowerOffset = newB - b * lowerScale; | |||
| } | |||
| float apply (float y) const noexcept | |||
| { | |||
| return y < middle ? (y * upperScale + upperOffset) | |||
| : (y * lowerScale + lowerOffset); | |||
| } | |||
| float middle, upperScale, upperOffset, lowerScale, lowerOffset; | |||
| }; | |||
| float cachedSize; | |||
| Scaling cachedScale; | |||
| static float getAverageY (const Font& font, const char* chars, bool getTop) | |||
| { | |||
| GlyphArrangement ga; | |||
| ga.addLineOfText (font, chars, 0, 0); | |||
| Array<float> y; | |||
| DefaultElementComparator<float> sorter; | |||
| for (int i = 0; i < ga.getNumGlyphs(); ++i) | |||
| { | |||
| Path p; | |||
| ga.getGlyph (i).createPath (p); | |||
| Rectangle<float> bounds (p.getBounds()); | |||
| if (! p.isEmpty()) | |||
| y.addSorted (sorter, getTop ? bounds.getY() : bounds.getBottom()); | |||
| } | |||
| float median = y[y.size() / 2]; | |||
| float total = 0; | |||
| int num = 0; | |||
| for (int i = 0; i < y.size(); ++i) | |||
| { | |||
| if (std::abs (median - y.getUnchecked(i)) < 0.05f * (float) standardHeight) | |||
| { | |||
| total += y.getUnchecked(i); | |||
| ++num; | |||
| } | |||
| } | |||
| return num < 4 ? 0.0f : total / (num * (float) standardHeight); | |||
| } | |||
| enum { standardHeight = 100 }; | |||
| float top, middle, bottom; | |||
| }; | |||
| void Typeface::applyVerticalHintingTransform (float fontSize, Path& path) | |||
| { | |||
| if (fontSize > 3.0f && fontSize < 25.0f) | |||
| { | |||
| ScopedLock sl (hintingLock); | |||
| if (hintingParams == nullptr) | |||
| hintingParams = new HintingParams (*this); | |||
| return hintingParams->applyVerticalHintingTransform (fontSize, path); | |||
| } | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,161 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| A typeface represents a size-independent font. | |||
| This base class is abstract, but calling createSystemTypefaceFor() will return | |||
| a platform-specific subclass that can be used. | |||
| The CustomTypeface subclass allow you to build your own typeface, and to | |||
| load and save it in the Juce typeface format. | |||
| Normally you should never need to deal directly with Typeface objects - the Font | |||
| class does everything you typically need for rendering text. | |||
| @see CustomTypeface, Font | |||
| */ | |||
| class JUCE_API Typeface : public ReferenceCountedObject | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** A handy typedef for a pointer to a typeface. */ | |||
| typedef ReferenceCountedObjectPtr<Typeface> Ptr; | |||
| //============================================================================== | |||
| /** Returns the font family of the typeface. | |||
| @see Font::getTypefaceName | |||
| */ | |||
| const String& getName() const noexcept { return name; } | |||
| //============================================================================== | |||
| /** Returns the font style of the typeface. | |||
| @see Font::getTypefaceStyle | |||
| */ | |||
| const String& getStyle() const noexcept { return style; } | |||
| //============================================================================== | |||
| /** Creates a new system typeface. */ | |||
| static Ptr createSystemTypefaceFor (const Font& font); | |||
| /** Attempts to create a font from some raw font file data (e.g. a TTF or OTF file image). | |||
| The system will take its own internal copy of the data, so you can free the block once | |||
| this method has returned. | |||
| */ | |||
| static Ptr createSystemTypefaceFor (const void* fontFileData, size_t fontFileDataSize); | |||
| //============================================================================== | |||
| /** Destructor. */ | |||
| virtual ~Typeface(); | |||
| /** Returns true if this typeface can be used to render the specified font. | |||
| When called, the font will already have been checked to make sure that its name and | |||
| style flags match the typeface. | |||
| */ | |||
| virtual bool isSuitableForFont (const Font&) const { return true; } | |||
| /** Returns the ascent of the font, as a proportion of its height. | |||
| The height is considered to always be normalised as 1.0, so this will be a | |||
| value less that 1.0, indicating the proportion of the font that lies above | |||
| its baseline. | |||
| */ | |||
| virtual float getAscent() const = 0; | |||
| /** Returns the descent of the font, as a proportion of its height. | |||
| The height is considered to always be normalised as 1.0, so this will be a | |||
| value less that 1.0, indicating the proportion of the font that lies below | |||
| its baseline. | |||
| */ | |||
| virtual float getDescent() const = 0; | |||
| /** Returns the value by which you should multiply a juce font-height value to | |||
| convert it to the equivalent point-size. | |||
| */ | |||
| virtual float getHeightToPointsFactor() const = 0; | |||
| /** Measures the width of a line of text. | |||
| The distance returned is based on the font having an normalised height of 1.0. | |||
| You should never need to call this directly! Use Font::getStringWidth() instead! | |||
| */ | |||
| virtual float getStringWidth (const String& text) = 0; | |||
| /** Converts a line of text into its glyph numbers and their positions. | |||
| The distances returned are based on the font having an normalised height of 1.0. | |||
| You should never need to call this directly! Use Font::getGlyphPositions() instead! | |||
| */ | |||
| virtual void getGlyphPositions (const String& text, Array <int>& glyphs, Array<float>& xOffsets) = 0; | |||
| /** Returns the outline for a glyph. | |||
| The path returned will be normalised to a font height of 1.0. | |||
| */ | |||
| virtual bool getOutlineForGlyph (int glyphNumber, Path& path) = 0; | |||
| /** Returns a new EdgeTable that contains the path for the givem glyph, with the specified transform applied. */ | |||
| virtual EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight); | |||
| /** Returns true if the typeface uses hinting. */ | |||
| virtual bool isHinted() const { return false; } | |||
| //============================================================================== | |||
| /** Changes the number of fonts that are cached in memory. */ | |||
| static void setTypefaceCacheSize (int numFontsToCache); | |||
| /** Clears any fonts that are currently cached in memory. */ | |||
| static void clearTypefaceCache(); | |||
| /** On some platforms, this allows a specific path to be scanned. | |||
| Currently only available when using FreeType. | |||
| */ | |||
| static void scanFolderForFonts (const File& folder); | |||
| /** Makes an attempt at performing a good overall distortion that will scale a font of | |||
| the given size to align vertically with the pixel grid. The path should be an unscaled | |||
| (i.e. normalised to height of 1.0) path for a glyph. | |||
| */ | |||
| void applyVerticalHintingTransform (float fontHeight, Path& path); | |||
| protected: | |||
| //============================================================================== | |||
| String name, style; | |||
| Typeface (const String& name, const String& style) noexcept; | |||
| static Ptr getFallbackTypeface(); | |||
| private: | |||
| struct HintingParams; | |||
| friend struct ContainerDeletePolicy<HintingParams>; | |||
| ScopedPointer<HintingParams> hintingParams; | |||
| CriticalSection hintingLock; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Typeface) | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,269 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| AffineTransform::AffineTransform() noexcept | |||
| : mat00 (1.0f), mat01 (0), mat02 (0), | |||
| mat10 (0), mat11 (1.0f), mat12 (0) | |||
| { | |||
| } | |||
| AffineTransform::AffineTransform (const AffineTransform& other) noexcept | |||
| : mat00 (other.mat00), mat01 (other.mat01), mat02 (other.mat02), | |||
| mat10 (other.mat10), mat11 (other.mat11), mat12 (other.mat12) | |||
| { | |||
| } | |||
| AffineTransform::AffineTransform (const float m00, const float m01, const float m02, | |||
| const float m10, const float m11, const float m12) noexcept | |||
| : mat00 (m00), mat01 (m01), mat02 (m02), | |||
| mat10 (m10), mat11 (m11), mat12 (m12) | |||
| { | |||
| } | |||
| AffineTransform& AffineTransform::operator= (const AffineTransform& other) noexcept | |||
| { | |||
| mat00 = other.mat00; | |||
| mat01 = other.mat01; | |||
| mat02 = other.mat02; | |||
| mat10 = other.mat10; | |||
| mat11 = other.mat11; | |||
| mat12 = other.mat12; | |||
| return *this; | |||
| } | |||
| bool AffineTransform::operator== (const AffineTransform& other) const noexcept | |||
| { | |||
| return mat00 == other.mat00 | |||
| && mat01 == other.mat01 | |||
| && mat02 == other.mat02 | |||
| && mat10 == other.mat10 | |||
| && mat11 == other.mat11 | |||
| && mat12 == other.mat12; | |||
| } | |||
| bool AffineTransform::operator!= (const AffineTransform& other) const noexcept | |||
| { | |||
| return ! operator== (other); | |||
| } | |||
| //============================================================================== | |||
| bool AffineTransform::isIdentity() const noexcept | |||
| { | |||
| return (mat01 == 0.0f) | |||
| && (mat02 == 0.0f) | |||
| && (mat10 == 0.0f) | |||
| && (mat12 == 0.0f) | |||
| && (mat00 == 1.0f) | |||
| && (mat11 == 1.0f); | |||
| } | |||
| #if JUCE_ALLOW_STATIC_NULL_VARIABLES | |||
| const AffineTransform AffineTransform::identity; | |||
| #endif | |||
| //============================================================================== | |||
| AffineTransform AffineTransform::followedBy (const AffineTransform& other) const noexcept | |||
| { | |||
| return AffineTransform (other.mat00 * mat00 + other.mat01 * mat10, | |||
| other.mat00 * mat01 + other.mat01 * mat11, | |||
| other.mat00 * mat02 + other.mat01 * mat12 + other.mat02, | |||
| other.mat10 * mat00 + other.mat11 * mat10, | |||
| other.mat10 * mat01 + other.mat11 * mat11, | |||
| other.mat10 * mat02 + other.mat11 * mat12 + other.mat12); | |||
| } | |||
| AffineTransform AffineTransform::translated (const float dx, const float dy) const noexcept | |||
| { | |||
| return AffineTransform (mat00, mat01, mat02 + dx, | |||
| mat10, mat11, mat12 + dy); | |||
| } | |||
| AffineTransform AffineTransform::translation (const float dx, const float dy) noexcept | |||
| { | |||
| return AffineTransform (1.0f, 0, dx, | |||
| 0, 1.0f, dy); | |||
| } | |||
| AffineTransform AffineTransform::withAbsoluteTranslation (const float tx, const float ty) const noexcept | |||
| { | |||
| return AffineTransform (mat00, mat01, tx, | |||
| mat10, mat11, ty); | |||
| } | |||
| AffineTransform AffineTransform::rotated (const float rad) const noexcept | |||
| { | |||
| const float cosRad = std::cos (rad); | |||
| const float sinRad = std::sin (rad); | |||
| return AffineTransform (cosRad * mat00 + -sinRad * mat10, | |||
| cosRad * mat01 + -sinRad * mat11, | |||
| cosRad * mat02 + -sinRad * mat12, | |||
| sinRad * mat00 + cosRad * mat10, | |||
| sinRad * mat01 + cosRad * mat11, | |||
| sinRad * mat02 + cosRad * mat12); | |||
| } | |||
| AffineTransform AffineTransform::rotation (const float rad) noexcept | |||
| { | |||
| const float cosRad = std::cos (rad); | |||
| const float sinRad = std::sin (rad); | |||
| return AffineTransform (cosRad, -sinRad, 0, | |||
| sinRad, cosRad, 0); | |||
| } | |||
| AffineTransform AffineTransform::rotation (const float rad, const float pivotX, const float pivotY) noexcept | |||
| { | |||
| const float cosRad = std::cos (rad); | |||
| const float sinRad = std::sin (rad); | |||
| return AffineTransform (cosRad, -sinRad, -cosRad * pivotX + sinRad * pivotY + pivotX, | |||
| sinRad, cosRad, -sinRad * pivotX + -cosRad * pivotY + pivotY); | |||
| } | |||
| AffineTransform AffineTransform::rotated (const float angle, const float pivotX, const float pivotY) const noexcept | |||
| { | |||
| return followedBy (rotation (angle, pivotX, pivotY)); | |||
| } | |||
| AffineTransform AffineTransform::scaled (const float factorX, const float factorY) const noexcept | |||
| { | |||
| return AffineTransform (factorX * mat00, factorX * mat01, factorX * mat02, | |||
| factorY * mat10, factorY * mat11, factorY * mat12); | |||
| } | |||
| AffineTransform AffineTransform::scaled (const float factor) const noexcept | |||
| { | |||
| return AffineTransform (factor * mat00, factor * mat01, factor * mat02, | |||
| factor * mat10, factor * mat11, factor * mat12); | |||
| } | |||
| AffineTransform AffineTransform::scale (const float factorX, const float factorY) noexcept | |||
| { | |||
| return AffineTransform (factorX, 0, 0, 0, factorY, 0); | |||
| } | |||
| AffineTransform AffineTransform::scale (const float factor) noexcept | |||
| { | |||
| return AffineTransform (factor, 0, 0, 0, factor, 0); | |||
| } | |||
| AffineTransform AffineTransform::scaled (const float factorX, const float factorY, | |||
| const float pivotX, const float pivotY) const noexcept | |||
| { | |||
| return AffineTransform (factorX * mat00, factorX * mat01, factorX * mat02 + pivotX * (1.0f - factorX), | |||
| factorY * mat10, factorY * mat11, factorY * mat12 + pivotY * (1.0f - factorY)); | |||
| } | |||
| AffineTransform AffineTransform::scale (const float factorX, const float factorY, | |||
| const float pivotX, const float pivotY) noexcept | |||
| { | |||
| return AffineTransform (factorX, 0, pivotX * (1.0f - factorX), | |||
| 0, factorY, pivotY * (1.0f - factorY)); | |||
| } | |||
| AffineTransform AffineTransform::shear (float shearX, float shearY) noexcept | |||
| { | |||
| return AffineTransform (1.0f, shearX, 0, | |||
| shearY, 1.0f, 0); | |||
| } | |||
| AffineTransform AffineTransform::sheared (const float shearX, const float shearY) const noexcept | |||
| { | |||
| return AffineTransform (mat00 + shearX * mat10, | |||
| mat01 + shearX * mat11, | |||
| mat02 + shearX * mat12, | |||
| mat10 + shearY * mat00, | |||
| mat11 + shearY * mat01, | |||
| mat12 + shearY * mat02); | |||
| } | |||
| AffineTransform AffineTransform::verticalFlip (const float height) noexcept | |||
| { | |||
| return AffineTransform (1.0f, 0, 0, 0, -1.0f, height); | |||
| } | |||
| AffineTransform AffineTransform::inverted() const noexcept | |||
| { | |||
| double determinant = (mat00 * mat11 - mat10 * mat01); | |||
| if (determinant != 0.0) | |||
| { | |||
| determinant = 1.0 / determinant; | |||
| const float dst00 = (float) ( mat11 * determinant); | |||
| const float dst10 = (float) (-mat10 * determinant); | |||
| const float dst01 = (float) (-mat01 * determinant); | |||
| const float dst11 = (float) ( mat00 * determinant); | |||
| return AffineTransform (dst00, dst01, -mat02 * dst00 - mat12 * dst01, | |||
| dst10, dst11, -mat02 * dst10 - mat12 * dst11); | |||
| } | |||
| // singularity.. | |||
| return *this; | |||
| } | |||
| bool AffineTransform::isSingularity() const noexcept | |||
| { | |||
| return (mat00 * mat11 - mat10 * mat01) == 0.0f; | |||
| } | |||
| AffineTransform AffineTransform::fromTargetPoints (const float x00, const float y00, | |||
| const float x10, const float y10, | |||
| const float x01, const float y01) noexcept | |||
| { | |||
| return AffineTransform (x10 - x00, x01 - x00, x00, | |||
| y10 - y00, y01 - y00, y00); | |||
| } | |||
| AffineTransform AffineTransform::fromTargetPoints (const float sx1, const float sy1, const float tx1, const float ty1, | |||
| const float sx2, const float sy2, const float tx2, const float ty2, | |||
| const float sx3, const float sy3, const float tx3, const float ty3) noexcept | |||
| { | |||
| return fromTargetPoints (sx1, sy1, sx2, sy2, sx3, sy3) | |||
| .inverted() | |||
| .followedBy (fromTargetPoints (tx1, ty1, tx2, ty2, tx3, ty3)); | |||
| } | |||
| bool AffineTransform::isOnlyTranslation() const noexcept | |||
| { | |||
| return (mat01 == 0.0f) | |||
| && (mat10 == 0.0f) | |||
| && (mat00 == 1.0f) | |||
| && (mat11 == 1.0f); | |||
| } | |||
| float AffineTransform::getScaleFactor() const noexcept | |||
| { | |||
| return (std::abs (mat00) + std::abs (mat11)) / 2.0f; | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,279 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Represents a 2D affine-transformation matrix. | |||
| An affine transformation is a transformation such as a rotation, scale, shear, | |||
| resize or translation. | |||
| These are used for various 2D transformation tasks, e.g. with Path objects. | |||
| @see Path, Point, Line | |||
| */ | |||
| class JUCE_API AffineTransform | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates an identity transform. */ | |||
| AffineTransform() noexcept; | |||
| /** Creates a copy of another transform. */ | |||
| AffineTransform (const AffineTransform& other) noexcept; | |||
| /** Creates a transform from a set of raw matrix values. | |||
| The resulting matrix is: | |||
| (mat00 mat01 mat02) | |||
| (mat10 mat11 mat12) | |||
| ( 0 0 1 ) | |||
| */ | |||
| AffineTransform (float mat00, float mat01, float mat02, | |||
| float mat10, float mat11, float mat12) noexcept; | |||
| /** Copies from another AffineTransform object */ | |||
| AffineTransform& operator= (const AffineTransform& other) noexcept; | |||
| /** Compares two transforms. */ | |||
| bool operator== (const AffineTransform& other) const noexcept; | |||
| /** Compares two transforms. */ | |||
| bool operator!= (const AffineTransform& other) const noexcept; | |||
| #if JUCE_ALLOW_STATIC_NULL_VARIABLES | |||
| /** A ready-to-use identity transform. | |||
| Note that you should always avoid using a static variable like this, and | |||
| prefer AffineTransform() or {} if you need a default-constructed instance. | |||
| */ | |||
| static const AffineTransform identity; | |||
| #endif | |||
| //============================================================================== | |||
| /** Transforms a 2D coordinate using this matrix. */ | |||
| template <typename ValueType> | |||
| void transformPoint (ValueType& x, ValueType& y) const noexcept | |||
| { | |||
| const ValueType oldX = x; | |||
| x = static_cast<ValueType> (mat00 * oldX + mat01 * y + mat02); | |||
| y = static_cast<ValueType> (mat10 * oldX + mat11 * y + mat12); | |||
| } | |||
| /** Transforms two 2D coordinates using this matrix. | |||
| This is just a shortcut for calling transformPoint() on each of these pairs of | |||
| coordinates in turn. (And putting all the calculations into one function hopefully | |||
| also gives the compiler a bit more scope for pipelining it). | |||
| */ | |||
| template <typename ValueType> | |||
| void transformPoints (ValueType& x1, ValueType& y1, | |||
| ValueType& x2, ValueType& y2) const noexcept | |||
| { | |||
| const ValueType oldX1 = x1, oldX2 = x2; | |||
| x1 = static_cast<ValueType> (mat00 * oldX1 + mat01 * y1 + mat02); | |||
| y1 = static_cast<ValueType> (mat10 * oldX1 + mat11 * y1 + mat12); | |||
| x2 = static_cast<ValueType> (mat00 * oldX2 + mat01 * y2 + mat02); | |||
| y2 = static_cast<ValueType> (mat10 * oldX2 + mat11 * y2 + mat12); | |||
| } | |||
| /** Transforms three 2D coordinates using this matrix. | |||
| This is just a shortcut for calling transformPoint() on each of these pairs of | |||
| coordinates in turn. (And putting all the calculations into one function hopefully | |||
| also gives the compiler a bit more scope for pipelining it). | |||
| */ | |||
| template <typename ValueType> | |||
| void transformPoints (ValueType& x1, ValueType& y1, | |||
| ValueType& x2, ValueType& y2, | |||
| ValueType& x3, ValueType& y3) const noexcept | |||
| { | |||
| const ValueType oldX1 = x1, oldX2 = x2, oldX3 = x3; | |||
| x1 = static_cast<ValueType> (mat00 * oldX1 + mat01 * y1 + mat02); | |||
| y1 = static_cast<ValueType> (mat10 * oldX1 + mat11 * y1 + mat12); | |||
| x2 = static_cast<ValueType> (mat00 * oldX2 + mat01 * y2 + mat02); | |||
| y2 = static_cast<ValueType> (mat10 * oldX2 + mat11 * y2 + mat12); | |||
| x3 = static_cast<ValueType> (mat00 * oldX3 + mat01 * y3 + mat02); | |||
| y3 = static_cast<ValueType> (mat10 * oldX3 + mat11 * y3 + mat12); | |||
| } | |||
| //============================================================================== | |||
| /** Returns a new transform which is the same as this one followed by a translation. */ | |||
| AffineTransform translated (float deltaX, | |||
| float deltaY) const noexcept; | |||
| /** Returns a new transform which is the same as this one followed by a translation. */ | |||
| template <typename PointType> | |||
| AffineTransform translated (PointType delta) const noexcept | |||
| { | |||
| return translated ((float) delta.x, (float) delta.y); | |||
| } | |||
| /** Returns a new transform which is a translation. */ | |||
| static AffineTransform translation (float deltaX, | |||
| float deltaY) noexcept; | |||
| /** Returns a new transform which is a translation. */ | |||
| template <typename PointType> | |||
| static AffineTransform translation (PointType delta) noexcept | |||
| { | |||
| return translation ((float) delta.x, (float) delta.y); | |||
| } | |||
| /** Returns a copy of this transform with the specified translation matrix values. */ | |||
| AffineTransform withAbsoluteTranslation (float translationX, | |||
| float translationY) const noexcept; | |||
| /** Returns a transform which is the same as this one followed by a rotation. | |||
| The rotation is specified by a number of radians to rotate clockwise, centred around | |||
| the origin (0, 0). | |||
| */ | |||
| AffineTransform rotated (float angleInRadians) const noexcept; | |||
| /** Returns a transform which is the same as this one followed by a rotation about a given point. | |||
| The rotation is specified by a number of radians to rotate clockwise, centred around | |||
| the coordinates passed in. | |||
| */ | |||
| AffineTransform rotated (float angleInRadians, | |||
| float pivotX, | |||
| float pivotY) const noexcept; | |||
| /** Returns a new transform which is a rotation about (0, 0). */ | |||
| static AffineTransform rotation (float angleInRadians) noexcept; | |||
| /** Returns a new transform which is a rotation about a given point. */ | |||
| static AffineTransform rotation (float angleInRadians, | |||
| float pivotX, | |||
| float pivotY) noexcept; | |||
| /** Returns a transform which is the same as this one followed by a re-scaling. | |||
| The scaling is centred around the origin (0, 0). | |||
| */ | |||
| AffineTransform scaled (float factorX, | |||
| float factorY) const noexcept; | |||
| /** Returns a transform which is the same as this one followed by a re-scaling. | |||
| The scaling is centred around the origin (0, 0). | |||
| */ | |||
| AffineTransform scaled (float factor) const noexcept; | |||
| /** Returns a transform which is the same as this one followed by a re-scaling. | |||
| The scaling is centred around the origin provided. | |||
| */ | |||
| AffineTransform scaled (float factorX, float factorY, | |||
| float pivotX, float pivotY) const noexcept; | |||
| /** Returns a new transform which is a re-scale about the origin. */ | |||
| static AffineTransform scale (float factorX, | |||
| float factorY) noexcept; | |||
| /** Returns a new transform which is a re-scale about the origin. */ | |||
| static AffineTransform scale (float factor) noexcept; | |||
| /** Returns a new transform which is a re-scale centred around the point provided. */ | |||
| static AffineTransform scale (float factorX, float factorY, | |||
| float pivotX, float pivotY) noexcept; | |||
| /** Returns a transform which is the same as this one followed by a shear. | |||
| The shear is centred around the origin (0, 0). | |||
| */ | |||
| AffineTransform sheared (float shearX, float shearY) const noexcept; | |||
| /** Returns a shear transform, centred around the origin (0, 0). */ | |||
| static AffineTransform shear (float shearX, float shearY) noexcept; | |||
| /** Returns a transform that will flip coordinates vertically within a window of the given height. | |||
| This is handy for converting between upside-down coordinate systems such as OpenGL or CoreGraphics. | |||
| */ | |||
| static AffineTransform verticalFlip (float height) noexcept; | |||
| /** Returns a matrix which is the inverse operation of this one. | |||
| Some matrices don't have an inverse - in this case, the method will just return | |||
| an identity transform. | |||
| */ | |||
| AffineTransform inverted() const noexcept; | |||
| /** Returns the transform that will map three known points onto three coordinates | |||
| that are supplied. | |||
| This returns the transform that will transform (0, 0) into (x00, y00), | |||
| (1, 0) to (x10, y10), and (0, 1) to (x01, y01). | |||
| */ | |||
| static AffineTransform fromTargetPoints (float x00, float y00, | |||
| float x10, float y10, | |||
| float x01, float y01) noexcept; | |||
| /** Returns the transform that will map three specified points onto three target points. */ | |||
| static AffineTransform fromTargetPoints (float sourceX1, float sourceY1, float targetX1, float targetY1, | |||
| float sourceX2, float sourceY2, float targetX2, float targetY2, | |||
| float sourceX3, float sourceY3, float targetX3, float targetY3) noexcept; | |||
| //============================================================================== | |||
| /** Returns the result of concatenating another transformation after this one. */ | |||
| AffineTransform followedBy (const AffineTransform& other) const noexcept; | |||
| /** Returns true if this transform has no effect on points. */ | |||
| bool isIdentity() const noexcept; | |||
| /** Returns true if this transform maps to a singularity - i.e. if it has no inverse. */ | |||
| bool isSingularity() const noexcept; | |||
| /** Returns true if the transform only translates, and doesn't scale or rotate the | |||
| points. */ | |||
| bool isOnlyTranslation() const noexcept; | |||
| /** If this transform is only a translation, this returns the X offset. | |||
| @see isOnlyTranslation | |||
| */ | |||
| float getTranslationX() const noexcept { return mat02; } | |||
| /** If this transform is only a translation, this returns the X offset. | |||
| @see isOnlyTranslation | |||
| */ | |||
| float getTranslationY() const noexcept { return mat12; } | |||
| /** Returns the approximate scale factor by which lengths will be transformed. | |||
| Obviously a length may be scaled by entirely different amounts depending on its | |||
| direction, so this is only appropriate as a rough guide. | |||
| */ | |||
| float getScaleFactor() const noexcept; | |||
| //============================================================================== | |||
| /* The transform matrix is: | |||
| (mat00 mat01 mat02) | |||
| (mat10 mat11 mat12) | |||
| ( 0 0 1 ) | |||
| */ | |||
| float mat00, mat01, mat02; | |||
| float mat10, mat11, mat12; | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,153 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Specifies a set of gaps to be left around the sides of a rectangle. | |||
| This is basically the size of the spaces at the top, bottom, left and right of | |||
| a rectangle. It's used by various component classes to specify borders. | |||
| @see Rectangle | |||
| */ | |||
| template <typename ValueType> | |||
| class BorderSize | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a null border. | |||
| All sizes are left as 0. | |||
| */ | |||
| BorderSize() noexcept | |||
| : top(), left(), bottom(), right() | |||
| { | |||
| } | |||
| /** Creates a copy of another border. */ | |||
| BorderSize (const BorderSize& other) noexcept | |||
| : top (other.top), left (other.left), bottom (other.bottom), right (other.right) | |||
| { | |||
| } | |||
| /** Creates a border with the given gaps. */ | |||
| BorderSize (ValueType topGap, ValueType leftGap, ValueType bottomGap, ValueType rightGap) noexcept | |||
| : top (topGap), left (leftGap), bottom (bottomGap), right (rightGap) | |||
| { | |||
| } | |||
| /** Creates a border with the given gap on all sides. */ | |||
| explicit BorderSize (ValueType allGaps) noexcept | |||
| : top (allGaps), left (allGaps), bottom (allGaps), right (allGaps) | |||
| { | |||
| } | |||
| //============================================================================== | |||
| /** Returns the gap that should be left at the top of the region. */ | |||
| ValueType getTop() const noexcept { return top; } | |||
| /** Returns the gap that should be left at the top of the region. */ | |||
| ValueType getLeft() const noexcept { return left; } | |||
| /** Returns the gap that should be left at the top of the region. */ | |||
| ValueType getBottom() const noexcept { return bottom; } | |||
| /** Returns the gap that should be left at the top of the region. */ | |||
| ValueType getRight() const noexcept { return right; } | |||
| /** Returns the sum of the top and bottom gaps. */ | |||
| ValueType getTopAndBottom() const noexcept { return top + bottom; } | |||
| /** Returns the sum of the left and right gaps. */ | |||
| ValueType getLeftAndRight() const noexcept { return left + right; } | |||
| /** Returns true if this border has no thickness along any edge. */ | |||
| bool isEmpty() const noexcept { return left + right + top + bottom == ValueType(); } | |||
| //============================================================================== | |||
| /** Changes the top gap. */ | |||
| void setTop (ValueType newTopGap) noexcept { top = newTopGap; } | |||
| /** Changes the left gap. */ | |||
| void setLeft (ValueType newLeftGap) noexcept { left = newLeftGap; } | |||
| /** Changes the bottom gap. */ | |||
| void setBottom (ValueType newBottomGap) noexcept { bottom = newBottomGap; } | |||
| /** Changes the right gap. */ | |||
| void setRight (ValueType newRightGap) noexcept { right = newRightGap; } | |||
| //============================================================================== | |||
| /** Returns a rectangle with these borders removed from it. */ | |||
| Rectangle<ValueType> subtractedFrom (const Rectangle<ValueType>& original) const noexcept | |||
| { | |||
| return Rectangle<ValueType> (original.getX() + left, | |||
| original.getY() + top, | |||
| original.getWidth() - (left + right), | |||
| original.getHeight() - (top + bottom)); | |||
| } | |||
| /** Removes this border from a given rectangle. */ | |||
| void subtractFrom (Rectangle<ValueType>& rectangle) const noexcept | |||
| { | |||
| rectangle = subtractedFrom (rectangle); | |||
| } | |||
| /** Returns a rectangle with these borders added around it. */ | |||
| Rectangle<ValueType> addedTo (const Rectangle<ValueType>& original) const noexcept | |||
| { | |||
| return Rectangle<ValueType> (original.getX() - left, | |||
| original.getY() - top, | |||
| original.getWidth() + (left + right), | |||
| original.getHeight() + (top + bottom)); | |||
| } | |||
| /** Adds this border around a given rectangle. */ | |||
| void addTo (Rectangle<ValueType>& rectangle) const noexcept | |||
| { | |||
| rectangle = addedTo (rectangle); | |||
| } | |||
| //============================================================================== | |||
| bool operator== (const BorderSize& other) const noexcept | |||
| { | |||
| return top == other.top && left == other.left && bottom == other.bottom && right == other.right; | |||
| } | |||
| bool operator!= (const BorderSize& other) const noexcept | |||
| { | |||
| return ! operator== (other); | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| ValueType top, left, bottom, right; | |||
| }; | |||
| } // namespace juce | |||
| @@ -0,0 +1,838 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| const int juce_edgeTableDefaultEdgesPerLine = 32; | |||
| //============================================================================== | |||
| EdgeTable::EdgeTable (const Rectangle<int>& area, | |||
| const Path& path, const AffineTransform& transform) | |||
| : bounds (area), | |||
| maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), | |||
| lineStrideElements (juce_edgeTableDefaultEdgesPerLine * 2 + 1), | |||
| needToCheckEmptiness (true) | |||
| { | |||
| allocate(); | |||
| int* t = table; | |||
| for (int i = bounds.getHeight(); --i >= 0;) | |||
| { | |||
| *t = 0; | |||
| t += lineStrideElements; | |||
| } | |||
| const int leftLimit = bounds.getX() << 8; | |||
| const int topLimit = bounds.getY() << 8; | |||
| const int rightLimit = bounds.getRight() << 8; | |||
| const int heightLimit = bounds.getHeight() << 8; | |||
| PathFlatteningIterator iter (path, transform); | |||
| while (iter.next()) | |||
| { | |||
| int y1 = roundToInt (iter.y1 * 256.0f); | |||
| int y2 = roundToInt (iter.y2 * 256.0f); | |||
| if (y1 != y2) | |||
| { | |||
| y1 -= topLimit; | |||
| y2 -= topLimit; | |||
| const int startY = y1; | |||
| int direction = -1; | |||
| if (y1 > y2) | |||
| { | |||
| std::swap (y1, y2); | |||
| direction = 1; | |||
| } | |||
| if (y1 < 0) | |||
| y1 = 0; | |||
| if (y2 > heightLimit) | |||
| y2 = heightLimit; | |||
| if (y1 < y2) | |||
| { | |||
| const double startX = 256.0f * iter.x1; | |||
| const double multiplier = (iter.x2 - iter.x1) / (iter.y2 - iter.y1); | |||
| const int stepSize = jlimit (1, 256, 256 / (1 + (int) std::abs (multiplier))); | |||
| do | |||
| { | |||
| const int step = jmin (stepSize, y2 - y1, 256 - (y1 & 255)); | |||
| int x = roundToInt (startX + multiplier * ((y1 + (step >> 1)) - startY)); | |||
| if (x < leftLimit) | |||
| x = leftLimit; | |||
| else if (x >= rightLimit) | |||
| x = rightLimit - 1; | |||
| addEdgePoint (x, y1 >> 8, direction * step); | |||
| y1 += step; | |||
| } | |||
| while (y1 < y2); | |||
| } | |||
| } | |||
| } | |||
| sanitiseLevels (path.isUsingNonZeroWinding()); | |||
| } | |||
| EdgeTable::EdgeTable (const Rectangle<int>& rectangleToAdd) | |||
| : bounds (rectangleToAdd), | |||
| maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), | |||
| lineStrideElements (juce_edgeTableDefaultEdgesPerLine * 2 + 1), | |||
| needToCheckEmptiness (true) | |||
| { | |||
| allocate(); | |||
| table[0] = 0; | |||
| const int x1 = rectangleToAdd.getX() << 8; | |||
| const int x2 = rectangleToAdd.getRight() << 8; | |||
| int* t = table; | |||
| for (int i = rectangleToAdd.getHeight(); --i >= 0;) | |||
| { | |||
| t[0] = 2; | |||
| t[1] = x1; | |||
| t[2] = 255; | |||
| t[3] = x2; | |||
| t[4] = 0; | |||
| t += lineStrideElements; | |||
| } | |||
| } | |||
| EdgeTable::EdgeTable (const RectangleList<int>& rectanglesToAdd) | |||
| : bounds (rectanglesToAdd.getBounds()), | |||
| maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), | |||
| lineStrideElements (juce_edgeTableDefaultEdgesPerLine * 2 + 1), | |||
| needToCheckEmptiness (true) | |||
| { | |||
| allocate(); | |||
| clearLineSizes(); | |||
| for (auto& r : rectanglesToAdd) | |||
| { | |||
| const int x1 = r.getX() << 8; | |||
| const int x2 = r.getRight() << 8; | |||
| int y = r.getY() - bounds.getY(); | |||
| for (int j = r.getHeight(); --j >= 0;) | |||
| addEdgePointPair (x1, x2, y++, 255); | |||
| } | |||
| sanitiseLevels (true); | |||
| } | |||
| EdgeTable::EdgeTable (const RectangleList<float>& rectanglesToAdd) | |||
| : bounds (rectanglesToAdd.getBounds().getSmallestIntegerContainer()), | |||
| maxEdgesPerLine (rectanglesToAdd.getNumRectangles() * 2), | |||
| lineStrideElements (rectanglesToAdd.getNumRectangles() * 4 + 1), | |||
| needToCheckEmptiness (true) | |||
| { | |||
| bounds.setHeight (bounds.getHeight() + 1); | |||
| allocate(); | |||
| clearLineSizes(); | |||
| for (auto& r : rectanglesToAdd) | |||
| { | |||
| const int x1 = roundToInt (r.getX() * 256.0f); | |||
| const int x2 = roundToInt (r.getRight() * 256.0f); | |||
| const int y1 = roundToInt (r.getY() * 256.0f) - (bounds.getY() << 8); | |||
| const int y2 = roundToInt (r.getBottom() * 256.0f) - (bounds.getY() << 8); | |||
| if (x2 <= x1 || y2 <= y1) | |||
| continue; | |||
| int y = y1 >> 8; | |||
| const int lastLine = y2 >> 8; | |||
| if (y == lastLine) | |||
| { | |||
| addEdgePointPair (x1, x2, y, y2 - y1); | |||
| } | |||
| else | |||
| { | |||
| addEdgePointPair (x1, x2, y++, 255 - (y1 & 255)); | |||
| while (y < lastLine) | |||
| addEdgePointPair (x1, x2, y++, 255); | |||
| jassert (y < bounds.getHeight()); | |||
| addEdgePointPair (x1, x2, y, y2 & 255); | |||
| } | |||
| } | |||
| sanitiseLevels (true); | |||
| } | |||
| EdgeTable::EdgeTable (const Rectangle<float>& rectangleToAdd) | |||
| : bounds (Rectangle<int> ((int) std::floor (rectangleToAdd.getX()), | |||
| roundToInt (rectangleToAdd.getY() * 256.0f) >> 8, | |||
| 2 + (int) rectangleToAdd.getWidth(), | |||
| 2 + (int) rectangleToAdd.getHeight())), | |||
| maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), | |||
| lineStrideElements ((juce_edgeTableDefaultEdgesPerLine << 1) + 1), | |||
| needToCheckEmptiness (true) | |||
| { | |||
| jassert (! rectangleToAdd.isEmpty()); | |||
| allocate(); | |||
| table[0] = 0; | |||
| const int x1 = roundToInt (rectangleToAdd.getX() * 256.0f); | |||
| const int x2 = roundToInt (rectangleToAdd.getRight() * 256.0f); | |||
| int y1 = roundToInt (rectangleToAdd.getY() * 256.0f) - (bounds.getY() << 8); | |||
| jassert (y1 < 256); | |||
| int y2 = roundToInt (rectangleToAdd.getBottom() * 256.0f) - (bounds.getY() << 8); | |||
| if (x2 <= x1 || y2 <= y1) | |||
| { | |||
| bounds.setHeight (0); | |||
| return; | |||
| } | |||
| int lineY = 0; | |||
| int* t = table; | |||
| if ((y1 >> 8) == (y2 >> 8)) | |||
| { | |||
| t[0] = 2; | |||
| t[1] = x1; | |||
| t[2] = y2 - y1; | |||
| t[3] = x2; | |||
| t[4] = 0; | |||
| ++lineY; | |||
| t += lineStrideElements; | |||
| } | |||
| else | |||
| { | |||
| t[0] = 2; | |||
| t[1] = x1; | |||
| t[2] = 255 - (y1 & 255); | |||
| t[3] = x2; | |||
| t[4] = 0; | |||
| ++lineY; | |||
| t += lineStrideElements; | |||
| while (lineY < (y2 >> 8)) | |||
| { | |||
| t[0] = 2; | |||
| t[1] = x1; | |||
| t[2] = 255; | |||
| t[3] = x2; | |||
| t[4] = 0; | |||
| ++lineY; | |||
| t += lineStrideElements; | |||
| } | |||
| jassert (lineY < bounds.getHeight()); | |||
| t[0] = 2; | |||
| t[1] = x1; | |||
| t[2] = y2 & 255; | |||
| t[3] = x2; | |||
| t[4] = 0; | |||
| ++lineY; | |||
| t += lineStrideElements; | |||
| } | |||
| while (lineY < bounds.getHeight()) | |||
| { | |||
| t[0] = 0; | |||
| t += lineStrideElements; | |||
| ++lineY; | |||
| } | |||
| } | |||
| EdgeTable::EdgeTable (const EdgeTable& other) | |||
| { | |||
| operator= (other); | |||
| } | |||
| EdgeTable& EdgeTable::operator= (const EdgeTable& other) | |||
| { | |||
| bounds = other.bounds; | |||
| maxEdgesPerLine = other.maxEdgesPerLine; | |||
| lineStrideElements = other.lineStrideElements; | |||
| needToCheckEmptiness = other.needToCheckEmptiness; | |||
| allocate(); | |||
| copyEdgeTableData (table, lineStrideElements, other.table, lineStrideElements, bounds.getHeight()); | |||
| return *this; | |||
| } | |||
| EdgeTable::~EdgeTable() | |||
| { | |||
| } | |||
| //============================================================================== | |||
| static size_t getEdgeTableAllocationSize (int lineStride, int height) noexcept | |||
| { | |||
| // (leave an extra line at the end for use as scratch space) | |||
| return (size_t) (lineStride * (2 + jmax (0, height))); | |||
| } | |||
| void EdgeTable::allocate() | |||
| { | |||
| table.malloc (getEdgeTableAllocationSize (lineStrideElements, bounds.getHeight())); | |||
| } | |||
| void EdgeTable::clearLineSizes() noexcept | |||
| { | |||
| int* t = table; | |||
| for (int i = bounds.getHeight(); --i >= 0;) | |||
| { | |||
| *t = 0; | |||
| t += lineStrideElements; | |||
| } | |||
| } | |||
| void EdgeTable::copyEdgeTableData (int* dest, const int destLineStride, const int* src, const int srcLineStride, int numLines) noexcept | |||
| { | |||
| while (--numLines >= 0) | |||
| { | |||
| memcpy (dest, src, (size_t) (src[0] * 2 + 1) * sizeof (int)); | |||
| src += srcLineStride; | |||
| dest += destLineStride; | |||
| } | |||
| } | |||
| void EdgeTable::sanitiseLevels (const bool useNonZeroWinding) noexcept | |||
| { | |||
| // Convert the table from relative windings to absolute levels.. | |||
| int* lineStart = table; | |||
| for (int y = bounds.getHeight(); --y >= 0;) | |||
| { | |||
| int num = lineStart[0]; | |||
| if (num > 0) | |||
| { | |||
| LineItem* items = reinterpret_cast<LineItem*> (lineStart + 1); | |||
| LineItem* const itemsEnd = items + num; | |||
| // sort the X coords | |||
| std::sort (items, itemsEnd); | |||
| const LineItem* src = items; | |||
| int correctedNum = num; | |||
| int level = 0; | |||
| while (src < itemsEnd) | |||
| { | |||
| level += src->level; | |||
| const int x = src->x; | |||
| ++src; | |||
| while (src < itemsEnd && src->x == x) | |||
| { | |||
| level += src->level; | |||
| ++src; | |||
| --correctedNum; | |||
| } | |||
| int corrected = std::abs (level); | |||
| if (corrected >> 8) | |||
| { | |||
| if (useNonZeroWinding) | |||
| { | |||
| corrected = 255; | |||
| } | |||
| else | |||
| { | |||
| corrected &= 511; | |||
| if (corrected >> 8) | |||
| corrected = 511 - corrected; | |||
| } | |||
| } | |||
| items->x = x; | |||
| items->level = corrected; | |||
| ++items; | |||
| } | |||
| lineStart[0] = correctedNum; | |||
| (items - 1)->level = 0; // force the last level to 0, just in case something went wrong in creating the table | |||
| } | |||
| lineStart += lineStrideElements; | |||
| } | |||
| } | |||
| void EdgeTable::remapTableForNumEdges (const int newNumEdgesPerLine) | |||
| { | |||
| if (newNumEdgesPerLine != maxEdgesPerLine) | |||
| { | |||
| maxEdgesPerLine = newNumEdgesPerLine; | |||
| jassert (bounds.getHeight() > 0); | |||
| const int newLineStrideElements = maxEdgesPerLine * 2 + 1; | |||
| HeapBlock<int> newTable (getEdgeTableAllocationSize (newLineStrideElements, bounds.getHeight())); | |||
| copyEdgeTableData (newTable, newLineStrideElements, table, lineStrideElements, bounds.getHeight()); | |||
| table.swapWith (newTable); | |||
| lineStrideElements = newLineStrideElements; | |||
| } | |||
| } | |||
| void EdgeTable::optimiseTable() | |||
| { | |||
| int maxLineElements = 0; | |||
| for (int i = bounds.getHeight(); --i >= 0;) | |||
| maxLineElements = jmax (maxLineElements, table [i * lineStrideElements]); | |||
| remapTableForNumEdges (maxLineElements); | |||
| } | |||
| void EdgeTable::addEdgePoint (const int x, const int y, const int winding) | |||
| { | |||
| jassert (y >= 0 && y < bounds.getHeight()); | |||
| int* line = table + lineStrideElements * y; | |||
| const int numPoints = line[0]; | |||
| if (numPoints >= maxEdgesPerLine) | |||
| { | |||
| remapTableForNumEdges (maxEdgesPerLine + juce_edgeTableDefaultEdgesPerLine); | |||
| jassert (numPoints < maxEdgesPerLine); | |||
| line = table + lineStrideElements * y; | |||
| } | |||
| line[0]++; | |||
| int n = numPoints << 1; | |||
| line [n + 1] = x; | |||
| line [n + 2] = winding; | |||
| } | |||
| void EdgeTable::addEdgePointPair (int x1, int x2, int y, int winding) | |||
| { | |||
| jassert (y >= 0 && y < bounds.getHeight()); | |||
| int* line = table + lineStrideElements * y; | |||
| const int numPoints = line[0]; | |||
| if (numPoints + 1 >= maxEdgesPerLine) | |||
| { | |||
| remapTableForNumEdges (maxEdgesPerLine + juce_edgeTableDefaultEdgesPerLine); | |||
| jassert (numPoints < maxEdgesPerLine); | |||
| line = table + lineStrideElements * y; | |||
| } | |||
| line[0] = numPoints + 2; | |||
| line += numPoints << 1; | |||
| line[1] = x1; | |||
| line[2] = winding; | |||
| line[3] = x2; | |||
| line[4] = -winding; | |||
| } | |||
| void EdgeTable::translate (float dx, const int dy) noexcept | |||
| { | |||
| bounds.translate ((int) std::floor (dx), dy); | |||
| int* lineStart = table; | |||
| const int intDx = (int) (dx * 256.0f); | |||
| for (int i = bounds.getHeight(); --i >= 0;) | |||
| { | |||
| int* line = lineStart; | |||
| lineStart += lineStrideElements; | |||
| int num = *line++; | |||
| while (--num >= 0) | |||
| { | |||
| *line += intDx; | |||
| line += 2; | |||
| } | |||
| } | |||
| } | |||
| void EdgeTable::multiplyLevels (float amount) | |||
| { | |||
| int* lineStart = table; | |||
| const int multiplier = (int) (amount * 256.0f); | |||
| for (int y = 0; y < bounds.getHeight(); ++y) | |||
| { | |||
| int numPoints = lineStart[0]; | |||
| LineItem* item = reinterpret_cast<LineItem*> (lineStart + 1); | |||
| lineStart += lineStrideElements; | |||
| while (--numPoints > 0) | |||
| { | |||
| item->level = jmin (255, (item->level * multiplier) >> 8); | |||
| ++item; | |||
| } | |||
| } | |||
| } | |||
| void EdgeTable::intersectWithEdgeTableLine (const int y, const int* const otherLine) | |||
| { | |||
| jassert (y >= 0 && y < bounds.getHeight()); | |||
| int* srcLine = table + lineStrideElements * y; | |||
| int srcNum1 = *srcLine; | |||
| if (srcNum1 == 0) | |||
| return; | |||
| int srcNum2 = *otherLine; | |||
| if (srcNum2 == 0) | |||
| { | |||
| *srcLine = 0; | |||
| return; | |||
| } | |||
| const int right = bounds.getRight() << 8; | |||
| // optimise for the common case where our line lies entirely within a | |||
| // single pair of points, as happens when clipping to a simple rect. | |||
| if (srcNum2 == 2 && otherLine[2] >= 255) | |||
| { | |||
| clipEdgeTableLineToRange (srcLine, otherLine[1], jmin (right, otherLine[3])); | |||
| return; | |||
| } | |||
| bool isUsingTempSpace = false; | |||
| const int* src1 = srcLine + 1; | |||
| int x1 = *src1++; | |||
| const int* src2 = otherLine + 1; | |||
| int x2 = *src2++; | |||
| int destIndex = 0, destTotal = 0; | |||
| int level1 = 0, level2 = 0; | |||
| int lastX = std::numeric_limits<int>::min(), lastLevel = 0; | |||
| while (srcNum1 > 0 && srcNum2 > 0) | |||
| { | |||
| int nextX; | |||
| if (x1 <= x2) | |||
| { | |||
| if (x1 == x2) | |||
| { | |||
| level2 = *src2++; | |||
| x2 = *src2++; | |||
| --srcNum2; | |||
| } | |||
| nextX = x1; | |||
| level1 = *src1++; | |||
| x1 = *src1++; | |||
| --srcNum1; | |||
| } | |||
| else | |||
| { | |||
| nextX = x2; | |||
| level2 = *src2++; | |||
| x2 = *src2++; | |||
| --srcNum2; | |||
| } | |||
| if (nextX > lastX) | |||
| { | |||
| if (nextX >= right) | |||
| break; | |||
| lastX = nextX; | |||
| const int nextLevel = (level1 * (level2 + 1)) >> 8; | |||
| jassert (isPositiveAndBelow (nextLevel, 256)); | |||
| if (nextLevel != lastLevel) | |||
| { | |||
| if (destTotal >= maxEdgesPerLine) | |||
| { | |||
| srcLine[0] = destTotal; | |||
| if (isUsingTempSpace) | |||
| { | |||
| const size_t tempSize = (size_t) srcNum1 * 2 * sizeof (int); | |||
| int* const oldTemp = static_cast<int*> (alloca (tempSize)); | |||
| memcpy (oldTemp, src1, tempSize); | |||
| remapTableForNumEdges (jmax (256, destTotal * 2)); | |||
| srcLine = table + lineStrideElements * y; | |||
| int* const newTemp = table + lineStrideElements * bounds.getHeight(); | |||
| memcpy (newTemp, oldTemp, tempSize); | |||
| src1 = newTemp; | |||
| } | |||
| else | |||
| { | |||
| remapTableForNumEdges (jmax (256, destTotal * 2)); | |||
| srcLine = table + lineStrideElements * y; | |||
| } | |||
| } | |||
| ++destTotal; | |||
| lastLevel = nextLevel; | |||
| if (! isUsingTempSpace) | |||
| { | |||
| isUsingTempSpace = true; | |||
| int* const temp = table + lineStrideElements * bounds.getHeight(); | |||
| memcpy (temp, src1, (size_t) srcNum1 * 2 * sizeof (int)); | |||
| src1 = temp; | |||
| } | |||
| srcLine[++destIndex] = nextX; | |||
| srcLine[++destIndex] = nextLevel; | |||
| } | |||
| } | |||
| } | |||
| if (lastLevel > 0) | |||
| { | |||
| if (destTotal >= maxEdgesPerLine) | |||
| { | |||
| srcLine[0] = destTotal; | |||
| remapTableForNumEdges (jmax (256, destTotal * 2)); | |||
| srcLine = table + lineStrideElements * y; | |||
| } | |||
| ++destTotal; | |||
| srcLine[++destIndex] = right; | |||
| srcLine[++destIndex] = 0; | |||
| } | |||
| srcLine[0] = destTotal; | |||
| } | |||
| void EdgeTable::clipEdgeTableLineToRange (int* dest, const int x1, const int x2) noexcept | |||
| { | |||
| int* lastItem = dest + (dest[0] * 2 - 1); | |||
| if (x2 < lastItem[0]) | |||
| { | |||
| if (x2 <= dest[1]) | |||
| { | |||
| dest[0] = 0; | |||
| return; | |||
| } | |||
| while (x2 < lastItem[-2]) | |||
| { | |||
| --(dest[0]); | |||
| lastItem -= 2; | |||
| } | |||
| lastItem[0] = x2; | |||
| lastItem[1] = 0; | |||
| } | |||
| if (x1 > dest[1]) | |||
| { | |||
| while (lastItem[0] > x1) | |||
| lastItem -= 2; | |||
| const int itemsRemoved = (int) (lastItem - (dest + 1)) / 2; | |||
| if (itemsRemoved > 0) | |||
| { | |||
| dest[0] -= itemsRemoved; | |||
| memmove (dest + 1, lastItem, (size_t) dest[0] * (sizeof (int) * 2)); | |||
| } | |||
| dest[1] = x1; | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void EdgeTable::clipToRectangle (const Rectangle<int>& r) | |||
| { | |||
| const Rectangle<int> clipped (r.getIntersection (bounds)); | |||
| if (clipped.isEmpty()) | |||
| { | |||
| needToCheckEmptiness = false; | |||
| bounds.setHeight (0); | |||
| } | |||
| else | |||
| { | |||
| const int top = clipped.getY() - bounds.getY(); | |||
| const int bottom = clipped.getBottom() - bounds.getY(); | |||
| if (bottom < bounds.getHeight()) | |||
| bounds.setHeight (bottom); | |||
| for (int i = top; --i >= 0;) | |||
| table [lineStrideElements * i] = 0; | |||
| if (clipped.getX() > bounds.getX() || clipped.getRight() < bounds.getRight()) | |||
| { | |||
| const int x1 = clipped.getX() << 8; | |||
| const int x2 = jmin (bounds.getRight(), clipped.getRight()) << 8; | |||
| int* line = table + lineStrideElements * top; | |||
| for (int i = bottom - top; --i >= 0;) | |||
| { | |||
| if (line[0] != 0) | |||
| clipEdgeTableLineToRange (line, x1, x2); | |||
| line += lineStrideElements; | |||
| } | |||
| } | |||
| needToCheckEmptiness = true; | |||
| } | |||
| } | |||
| void EdgeTable::excludeRectangle (const Rectangle<int>& r) | |||
| { | |||
| const Rectangle<int> clipped (r.getIntersection (bounds)); | |||
| if (! clipped.isEmpty()) | |||
| { | |||
| const int top = clipped.getY() - bounds.getY(); | |||
| const int bottom = clipped.getBottom() - bounds.getY(); | |||
| const int rectLine[] = { 4, std::numeric_limits<int>::min(), 255, | |||
| clipped.getX() << 8, 0, | |||
| clipped.getRight() << 8, 255, | |||
| std::numeric_limits<int>::max(), 0 }; | |||
| for (int i = top; i < bottom; ++i) | |||
| intersectWithEdgeTableLine (i, rectLine); | |||
| needToCheckEmptiness = true; | |||
| } | |||
| } | |||
| void EdgeTable::clipToEdgeTable (const EdgeTable& other) | |||
| { | |||
| const Rectangle<int> clipped (other.bounds.getIntersection (bounds)); | |||
| if (clipped.isEmpty()) | |||
| { | |||
| needToCheckEmptiness = false; | |||
| bounds.setHeight (0); | |||
| } | |||
| else | |||
| { | |||
| const int top = clipped.getY() - bounds.getY(); | |||
| const int bottom = clipped.getBottom() - bounds.getY(); | |||
| if (bottom < bounds.getHeight()) | |||
| bounds.setHeight (bottom); | |||
| if (clipped.getRight() < bounds.getRight()) | |||
| bounds.setRight (clipped.getRight()); | |||
| for (int i = 0; i < top; ++i) | |||
| table [lineStrideElements * i] = 0; | |||
| const int* otherLine = other.table + other.lineStrideElements * (clipped.getY() - other.bounds.getY()); | |||
| for (int i = top; i < bottom; ++i) | |||
| { | |||
| intersectWithEdgeTableLine (i, otherLine); | |||
| otherLine += other.lineStrideElements; | |||
| } | |||
| needToCheckEmptiness = true; | |||
| } | |||
| } | |||
| void EdgeTable::clipLineToMask (int x, int y, const uint8* mask, int maskStride, int numPixels) | |||
| { | |||
| y -= bounds.getY(); | |||
| if (y < 0 || y >= bounds.getHeight()) | |||
| return; | |||
| needToCheckEmptiness = true; | |||
| if (numPixels <= 0) | |||
| { | |||
| table [lineStrideElements * y] = 0; | |||
| return; | |||
| } | |||
| int* tempLine = static_cast<int*> (alloca ((size_t) (numPixels * 2 + 4) * sizeof (int))); | |||
| int destIndex = 0, lastLevel = 0; | |||
| while (--numPixels >= 0) | |||
| { | |||
| const int alpha = *mask; | |||
| mask += maskStride; | |||
| if (alpha != lastLevel) | |||
| { | |||
| tempLine[++destIndex] = (x << 8); | |||
| tempLine[++destIndex] = alpha; | |||
| lastLevel = alpha; | |||
| } | |||
| ++x; | |||
| } | |||
| if (lastLevel > 0) | |||
| { | |||
| tempLine[++destIndex] = (x << 8); | |||
| tempLine[++destIndex] = 0; | |||
| } | |||
| tempLine[0] = destIndex >> 1; | |||
| intersectWithEdgeTableLine (y, tempLine); | |||
| } | |||
| bool EdgeTable::isEmpty() noexcept | |||
| { | |||
| if (needToCheckEmptiness) | |||
| { | |||
| needToCheckEmptiness = false; | |||
| int* t = table; | |||
| for (int i = bounds.getHeight(); --i >= 0;) | |||
| { | |||
| if (t[0] > 1) | |||
| return false; | |||
| t += lineStrideElements; | |||
| } | |||
| bounds.setHeight (0); | |||
| } | |||
| return bounds.getHeight() == 0; | |||
| } | |||
| } // namespace juce | |||