@@ -1,119 +0,0 @@ | |||
#!/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) | |||
# ---------------------------------------------------------------------------------------------------------------------------- |
@@ -1,109 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,132 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,363 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,252 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,44 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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" |
@@ -1,64 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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" |
@@ -1,352 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,246 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,99 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,159 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,312 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,242 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,243 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,578 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,242 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,97 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,123 +0,0 @@ | |||
#!/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) | |||
# ---------------------------------------------------------------------------------------------------------------------------- |
@@ -1,92 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,77 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,46 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,93 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,108 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,99 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,101 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,61 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,267 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,189 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,362 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,207 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,81 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,104 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,102 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,96 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,333 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,312 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,72 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,94 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,63 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,200 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,62 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,52 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,68 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,387 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,381 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,57 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,39 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,147 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,103 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,55 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,265 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,430 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,105 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,135 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,232 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,26 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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) | |||
} |
@@ -1,131 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,108 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,123 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,371 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,135 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,123 +0,0 @@ | |||
#!/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) | |||
# ---------------------------------------------------------------------------------------------------------------------------- |
@@ -1,468 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,367 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,244 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,202 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,335 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,109 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,153 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,150 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,757 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,699 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,746 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,101 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,540 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,120 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,45 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,56 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,189 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,110 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,58 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,75 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,70 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,356 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,207 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,413 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,165 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,722 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,478 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,820 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,327 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,592 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,195 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,267 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,161 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,269 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,279 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,153 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |
@@ -1,838 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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 |