| @@ -26,12 +26,12 @@ namespace MidiBufferHelpers | |||||
| { | { | ||||
| inline int getEventTime (const void* const d) noexcept | inline int getEventTime (const void* const d) noexcept | ||||
| { | { | ||||
| return *static_cast<const int32*> (d); | |||||
| return readUnaligned<int32> (d); | |||||
| } | } | ||||
| inline uint16 getEventDataSize (const void* const d) noexcept | inline uint16 getEventDataSize (const void* const d) noexcept | ||||
| { | { | ||||
| return *reinterpret_cast<const uint16*> (static_cast<const char*> (d) + sizeof (int32)); | |||||
| return readUnaligned<uint16> (static_cast<const char*> (d) + sizeof (int32)); | |||||
| } | } | ||||
| inline uint16 getEventTotalSize (const void* const d) noexcept | inline uint16 getEventTotalSize (const void* const d) noexcept | ||||
| @@ -124,8 +124,8 @@ void MidiBuffer::addEvent (const void* const newData, const int maxBytes, const | |||||
| data.insertMultiple (offset, 0, (int) newItemSize); | data.insertMultiple (offset, 0, (int) newItemSize); | ||||
| uint8* const d = data.begin() + offset; | uint8* const d = data.begin() + offset; | ||||
| *reinterpret_cast<int32*> (d) = sampleNumber; | |||||
| *reinterpret_cast<uint16*> (d + 4) = (uint16) numBytes; | |||||
| writeUnaligned<int32> (d, sampleNumber); | |||||
| writeUnaligned<uint16> (d + 4, static_cast<uint16> (numBytes)); | |||||
| memcpy (d + 6, newData, (size_t) numBytes); | memcpy (d + 6, newData, (size_t) numBytes); | ||||
| } | } | ||||
| } | } | ||||
| @@ -538,10 +538,10 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||||
| { | { | ||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | SynthesiserVoice* const voice = voices.getUnchecked (i); | ||||
| jassert (voice->isVoiceActive()); // We wouldn't be here otherwise | |||||
| if (voice->canPlaySound (soundToPlay)) | if (voice->canPlaySound (soundToPlay)) | ||||
| { | { | ||||
| jassert (voice->isVoiceActive()); // We wouldn't be here otherwise | |||||
| VoiceAgeSorter sorter; | VoiceAgeSorter sorter; | ||||
| usableVoices.addSorted (sorter, voice); | usableVoices.addSorted (sorter, voice); | ||||
| @@ -977,7 +977,6 @@ bool AudioProcessorGraph::removeNode (const uint32 nodeId) | |||||
| { | { | ||||
| if (nodes.getUnchecked(i)->nodeId == nodeId) | if (nodes.getUnchecked(i)->nodeId == nodeId) | ||||
| { | { | ||||
| nodes.getUnchecked(i)->setParentGraph (nullptr); | |||||
| nodes.remove (i); | nodes.remove (i); | ||||
| triggerAsyncUpdate(); | triggerAsyncUpdate(); | ||||
| @@ -1282,6 +1281,8 @@ void AudioProcessorGraph::setPlayHead (AudioPlayHead* audioPlayHead) | |||||
| { | { | ||||
| const ScopedLock sl (getCallbackLock()); | const ScopedLock sl (getCallbackLock()); | ||||
| AudioProcessor::setPlayHead (audioPlayHead); | |||||
| for (int i = 0; i < nodes.size(); ++i) | for (int i = 0; i < nodes.size(); ++i) | ||||
| nodes.getUnchecked(i)->getProcessor()->setPlayHead (audioPlayHead); | nodes.getUnchecked(i)->getProcessor()->setPlayHead (audioPlayHead); | ||||
| } | } | ||||
| @@ -63,6 +63,14 @@ | |||||
| */ | */ | ||||
| #define JUCE_INCLUDE_ZLIB_CODE 1 | #define JUCE_INCLUDE_ZLIB_CODE 1 | ||||
| /** Config: JUCE_USE_CURL | |||||
| Enables http/https support via libcurl (Linux only). Enabling this will add an additional | |||||
| run-time dynmic dependency to libcurl. | |||||
| If you disable this then https/ssl support will not be available on linux. | |||||
| */ | |||||
| #define JUCE_USE_CURL 0 | |||||
| /* Config: JUCE_CATCH_UNHANDLED_EXCEPTIONS | /* Config: JUCE_CATCH_UNHANDLED_EXCEPTIONS | ||||
| If enabled, this will add some exception-catching code to forward unhandled exceptions | If enabled, this will add some exception-catching code to forward unhandled exceptions | ||||
| to your JUCEApplicationBase::unhandledException() callback. | to your JUCEApplicationBase::unhandledException() callback. | ||||
| @@ -1584,8 +1584,8 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||||
| static var Math_range (Args a) { return isInt (a, 0) ? var (jlimit (getInt (a, 1), getInt (a, 2), getInt (a, 0))) : var (jlimit (getDouble (a, 1), getDouble (a, 2), getDouble (a, 0))); } | static var Math_range (Args a) { return isInt (a, 0) ? var (jlimit (getInt (a, 1), getInt (a, 2), getInt (a, 0))) : var (jlimit (getDouble (a, 1), getDouble (a, 2), getDouble (a, 0))); } | ||||
| static var Math_min (Args a) { return (isInt (a, 0) && isInt (a, 1)) ? var (jmin (getInt (a, 0), getInt (a, 1))) : var (jmin (getDouble (a, 0), getDouble (a, 1))); } | static var Math_min (Args a) { return (isInt (a, 0) && isInt (a, 1)) ? var (jmin (getInt (a, 0), getInt (a, 1))) : var (jmin (getDouble (a, 0), getDouble (a, 1))); } | ||||
| static var Math_max (Args a) { return (isInt (a, 0) && isInt (a, 1)) ? var (jmax (getInt (a, 0), getInt (a, 1))) : var (jmax (getDouble (a, 0), getDouble (a, 1))); } | static var Math_max (Args a) { return (isInt (a, 0) && isInt (a, 1)) ? var (jmax (getInt (a, 0), getInt (a, 1))) : var (jmax (getDouble (a, 0), getDouble (a, 1))); } | ||||
| static var Math_toDegrees (Args a) { return (180.0 / double_Pi) * getDouble (a, 0); } | |||||
| static var Math_toRadians (Args a) { return (double_Pi / 180.0) * getDouble (a, 0); } | |||||
| static var Math_toDegrees (Args a) { return radiansToDegrees (getDouble (a, 0)); } | |||||
| static var Math_toRadians (Args a) { return degreesToRadians (getDouble (a, 0)); } | |||||
| static var Math_sin (Args a) { return sin (getDouble (a, 0)); } | static var Math_sin (Args a) { return sin (getDouble (a, 0)); } | ||||
| static var Math_asin (Args a) { return asin (getDouble (a, 0)); } | static var Math_asin (Args a) { return asin (getDouble (a, 0)); } | ||||
| static var Math_cos (Args a) { return cos (getDouble (a, 0)); } | static var Math_cos (Args a) { return cos (getDouble (a, 0)); } | ||||
| @@ -82,6 +82,10 @@ | |||||
| #if JUCE_LINUX | #if JUCE_LINUX | ||||
| #include <langinfo.h> | #include <langinfo.h> | ||||
| #include <ifaddrs.h> | #include <ifaddrs.h> | ||||
| #if JUCE_USE_CURL | |||||
| #include <curl/curl.h> | |||||
| #endif | |||||
| #endif | #endif | ||||
| #include <pwd.h> | #include <pwd.h> | ||||
| @@ -210,6 +214,9 @@ namespace juce | |||||
| #include "native/juce_linux_CommonFile.cpp" | #include "native/juce_linux_CommonFile.cpp" | ||||
| #include "native/juce_linux_Files.cpp" | #include "native/juce_linux_Files.cpp" | ||||
| #include "native/juce_linux_Network.cpp" | #include "native/juce_linux_Network.cpp" | ||||
| #if JUCE_USE_CURL | |||||
| #include "native/juce_curl_Network.cpp" | |||||
| #endif | |||||
| #include "native/juce_linux_SystemStats.cpp" | #include "native/juce_linux_SystemStats.cpp" | ||||
| #include "native/juce_linux_Threads.cpp" | #include "native/juce_linux_Threads.cpp" | ||||
| @@ -126,6 +126,17 @@ | |||||
| #define JUCE_ZLIB_INCLUDE_PATH <zlib.h> | #define JUCE_ZLIB_INCLUDE_PATH <zlib.h> | ||||
| #endif | #endif | ||||
| /** Config: JUCE_USE_CURL | |||||
| Enables http/https support via libcurl (Linux only). Enabling this will add an additional | |||||
| run-time dynmic dependency to libcurl. | |||||
| If you disable this then https/ssl support will not be available on linux. | |||||
| */ | |||||
| #ifndef JUCE_USE_CURL | |||||
| #define JUCE_USE_CURL 0 | |||||
| #endif | |||||
| /* Config: JUCE_CATCH_UNHANDLED_EXCEPTIONS | /* Config: JUCE_CATCH_UNHANDLED_EXCEPTIONS | ||||
| If enabled, this will add some exception-catching code to forward unhandled exceptions | If enabled, this will add some exception-catching code to forward unhandled exceptions | ||||
| to your JUCEApplicationBase::unhandledException() callback. | to your JUCEApplicationBase::unhandledException() callback. | ||||
| @@ -354,6 +354,15 @@ const double double_Pi = 3.1415926535897932384626433832795; | |||||
| const float float_Pi = 3.14159265358979323846f; | const float float_Pi = 3.14159265358979323846f; | ||||
| /** Converts an angle in degrees to radians. */ | |||||
| template <typename FloatType> | |||||
| inline FloatType degreesToRadians (FloatType degrees) noexcept { return degrees * static_cast<FloatType> (double_Pi / 180.0); } | |||||
| /** Converts an angle in radians to degrees. */ | |||||
| template <typename FloatType> | |||||
| inline FloatType radiansToDegrees (FloatType radians) noexcept { return radians * static_cast<FloatType> (180.0 / double_Pi); } | |||||
| //============================================================================== | //============================================================================== | ||||
| /** The isfinite() method seems to vary between platforms, so this is a | /** The isfinite() method seems to vary between platforms, so this is a | ||||
| platform-independent function for it. | platform-independent function for it. | ||||
| @@ -477,16 +486,14 @@ inline int roundFloatToInt (const float value) noexcept | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| /** Returns true if the specified integer is a power-of-two. | |||||
| */ | |||||
| /** Returns true if the specified integer is a power-of-two. */ | |||||
| template <typename IntegerType> | template <typename IntegerType> | ||||
| bool isPowerOfTwo (IntegerType value) | bool isPowerOfTwo (IntegerType value) | ||||
| { | { | ||||
| return (value & (value - 1)) == 0; | return (value & (value - 1)) == 0; | ||||
| } | } | ||||
| /** Returns the smallest power-of-two which is equal to or greater than the given integer. | |||||
| */ | |||||
| /** Returns the smallest power-of-two which is equal to or greater than the given integer. */ | |||||
| inline int nextPowerOfTwo (int n) noexcept | inline int nextPowerOfTwo (int n) noexcept | ||||
| { | { | ||||
| --n; | --n; | ||||
| @@ -533,6 +540,7 @@ NumericType square (NumericType n) noexcept | |||||
| return n * n; | return n * n; | ||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| #if JUCE_INTEL || defined (DOXYGEN) | #if JUCE_INTEL || defined (DOXYGEN) | ||||
| /** This macro can be applied to a float variable to check whether it contains a denormalised | /** This macro can be applied to a float variable to check whether it contains a denormalised | ||||
| @@ -64,6 +64,24 @@ inline int getAddressDifference (Type1* pointer1, Type2* pointer2) noexcept { r | |||||
| template <class Type> | template <class Type> | ||||
| inline Type* createCopyIfNotNull (const Type* objectToCopy) { return objectToCopy != nullptr ? new Type (*objectToCopy) : nullptr; } | inline Type* createCopyIfNotNull (const Type* objectToCopy) { return objectToCopy != nullptr ? new Type (*objectToCopy) : nullptr; } | ||||
| //============================================================================== | |||||
| /** A handy function to read un-aligned memory without a performance penalty or bus-error. */ | |||||
| template <typename Type> | |||||
| inline Type readUnaligned (const void* srcPtr) noexcept | |||||
| { | |||||
| Type value; | |||||
| memcpy (&value, srcPtr, sizeof (Type)); | |||||
| return value; | |||||
| } | |||||
| /** A handy function to write un-aligned memory without a performance penalty or bus-error. */ | |||||
| template <typename Type> | |||||
| inline void writeUnaligned (void* dstPtr, Type value) noexcept | |||||
| { | |||||
| memcpy (dstPtr, &value, sizeof(Type)); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| #if JUCE_MAC || JUCE_IOS || DOXYGEN | #if JUCE_MAC || JUCE_IOS || DOXYGEN | ||||
| @@ -227,8 +227,14 @@ private: | |||||
| //============================================================================== | //============================================================================== | ||||
| #if defined (__arm__) | |||||
| #define JUCE_ARM_SOFT_FLOAT_ABI __attribute__ ((pcs("aapcs"))) | |||||
| #else | |||||
| #define JUCE_ARM_SOFT_FLOAT_ABI | |||||
| #endif | |||||
| #define JUCE_JNI_CALLBACK(className, methodName, returnType, params) \ | #define JUCE_JNI_CALLBACK(className, methodName, returnType, params) \ | ||||
| extern "C" __attribute__ ((visibility("default"))) returnType JUCE_JOIN_MACRO (JUCE_JOIN_MACRO (Java_, className), _ ## methodName) params | |||||
| extern "C" __attribute__ ((visibility("default"))) JUCE_ARM_SOFT_FLOAT_ABI returnType JUCE_JOIN_MACRO (JUCE_JOIN_MACRO (Java_, className), _ ## methodName) params | |||||
| //============================================================================== | //============================================================================== | ||||
| class AndroidSystem | class AndroidSystem | ||||
| @@ -0,0 +1,472 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the juce_core module of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software Ltd. | |||||
| 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. | |||||
| THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN | |||||
| NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | |||||
| DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER | |||||
| IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |||||
| CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||||
| ------------------------------------------------------------------------------ | |||||
| NOTE! This permissive ISC license applies ONLY to files within the juce_core module! | |||||
| All other JUCE modules are covered by a dual GPL/commercial license, so if you are | |||||
| using any other modules, be sure to check that you also comply with their license. | |||||
| For more details, visit www.juce.com | |||||
| ============================================================================== | |||||
| */ | |||||
| class WebInputStream : public InputStream | |||||
| { | |||||
| public: | |||||
| WebInputStream (const String& address, bool isPost, const MemoryBlock& postData, | |||||
| URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | |||||
| const String& headers, int timeOutMs, StringPairArray* responseHeaders, | |||||
| const int maxRedirects) | |||||
| : multi (nullptr), curl (nullptr), headerList (nullptr), lastError (CURLE_OK), | |||||
| contentLength (-1), streamPos (0), | |||||
| finished (false), skipBytes (0), | |||||
| postBuffer (nullptr), postPosition (0) | |||||
| { | |||||
| statusCode = -1; | |||||
| if (init() && setOptions (address, timeOutMs, (responseHeaders != nullptr), | |||||
| maxRedirects, headers, isPost, postData.getSize())) | |||||
| { | |||||
| connect (responseHeaders, isPost, postData, progressCallback, progressCallbackContext); | |||||
| } | |||||
| else | |||||
| { | |||||
| cleanup(); | |||||
| } | |||||
| } | |||||
| ~WebInputStream() | |||||
| { | |||||
| cleanup(); | |||||
| } | |||||
| //============================================================================== | |||||
| // Input Stream overrides | |||||
| bool isError() const { return curl == nullptr || lastError != CURLE_OK; } | |||||
| bool isExhausted() override { return (isError() || finished) && curlBuffer.getSize() == 0; } | |||||
| int64 getPosition() override { return streamPos; } | |||||
| int64 getTotalLength() override { return contentLength; } | |||||
| int read (void* buffer, int bytesToRead) override | |||||
| { | |||||
| return readOrSkip (buffer, bytesToRead, false); | |||||
| } | |||||
| bool setPosition (int64 wantedPos) override | |||||
| { | |||||
| const int amountToSkip = static_cast<int> (wantedPos - getPosition()); | |||||
| if (amountToSkip < 0) | |||||
| return false; | |||||
| if (amountToSkip == 0) | |||||
| return true; | |||||
| const int actuallySkipped = readOrSkip (nullptr, amountToSkip, true); | |||||
| return actuallySkipped == amountToSkip; | |||||
| } | |||||
| //============================================================================== | |||||
| int statusCode; | |||||
| private: | |||||
| //============================================================================== | |||||
| bool init() | |||||
| { | |||||
| multi = curl_multi_init(); | |||||
| if (multi != nullptr) | |||||
| { | |||||
| curl = curl_easy_init(); | |||||
| if (curl != nullptr) | |||||
| if (curl_multi_add_handle (multi, curl) == CURLM_OK) | |||||
| return true; | |||||
| } | |||||
| cleanup(); | |||||
| return false; | |||||
| } | |||||
| void cleanup() | |||||
| { | |||||
| if (curl != nullptr) | |||||
| { | |||||
| curl_multi_remove_handle (multi, curl); | |||||
| if (headerList != nullptr) | |||||
| { | |||||
| curl_slist_free_all (headerList); | |||||
| headerList = nullptr; | |||||
| } | |||||
| curl_easy_cleanup (curl); | |||||
| curl = nullptr; | |||||
| } | |||||
| if (multi != nullptr) | |||||
| { | |||||
| curl_multi_cleanup (multi); | |||||
| multi = nullptr; | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| bool setOptions (const String& address, int timeOutMs, bool wantsHeaders, | |||||
| const int maxRedirects, const String& headers, | |||||
| bool isPost, size_t postSize) | |||||
| { | |||||
| if (curl_easy_setopt (curl, CURLOPT_URL, address.toRawUTF8()) == CURLE_OK | |||||
| && curl_easy_setopt (curl, CURLOPT_WRITEDATA, this) == CURLE_OK | |||||
| && curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, StaticCurlWrite) == CURLE_OK | |||||
| && curl_easy_setopt (curl, CURLOPT_MAXREDIRS, static_cast<long> (maxRedirects)) == CURLE_OK) | |||||
| { | |||||
| if (isPost) | |||||
| { | |||||
| if (curl_easy_setopt (curl, CURLOPT_READDATA, this) != CURLE_OK | |||||
| || curl_easy_setopt (curl, CURLOPT_READFUNCTION, StaticCurlRead) != CURLE_OK) | |||||
| return false; | |||||
| if (curl_easy_setopt (curl, CURLOPT_POST, 1) != CURLE_OK | |||||
| || curl_easy_setopt (curl, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t> (postSize)) != CURLE_OK) | |||||
| return false; | |||||
| } | |||||
| // do we want to parse the headers | |||||
| if (wantsHeaders) | |||||
| { | |||||
| if (curl_easy_setopt (curl, CURLOPT_HEADERDATA, this) != CURLE_OK | |||||
| || curl_easy_setopt (curl, CURLOPT_HEADERFUNCTION, StaticCurlHeader) != CURLE_OK) | |||||
| return false; | |||||
| } | |||||
| if (headers.isNotEmpty()) | |||||
| { | |||||
| const StringArray headerLines = StringArray::fromLines (headers); | |||||
| // fromLines will always return at least one line if the string is not empty | |||||
| jassert (headerLines.size() > 0); | |||||
| headerList = curl_slist_append (headerList, headerLines [0].toRawUTF8()); | |||||
| for (int i = 1; (i < headerLines.size() && headerList != nullptr); ++i) | |||||
| headerList = curl_slist_append (headerList, headerLines [i].toRawUTF8()); | |||||
| if (headerList == nullptr) | |||||
| return false; | |||||
| if (curl_easy_setopt (curl, CURLOPT_HTTPHEADER, headerList) != CURLE_OK) | |||||
| return false; | |||||
| } | |||||
| if (timeOutMs > 0) | |||||
| { | |||||
| long timeOutSecs = static_cast<long> (ceil (static_cast<double> (timeOutMs) / 1000.0)); | |||||
| if (curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, timeOutSecs) != CURLE_OK) | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| void connect (StringPairArray* responseHeaders, bool isPost, const MemoryBlock& postData, | |||||
| URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext) | |||||
| { | |||||
| if (isPost) | |||||
| postBuffer = &postData; | |||||
| size_t lastPos = static_cast<size_t> (-1); | |||||
| // step until either: 1) there is an error 2) the transaction is complete | |||||
| // or 3) data is in the in buffer | |||||
| while ((! finished) && curlBuffer.getSize() == 0 && curl != nullptr) | |||||
| { | |||||
| singleStep(); | |||||
| // call callbacks if this is a post request | |||||
| if (isPost && progressCallback != nullptr && lastPos != postPosition) | |||||
| { | |||||
| lastPos = postPosition; | |||||
| if (! progressCallback (progressCallbackContext, | |||||
| static_cast<int> (lastPos), | |||||
| static_cast<int> (postData.getSize()))) | |||||
| { | |||||
| // user has decided to abort the transaction | |||||
| cleanup(); | |||||
| return; | |||||
| } | |||||
| } | |||||
| } | |||||
| long responseCode; | |||||
| if (curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &responseCode) == CURLE_OK) | |||||
| statusCode = static_cast<int> (responseCode); | |||||
| // parse headers | |||||
| if (responseHeaders != nullptr) | |||||
| parseHttpHeaders (*responseHeaders); | |||||
| // get content length size | |||||
| double curlLength; | |||||
| if (curl_easy_getinfo (curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &curlLength) == CURLE_OK) | |||||
| contentLength = static_cast<int64> (curlLength); | |||||
| } | |||||
| void finish() | |||||
| { | |||||
| if (curl == nullptr) | |||||
| return; | |||||
| for (;;) | |||||
| { | |||||
| int cnt = 0; | |||||
| if (CURLMsg* msg = curl_multi_info_read (multi, &cnt)) | |||||
| { | |||||
| if (msg->msg == CURLMSG_DONE && msg->easy_handle == curl) | |||||
| { | |||||
| lastError = msg->data.result; // this is the error that stopped our process from continuing | |||||
| break; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| break; | |||||
| } | |||||
| } | |||||
| finished = true; | |||||
| } | |||||
| //============================================================================== | |||||
| void singleStep() | |||||
| { | |||||
| if (curl == nullptr || lastError != CURLE_OK) | |||||
| return; | |||||
| fd_set fdread, fdwrite, fdexcep; | |||||
| int maxfd = -1; | |||||
| long curl_timeo; | |||||
| if ((lastError = (int) curl_multi_timeout (multi, &curl_timeo)) != CURLM_OK) | |||||
| return; | |||||
| // why 980? see http://curl.haxx.se/libcurl/c/curl_multi_timeout.html | |||||
| if (curl_timeo < 0) | |||||
| curl_timeo = 980; | |||||
| struct timeval tv; | |||||
| tv.tv_sec = curl_timeo / 1000; | |||||
| tv.tv_usec = (curl_timeo % 1000) * 1000; | |||||
| FD_ZERO (&fdread); | |||||
| FD_ZERO (&fdwrite); | |||||
| FD_ZERO (&fdexcep); | |||||
| if ((lastError = (int) curl_multi_fdset (multi, &fdread, &fdwrite, &fdexcep, &maxfd)) != CURLM_OK) | |||||
| return; | |||||
| if (maxfd != -1) | |||||
| { | |||||
| if (select (maxfd + 1, &fdread, &fdwrite, &fdexcep, &tv) < 0) | |||||
| { | |||||
| lastError = -1; | |||||
| return; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| // if curl does not return any sockets for to wait on, then the doc says to wait 100 ms | |||||
| Thread::sleep (100); | |||||
| } | |||||
| int still_running = 0; | |||||
| int curlRet; | |||||
| while ((curlRet = (int) curl_multi_perform (multi, &still_running)) == CURLM_CALL_MULTI_PERFORM) | |||||
| {} | |||||
| if ((lastError = curlRet) != CURLM_OK) | |||||
| return; | |||||
| if (still_running <= 0) | |||||
| finish(); | |||||
| } | |||||
| int readOrSkip (void* buffer, int bytesToRead, bool skip) | |||||
| { | |||||
| if (bytesToRead <= 0) | |||||
| return 0; | |||||
| size_t pos = 0; | |||||
| size_t len = static_cast<size_t> (bytesToRead); | |||||
| while (len > 0) | |||||
| { | |||||
| size_t bufferBytes = curlBuffer.getSize(); | |||||
| bool removeSection = true; | |||||
| if (bufferBytes == 0) | |||||
| { | |||||
| // do not call curl again if we are finished | |||||
| if (finished || curl == nullptr) | |||||
| return static_cast<int> (pos); | |||||
| skipBytes = skip ? len : 0; | |||||
| singleStep(); | |||||
| // update the amount that was read/skipped from curl | |||||
| bufferBytes = skip ? len - skipBytes : curlBuffer.getSize(); | |||||
| removeSection = ! skip; | |||||
| } | |||||
| // can we copy data from the internal buffer? | |||||
| if (bufferBytes > 0) | |||||
| { | |||||
| size_t max = jmin (len, bufferBytes); | |||||
| if (! skip) | |||||
| memcpy (addBytesToPointer (buffer, pos), curlBuffer.getData(), max); | |||||
| pos += max; | |||||
| streamPos += static_cast<int64> (max); | |||||
| len -= max; | |||||
| if (removeSection) | |||||
| curlBuffer.removeSection (0, max); | |||||
| } | |||||
| } | |||||
| return static_cast<int> (pos); | |||||
| } | |||||
| //============================================================================== | |||||
| void parseHttpHeaders (StringPairArray& responseHeaders) | |||||
| { | |||||
| StringArray headerLines = StringArray::fromLines (curlHeaders); | |||||
| // ignore the first line as this is the status line | |||||
| for (int i = 1; i < headerLines.size(); ++i) | |||||
| { | |||||
| const String& headersEntry = headerLines[i]; | |||||
| if (headersEntry.isNotEmpty()) | |||||
| { | |||||
| const String key (headersEntry.upToFirstOccurrenceOf (": ", false, false)); | |||||
| const String value (headersEntry.fromFirstOccurrenceOf (": ", false, false)); | |||||
| const String previousValue (responseHeaders [key]); | |||||
| responseHeaders.set (key, previousValue.isEmpty() ? value : (previousValue + "," + value)); | |||||
| } | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| // CURL callbacks | |||||
| size_t curlWriteCallback (char* ptr, size_t size, size_t nmemb) | |||||
| { | |||||
| if (curl == nullptr || lastError != CURLE_OK) | |||||
| return 0; | |||||
| const size_t len = size * nmemb; | |||||
| // skip bytes if necessary | |||||
| size_t max = jmin (skipBytes, len); | |||||
| skipBytes -= max; | |||||
| if (len > max) | |||||
| curlBuffer.append (ptr + max, len - max); | |||||
| return len; | |||||
| } | |||||
| size_t curlReadCallback (char* ptr, size_t size, size_t nmemb) | |||||
| { | |||||
| if (curl == nullptr || postBuffer == nullptr || lastError != CURLE_OK) | |||||
| return 0; | |||||
| const size_t len = size * nmemb; | |||||
| size_t max = jmin (postBuffer->getSize() - postPosition, len); | |||||
| memcpy (ptr, (char*)postBuffer->getData() + postPosition, max); | |||||
| postPosition += max; | |||||
| return max; | |||||
| } | |||||
| size_t curlHeaderCallback (char* ptr, size_t size, size_t nmemb) | |||||
| { | |||||
| if (curl == nullptr || lastError != CURLE_OK) | |||||
| return 0; | |||||
| size_t len = size * nmemb; | |||||
| curlHeaders += String (ptr, len); | |||||
| return len; | |||||
| } | |||||
| //============================================================================== | |||||
| // Static method wrappers | |||||
| static size_t StaticCurlWrite (char* ptr, size_t size, size_t nmemb, void* userdata) | |||||
| { | |||||
| WebInputStream* wi = reinterpret_cast<WebInputStream*> (userdata); | |||||
| return wi->curlWriteCallback (ptr, size, nmemb); | |||||
| } | |||||
| static size_t StaticCurlRead (char* ptr, size_t size, size_t nmemb, void* userdata) | |||||
| { | |||||
| WebInputStream* wi = reinterpret_cast<WebInputStream*> (userdata); | |||||
| return wi->curlReadCallback (ptr, size, nmemb); | |||||
| } | |||||
| static size_t StaticCurlHeader (char* ptr, size_t size, size_t nmemb, void* userdata) | |||||
| { | |||||
| WebInputStream* wi = reinterpret_cast<WebInputStream*> (userdata); | |||||
| return wi->curlHeaderCallback (ptr, size, nmemb); | |||||
| } | |||||
| private: | |||||
| CURLM* multi; | |||||
| CURL* curl; | |||||
| struct curl_slist* headerList; | |||||
| int lastError; | |||||
| //============================================================================== | |||||
| // internal buffers and buffer positions | |||||
| int64 contentLength, streamPos; | |||||
| MemoryBlock curlBuffer; | |||||
| String curlHeaders; | |||||
| bool finished; | |||||
| size_t skipBytes; | |||||
| //============================================================================== | |||||
| // Http POST variables | |||||
| const MemoryBlock* postBuffer; | |||||
| size_t postPosition; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream) | |||||
| }; | |||||
| @@ -67,8 +67,8 @@ bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& /* targetEma | |||||
| return false; | return false; | ||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| #if ! JUCE_USE_CURL | |||||
| class WebInputStream : public InputStream | class WebInputStream : public InputStream | ||||
| { | { | ||||
| public: | public: | ||||
| @@ -77,8 +77,9 @@ public: | |||||
| const String& headers_, int timeOutMs_, StringPairArray* responseHeaders, | const String& headers_, int timeOutMs_, StringPairArray* responseHeaders, | ||||
| const int maxRedirects) | const int maxRedirects) | ||||
| : statusCode (0), socketHandle (-1), levelsOfRedirection (0), | : statusCode (0), socketHandle (-1), levelsOfRedirection (0), | ||||
| address (address_), headers (headers_), postData (postData_), position (0), | |||||
| finished (false), isPost (isPost_), timeOutMs (timeOutMs_), numRedirectsToFollow (maxRedirects) | |||||
| address (address_), headers (headers_), postData (postData_), contentLength (-1), position (0), | |||||
| finished (false), isPost (isPost_), timeOutMs (timeOutMs_), numRedirectsToFollow (maxRedirects), | |||||
| chunkEnd (0), isChunked (false), readingChunk (false) | |||||
| { | { | ||||
| statusCode = createConnection (progressCallback, progressCallbackContext, numRedirectsToFollow); | statusCode = createConnection (progressCallback, progressCallbackContext, numRedirectsToFollow); | ||||
| @@ -104,18 +105,63 @@ public: | |||||
| bool isError() const { return socketHandle < 0; } | bool isError() const { return socketHandle < 0; } | ||||
| bool isExhausted() override { return finished; } | bool isExhausted() override { return finished; } | ||||
| int64 getPosition() override { return position; } | int64 getPosition() override { return position; } | ||||
| int64 getTotalLength() override | |||||
| { | |||||
| //xxx to do | |||||
| return -1; | |||||
| } | |||||
| int64 getTotalLength() override { return contentLength; } | |||||
| int read (void* buffer, int bytesToRead) override | int read (void* buffer, int bytesToRead) override | ||||
| { | { | ||||
| if (finished || isError()) | if (finished || isError()) | ||||
| return 0; | return 0; | ||||
| if (isChunked && ! readingChunk) | |||||
| { | |||||
| if (position >= chunkEnd) | |||||
| { | |||||
| const ScopedValueSetter<bool> setter (readingChunk, true, false); | |||||
| MemoryOutputStream chunkLengthBuffer; | |||||
| char c = 0; | |||||
| if (chunkEnd > 0) | |||||
| { | |||||
| if (read (&c, 1) != 1 || c != '\r' | |||||
| || read (&c, 1) != 1 || c != '\n') | |||||
| { | |||||
| finished = true; | |||||
| return 0; | |||||
| } | |||||
| } | |||||
| while (chunkLengthBuffer.getDataSize() < 512 && ! (finished || isError())) | |||||
| { | |||||
| if (read (&c, 1) != 1) | |||||
| { | |||||
| finished = true; | |||||
| return 0; | |||||
| } | |||||
| if (c == '\r') | |||||
| continue; | |||||
| if (c == '\n') | |||||
| break; | |||||
| chunkLengthBuffer.writeByte (c); | |||||
| } | |||||
| const int64 chunkSize = chunkLengthBuffer.toString().trimStart().getHexValue64(); | |||||
| if (chunkSize == 0) | |||||
| { | |||||
| finished = true; | |||||
| return 0; | |||||
| } | |||||
| chunkEnd += chunkSize; | |||||
| } | |||||
| if (bytesToRead > chunkEnd - position) | |||||
| bytesToRead = chunkEnd - position; | |||||
| } | |||||
| fd_set readbits; | fd_set readbits; | ||||
| FD_ZERO (&readbits); | FD_ZERO (&readbits); | ||||
| FD_SET (socketHandle, &readbits); | FD_SET (socketHandle, &readbits); | ||||
| @@ -131,7 +177,9 @@ public: | |||||
| if (bytesRead == 0) | if (bytesRead == 0) | ||||
| finished = true; | finished = true; | ||||
| position += bytesRead; | |||||
| if (! readingChunk) | |||||
| position += bytesRead; | |||||
| return bytesRead; | return bytesRead; | ||||
| } | } | ||||
| @@ -165,11 +213,13 @@ private: | |||||
| StringArray headerLines; | StringArray headerLines; | ||||
| String address, headers; | String address, headers; | ||||
| MemoryBlock postData; | MemoryBlock postData; | ||||
| int64 position; | |||||
| int64 contentLength, position; | |||||
| bool finished; | bool finished; | ||||
| const bool isPost; | const bool isPost; | ||||
| const int timeOutMs; | const int timeOutMs; | ||||
| const int numRedirectsToFollow; | const int numRedirectsToFollow; | ||||
| int64 chunkEnd; | |||||
| bool isChunked, readingChunk; | |||||
| void closeSocket (bool resetLevelsOfRedirection = true) | void closeSocket (bool resetLevelsOfRedirection = true) | ||||
| { | { | ||||
| @@ -298,6 +348,13 @@ private: | |||||
| return createConnection (progressCallback, progressCallbackContext, numRedirects); | return createConnection (progressCallback, progressCallbackContext, numRedirects); | ||||
| } | } | ||||
| String contentLengthString (findHeaderItem (headerLines, "Content-Length:")); | |||||
| if (contentLengthString.isNotEmpty()) | |||||
| contentLength = contentLengthString.getLargeIntValue(); | |||||
| isChunked = (findHeaderItem (headerLines, "Transfer-Encoding:") == "chunked"); | |||||
| return status; | return status; | ||||
| } | } | ||||
| @@ -345,7 +402,7 @@ private: | |||||
| static void writeHost (MemoryOutputStream& dest, const bool isPost, | static void writeHost (MemoryOutputStream& dest, const bool isPost, | ||||
| const String& path, const String& host, int port) | const String& path, const String& host, int port) | ||||
| { | { | ||||
| dest << (isPost ? "POST " : "GET ") << path << " HTTP/1.0\r\nHost: " << host; | |||||
| dest << (isPost ? "POST " : "GET ") << path << " HTTP/1.1\r\nHost: " << host; | |||||
| /* HTTP spec 14.23 says that the port number must be included in the header if it is not 80 */ | /* HTTP spec 14.23 says that the port number must be included in the header if it is not 80 */ | ||||
| if (port != 80) | if (port != 80) | ||||
| @@ -455,3 +512,4 @@ private: | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream) | ||||
| }; | }; | ||||
| #endif | |||||
| @@ -987,7 +987,7 @@ void JUCE_CALLTYPE Thread::setCurrentThreadAffinityMask (const uint32 affinityMa | |||||
| if ((affinityMask & (1 << i)) != 0) | if ((affinityMask & (1 << i)) != 0) | ||||
| CPU_SET (i, &affinity); | CPU_SET (i, &affinity); | ||||
| #if (! JUCE_LINUX) || ((__GLIBC__ * 1000 + __GLIBC_MINOR__) >= 2004) | |||||
| #if (! JUCE_ANDROID) && ((! JUCE_LINUX) || ((__GLIBC__ * 1000 + __GLIBC_MINOR__) >= 2004)) | |||||
| pthread_setaffinity_np (pthread_self(), sizeof (cpu_set_t), &affinity); | pthread_setaffinity_np (pthread_self(), sizeof (cpu_set_t), &affinity); | ||||
| #else | #else | ||||
| // NB: this call isn't really correct because it sets the affinity of the process, | // NB: this call isn't really correct because it sets the affinity of the process, | ||||
| @@ -795,18 +795,19 @@ public: | |||||
| connected (false), ownsPipe (createPipe), shouldStop (false) | connected (false), ownsPipe (createPipe), shouldStop (false) | ||||
| { | { | ||||
| if (createPipe) | if (createPipe) | ||||
| { | |||||
| pipeH = CreateNamedPipe (filename.toWideCharPointer(), | pipeH = CreateNamedPipe (filename.toWideCharPointer(), | ||||
| PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, 0, | PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, 0, | ||||
| PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, 0); | PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, 0); | ||||
| if (GetLastError() == ERROR_ALREADY_EXISTS) | |||||
| closePipeHandle(); | |||||
| } | |||||
| } | } | ||||
| ~Pimpl() | ~Pimpl() | ||||
| { | { | ||||
| disconnectPipe(); | |||||
| if (pipeH != INVALID_HANDLE_VALUE) | |||||
| CloseHandle (pipeH); | |||||
| closePipeHandle(); | |||||
| CloseHandle (cancelEvent); | CloseHandle (cancelEvent); | ||||
| } | } | ||||
| @@ -868,6 +869,16 @@ public: | |||||
| } | } | ||||
| } | } | ||||
| void closePipeHandle() | |||||
| { | |||||
| if (pipeH != INVALID_HANDLE_VALUE) | |||||
| { | |||||
| disconnectPipe(); | |||||
| CloseHandle (pipeH); | |||||
| pipeH = INVALID_HANDLE_VALUE; | |||||
| } | |||||
| } | |||||
| int read (void* destBuffer, const int maxBytesToRead, const int timeOutMilliseconds) | int read (void* destBuffer, const int maxBytesToRead, const int timeOutMilliseconds) | ||||
| { | { | ||||
| while (connect (timeOutMilliseconds)) | while (connect (timeOutMilliseconds)) | ||||
| @@ -113,11 +113,11 @@ | |||||
| #if _MSC_VER >= 1800 | #if _MSC_VER >= 1800 | ||||
| #define JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS 1 | #define JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS 1 | ||||
| #define JUCE_COMPILER_SUPPORTS_VARIADIC_TEMPLATES 1 | #define JUCE_COMPILER_SUPPORTS_VARIADIC_TEMPLATES 1 | ||||
| #define JUCE_DELETED_FUNCTION = delete | |||||
| #endif | #endif | ||||
| #if _MSC_VER >= 1900 | #if _MSC_VER >= 1900 | ||||
| #define JUCE_COMPILER_SUPPORTS_NOEXCEPT 1 | #define JUCE_COMPILER_SUPPORTS_NOEXCEPT 1 | ||||
| #define JUCE_DELETED_FUNCTION = delete | |||||
| #endif | #endif | ||||
| #endif | #endif | ||||
| @@ -63,8 +63,8 @@ | |||||
| //============================================================================== | //============================================================================== | ||||
| #if JUCE_IOS || JUCE_LINUX || JUCE_ANDROID || JUCE_PPC | #if JUCE_IOS || JUCE_LINUX || JUCE_ANDROID || JUCE_PPC | ||||
| /** This will try to break into the debugger if the app is currently being debugged. | /** This will try to break into the debugger if the app is currently being debugged. | ||||
| If called by an app that's not being debugged, the behaiour isn't defined - it may crash or not, depending | |||||
| on the platform. | |||||
| If called by an app that's not being debugged, the behaviour isn't defined - it may | |||||
| crash or not, depending on the platform. | |||||
| @see jassert() | @see jassert() | ||||
| */ | */ | ||||
| #define juce_breakDebugger { ::kill (0, SIGTRAP); } | #define juce_breakDebugger { ::kill (0, SIGTRAP); } | ||||
| @@ -328,7 +328,7 @@ void ZipFile::sortEntriesByFilename() | |||||
| //============================================================================== | //============================================================================== | ||||
| void ZipFile::init() | void ZipFile::init() | ||||
| { | { | ||||
| ScopedPointer <InputStream> toDelete; | |||||
| ScopedPointer<InputStream> toDelete; | |||||
| InputStream* in = inputStream; | InputStream* in = inputStream; | ||||
| if (inputSource != nullptr) | if (inputSource != nullptr) | ||||
| @@ -358,7 +358,7 @@ void ZipFile::init() | |||||
| if (pos + 46 > size) | if (pos + 46 > size) | ||||
| break; | break; | ||||
| const char* const buffer = static_cast <const char*> (headerData.getData()) + pos; | |||||
| const char* const buffer = static_cast<const char*> (headerData.getData()) + pos; | |||||
| const int fileNameLen = ByteOrder::littleEndianShort (buffer + 28); | const int fileNameLen = ByteOrder::littleEndianShort (buffer + 28); | ||||
| @@ -40,7 +40,7 @@ | |||||
| class JUCE_API ZipFile | class JUCE_API ZipFile | ||||
| { | { | ||||
| public: | public: | ||||
| /** Creates a ZipFile based for a file. */ | |||||
| /** Creates a ZipFile to read a specific file. */ | |||||
| explicit ZipFile (const File& file); | explicit ZipFile (const File& file); | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -91,15 +91,12 @@ public: | |||||
| int getNumEntries() const noexcept; | int getNumEntries() const noexcept; | ||||
| /** Returns a structure that describes one of the entries in the zip file. | /** Returns a structure that describes one of the entries in the zip file. | ||||
| This may return zero if the index is out of range. | This may return zero if the index is out of range. | ||||
| @see ZipFile::ZipEntry | @see ZipFile::ZipEntry | ||||
| */ | */ | ||||
| const ZipEntry* getEntry (int index) const noexcept; | const ZipEntry* getEntry (int index) const noexcept; | ||||
| /** Returns the index of the first entry with a given filename. | /** Returns the index of the first entry with a given filename. | ||||
| This uses a case-sensitive comparison to look for a filename in the | This uses a case-sensitive comparison to look for a filename in the | ||||
| list of entries. It might return -1 if no match is found. | list of entries. It might return -1 if no match is found. | ||||
| @@ -116,8 +113,7 @@ public: | |||||
| */ | */ | ||||
| const ZipEntry* getEntry (const String& fileName) const noexcept; | const ZipEntry* getEntry (const String& fileName) const noexcept; | ||||
| /** Sorts the list of entries, based on the filename. | |||||
| */ | |||||
| /** Sorts the list of entries, based on the filename. */ | |||||
| void sortEntriesByFilename(); | void sortEntriesByFilename(); | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -128,6 +124,11 @@ public: | |||||
| The stream must not be used after the ZipFile object that created | The stream must not be used after the ZipFile object that created | ||||
| has been deleted. | has been deleted. | ||||
| Note that if the ZipFile was created with a user-supplied InputStream object, | |||||
| then all the streams which are created by this method will by trying to share | |||||
| the same source stream, so cannot be safely used on multiple threads! (But if | |||||
| you create the ZipFile from a File or InputSource, then it is safe to do this). | |||||
| */ | */ | ||||
| InputStream* createStreamForEntry (int index); | InputStream* createStreamForEntry (int index); | ||||
| @@ -138,6 +139,11 @@ public: | |||||
| The stream must not be used after the ZipFile object that created | The stream must not be used after the ZipFile object that created | ||||
| has been deleted. | has been deleted. | ||||
| Note that if the ZipFile was created with a user-supplied InputStream object, | |||||
| then all the streams which are created by this method will by trying to share | |||||
| the same source stream, so cannot be safely used on multiple threads! (But if | |||||
| you create the ZipFile from a File or InputSource, then it is safe to do this). | |||||
| */ | */ | ||||
| InputStream* createStreamForEntry (const ZipEntry& entry); | InputStream* createStreamForEntry (const ZipEntry& entry); | ||||
| @@ -194,7 +200,7 @@ public: | |||||
| will be stored for this file. | will be stored for this file. | ||||
| */ | */ | ||||
| void addFile (const File& fileToAdd, int compressionLevel, | void addFile (const File& fileToAdd, int compressionLevel, | ||||
| const String& storedPathName = String::empty); | |||||
| const String& storedPathName = String()); | |||||
| /** Adds a file while should be added to the archive. | /** Adds a file while should be added to the archive. | ||||
| @@ -233,11 +239,11 @@ private: | |||||
| friend class ZipInputStream; | friend class ZipInputStream; | ||||
| friend class ZipEntryHolder; | friend class ZipEntryHolder; | ||||
| OwnedArray <ZipEntryHolder> entries; | |||||
| OwnedArray<ZipEntryHolder> entries; | |||||
| CriticalSection lock; | CriticalSection lock; | ||||
| InputStream* inputStream; | InputStream* inputStream; | ||||
| ScopedPointer <InputStream> streamToDelete; | |||||
| ScopedPointer <InputSource> inputSource; | |||||
| ScopedPointer<InputStream> streamToDelete; | |||||
| ScopedPointer<InputSource> inputSource; | |||||
| #if JUCE_DEBUG | #if JUCE_DEBUG | ||||
| struct OpenStreamCounter | struct OpenStreamCounter | ||||
| @@ -279,8 +279,12 @@ public: | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Makes the font bold or non-bold. */ | /** Makes the font bold or non-bold. */ | ||||
| void setBold (bool shouldBeBold); | void setBold (bool shouldBeBold); | ||||
| /** Returns a copy of this font with the bold attribute set. */ | |||||
| /** 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; | Font boldened() const; | ||||
| /** Returns true if the font is bold. */ | /** Returns true if the font is bold. */ | ||||
| bool isBold() const noexcept; | bool isBold() const noexcept; | ||||
| @@ -111,11 +111,13 @@ public: | |||||
| static JUCEApplication* JUCE_CALLTYPE getInstance() noexcept; | static JUCEApplication* JUCE_CALLTYPE getInstance() noexcept; | ||||
| //============================================================================== | //============================================================================== | ||||
| #if DOXYGEN | |||||
| /** Returns the application's name. */ | /** Returns the application's name. */ | ||||
| virtual const String getApplicationName() = 0; | virtual const String getApplicationName() = 0; | ||||
| /** Returns the application's version number. */ | /** Returns the application's version number. */ | ||||
| virtual const String getApplicationVersion() = 0; | virtual const String getApplicationVersion() = 0; | ||||
| #endif | |||||
| /** Checks whether multiple instances of the app are allowed. | /** Checks whether multiple instances of the app are allowed. | ||||
| @@ -443,7 +443,7 @@ public: | |||||
| /** Changes the component's position and size. | /** Changes the component's position and size. | ||||
| The coordinates are relative to the top-left of the component's parent, or relative | The coordinates are relative to the top-left of the component's parent, or relative | ||||
| to the origin of the screen is the component is on the desktop. | |||||
| to the origin of the screen if the component is on the desktop. | |||||
| If this method changes the component's top-left position, it will make a synchronous | If this method changes the component's top-left position, it will make a synchronous | ||||
| call to moved(). If it changes the size, it will also make a call to resized(). | call to moved(). If it changes the size, it will also make a call to resized(). | ||||
| @@ -459,7 +459,7 @@ public: | |||||
| /** Changes the component's position and size. | /** Changes the component's position and size. | ||||
| The coordinates are relative to the top-left of the component's parent, or relative | The coordinates are relative to the top-left of the component's parent, or relative | ||||
| to the origin of the screen is the component is on the desktop. | |||||
| to the origin of the screen if the component is on the desktop. | |||||
| If this method changes the component's top-left position, it will make a synchronous | If this method changes the component's top-left position, it will make a synchronous | ||||
| call to moved(). If it changes the size, it will also make a call to resized(). | call to moved(). If it changes the size, it will also make a call to resized(). | ||||
| @@ -333,7 +333,6 @@ bool operator!= (const Desktop::Displays::Display& d1, const Desktop::Displays:: | |||||
| void Desktop::Displays::init (Desktop& desktop) | void Desktop::Displays::init (Desktop& desktop) | ||||
| { | { | ||||
| findDisplays (desktop.getGlobalScaleFactor()); | findDisplays (desktop.getGlobalScaleFactor()); | ||||
| jassert (displays.size() > 0); | |||||
| } | } | ||||
| void Desktop::Displays::refresh() | void Desktop::Displays::refresh() | ||||
| @@ -342,7 +341,6 @@ void Desktop::Displays::refresh() | |||||
| oldDisplays.swapWith (displays); | oldDisplays.swapWith (displays); | ||||
| init (Desktop::getInstance()); | init (Desktop::getInstance()); | ||||
| jassert (displays.size() > 0); | |||||
| if (oldDisplays != displays) | if (oldDisplays != displays) | ||||
| { | { | ||||
| @@ -272,7 +272,7 @@ public: | |||||
| if (parseNextNumber (d, num, false)) | if (parseNextNumber (d, num, false)) | ||||
| { | { | ||||
| const float angle = num.getFloatValue() * (180.0f / float_Pi); | |||||
| const float angle = degreesToRadians (num.getFloatValue()); | |||||
| if (parseNextNumber (d, num, false)) | if (parseNextNumber (d, num, false)) | ||||
| { | { | ||||
| @@ -1221,15 +1221,15 @@ private: | |||||
| } | } | ||||
| else if (t.startsWithIgnoreCase ("rotate")) | else if (t.startsWithIgnoreCase ("rotate")) | ||||
| { | { | ||||
| trans = AffineTransform::rotation (numbers[0] / (180.0f / float_Pi), numbers[1], numbers[2]); | |||||
| trans = AffineTransform::rotation (degreesToRadians (numbers[0]), numbers[1], numbers[2]); | |||||
| } | } | ||||
| else if (t.startsWithIgnoreCase ("skewX")) | else if (t.startsWithIgnoreCase ("skewX")) | ||||
| { | { | ||||
| trans = AffineTransform::shear (std::tan (numbers[0] * (float_Pi / 180.0f)), 0.0f); | |||||
| trans = AffineTransform::shear (std::tan (degreesToRadians (numbers[0])), 0.0f); | |||||
| } | } | ||||
| else if (t.startsWithIgnoreCase ("skewY")) | else if (t.startsWithIgnoreCase ("skewY")) | ||||
| { | { | ||||
| trans = AffineTransform::shear (0.0f, std::tan (numbers[0] * (float_Pi / 180.0f))); | |||||
| trans = AffineTransform::shear (0.0f, std::tan (degreesToRadians (numbers[0]))); | |||||
| } | } | ||||
| result = trans.followedBy (result); | result = trans.followedBy (result); | ||||
| @@ -85,12 +85,6 @@ struct TextEditorKeyMapper | |||||
| if (key.isKeyCode (KeyPress::pageDownKey)) return target.pageDown (isShiftDown); | if (key.isKeyCode (KeyPress::pageDownKey)) return target.pageDown (isShiftDown); | ||||
| } | } | ||||
| if (numCtrlAltCommandKeys < 2) | |||||
| { | |||||
| if (key.isKeyCode (KeyPress::backspaceKey)) return target.deleteBackwards (ctrlOrAltDown); | |||||
| if (key.isKeyCode (KeyPress::deleteKey)) return target.deleteForwards (ctrlOrAltDown); | |||||
| } | |||||
| if (key == KeyPress ('c', ModifierKeys::commandModifier, 0) | if (key == KeyPress ('c', ModifierKeys::commandModifier, 0) | ||||
| || key == KeyPress (KeyPress::insertKey, ModifierKeys::ctrlModifier, 0)) | || key == KeyPress (KeyPress::insertKey, ModifierKeys::ctrlModifier, 0)) | ||||
| return target.copyToClipboard(); | return target.copyToClipboard(); | ||||
| @@ -103,6 +97,13 @@ struct TextEditorKeyMapper | |||||
| || key == KeyPress (KeyPress::insertKey, ModifierKeys::shiftModifier, 0)) | || key == KeyPress (KeyPress::insertKey, ModifierKeys::shiftModifier, 0)) | ||||
| return target.pasteFromClipboard(); | return target.pasteFromClipboard(); | ||||
| // NB: checking for delete must happen after the earlier check for shift + delete | |||||
| if (numCtrlAltCommandKeys < 2) | |||||
| { | |||||
| if (key.isKeyCode (KeyPress::backspaceKey)) return target.deleteBackwards (ctrlOrAltDown); | |||||
| if (key.isKeyCode (KeyPress::deleteKey)) return target.deleteForwards (ctrlOrAltDown); | |||||
| } | |||||
| if (key == KeyPress ('a', ModifierKeys::commandModifier, 0)) | if (key == KeyPress ('a', ModifierKeys::commandModifier, 0)) | ||||
| return target.selectAll(); | return target.selectAll(); | ||||
| @@ -490,6 +490,12 @@ int LookAndFeel_V2::getAlertWindowButtonHeight() | |||||
| return 28; | return 28; | ||||
| } | } | ||||
| Font LookAndFeel_V2::getAlertWindowTitleFont() | |||||
| { | |||||
| Font messageFont = getAlertWindowMessageFont(); | |||||
| return messageFont.withHeight (messageFont.getHeight() * 1.1f).boldened(); | |||||
| } | |||||
| Font LookAndFeel_V2::getAlertWindowMessageFont() | Font LookAndFeel_V2::getAlertWindowMessageFont() | ||||
| { | { | ||||
| return Font (15.0f); | return Font (15.0f); | ||||
| @@ -67,7 +67,22 @@ public: | |||||
| void drawAlertBox (Graphics&, AlertWindow&, const Rectangle<int>& textArea, TextLayout&) override; | void drawAlertBox (Graphics&, AlertWindow&, const Rectangle<int>& textArea, TextLayout&) override; | ||||
| int getAlertBoxWindowFlags() override; | int getAlertBoxWindowFlags() override; | ||||
| int getAlertWindowButtonHeight() override; | int getAlertWindowButtonHeight() override; | ||||
| /** Override this function to supply a custom font for the alert window title. | |||||
| This default implementation will use a boldened and slightly larger version | |||||
| of the alert window message font. | |||||
| @see getAlertWindowMessageFont. | |||||
| */ | |||||
| Font getAlertWindowTitleFont() override; | |||||
| /** Override this function to supply a custom font for the alert window message. | |||||
| This default implementation will use the default font with height set to 15.0f. | |||||
| @see getAlertWindowTitleFont | |||||
| */ | |||||
| Font getAlertWindowMessageFont() override; | Font getAlertWindowMessageFont() override; | ||||
| Font getAlertWindowFont() override; | Font getAlertWindowFont() override; | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -1219,7 +1219,7 @@ public: | |||||
| getLookAndFeel().drawPopupMenuSectionHeader (g, getLocalBounds(), getName()); | getLookAndFeel().drawPopupMenuSectionHeader (g, getLocalBounds(), getName()); | ||||
| } | } | ||||
| void getIdealSize (int& idealWidth, int& idealHeight) | |||||
| void getIdealSize (int& idealWidth, int& idealHeight) override | |||||
| { | { | ||||
| getLookAndFeel().getIdealPopupMenuItemSize (getName(), false, -1, idealWidth, idealHeight); | getLookAndFeel().getIdealPopupMenuItemSize (getName(), false, -1, idealWidth, idealHeight); | ||||
| idealHeight += idealHeight / 2; | idealHeight += idealHeight / 2; | ||||
| @@ -1698,9 +1698,9 @@ void PopupMenu::CustomComponent::setHighlighted (bool shouldBeHighlighted) | |||||
| void PopupMenu::CustomComponent::triggerMenuItem() | void PopupMenu::CustomComponent::triggerMenuItem() | ||||
| { | { | ||||
| if (HelperClasses::ItemComponent* const mic = dynamic_cast<HelperClasses::ItemComponent*> (getParentComponent())) | |||||
| if (HelperClasses::ItemComponent* const mic = findParentComponentOfClass<HelperClasses::ItemComponent>()) | |||||
| { | { | ||||
| if (HelperClasses::MenuWindow* const pmw = dynamic_cast<HelperClasses::MenuWindow*> (mic->getParentComponent())) | |||||
| if (HelperClasses::MenuWindow* const pmw = mic->findParentComponentOfClass<HelperClasses::MenuWindow>()) | |||||
| { | { | ||||
| pmw->dismissMenu (&mic->itemInfo); | pmw->dismissMenu (&mic->itemInfo); | ||||
| } | } | ||||
| @@ -135,7 +135,7 @@ static void addZenityArgs (StringArray& args, String& separator, | |||||
| tokens.addTokens (filters, ";,|", "\""); | tokens.addTokens (filters, ";,|", "\""); | ||||
| for (int i = 0; i < tokens.size(); ++i) | for (int i = 0; i < tokens.size(); ++i) | ||||
| args.add ("--file-filter='" + tokens[i] + "'"); | |||||
| args.add ("--file-filter=" + tokens[i]); | |||||
| } | } | ||||
| if (file.isDirectory()) | if (file.isDirectory()) | ||||
| @@ -1881,6 +1881,9 @@ public: | |||||
| { | { | ||||
| if (LinuxComponentPeer* const otherPeer = dynamic_cast<LinuxComponentPeer*> (other)) | if (LinuxComponentPeer* const otherPeer = dynamic_cast<LinuxComponentPeer*> (other)) | ||||
| { | { | ||||
| if (otherPeer->styleFlags & windowIsTemporary) | |||||
| return; | |||||
| setMinimised (false); | setMinimised (false); | ||||
| Window newStack[] = { otherPeer->windowH, windowH }; | Window newStack[] = { otherPeer->windowH, windowH }; | ||||
| @@ -2320,6 +2323,10 @@ public: | |||||
| XEvent nextEvent; | XEvent nextEvent; | ||||
| ScopedXLock xlock; | ScopedXLock xlock; | ||||
| // if we have opengl contexts then just repaint them all | |||||
| // regardless if this is really necessary | |||||
| repaintOpenGLContexts (); | |||||
| if (exposeEvent.window != windowH) | if (exposeEvent.window != windowH) | ||||
| { | { | ||||
| Window child; | Window child; | ||||
| @@ -2509,6 +2516,28 @@ public: | |||||
| return currentScaleFactor; | return currentScaleFactor; | ||||
| } | } | ||||
| //=============================================================================== | |||||
| void addOpenGLRepaintListener (Component* dummy) | |||||
| { | |||||
| if (dummy != nullptr) | |||||
| glRepaintListeners.addIfNotAlreadyThere (dummy); | |||||
| } | |||||
| void removeOpenGLRepaintListener (Component* dummy) | |||||
| { | |||||
| if (dummy != nullptr) | |||||
| glRepaintListeners.removeAllInstancesOf (dummy); | |||||
| } | |||||
| void repaintOpenGLContexts() | |||||
| { | |||||
| for (int i = 0; i < glRepaintListeners.size(); ++i) | |||||
| { | |||||
| if (Component* c = glRepaintListeners [i]) | |||||
| c->handleCommandMessage (0); | |||||
| } | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| bool dontRepaint; | bool dontRepaint; | ||||
| @@ -2668,6 +2697,7 @@ private: | |||||
| BorderSize<int> windowBorder; | BorderSize<int> windowBorder; | ||||
| bool isAlwaysOnTop; | bool isAlwaysOnTop; | ||||
| double currentScaleFactor; | double currentScaleFactor; | ||||
| Array<Component*> glRepaintListeners; | |||||
| enum { KeyPressEventType = 2 }; | enum { KeyPressEventType = 2 }; | ||||
| struct MotifWmHints | struct MotifWmHints | ||||
| @@ -4125,6 +4155,18 @@ Rectangle<int> juce_LinuxScaledToPhysicalBounds(ComponentPeer* peer, const Recta | |||||
| return retval; | return retval; | ||||
| } | } | ||||
| void juce_LinuxAddRepaintListener (ComponentPeer* peer, Component* dummy) | |||||
| { | |||||
| if (LinuxComponentPeer* linuxPeer = dynamic_cast<LinuxComponentPeer*> (peer)) | |||||
| linuxPeer->addOpenGLRepaintListener (dummy); | |||||
| } | |||||
| void juce_LinuxRemoveRepaintListener (ComponentPeer* peer, Component* dummy) | |||||
| { | |||||
| if (LinuxComponentPeer* linuxPeer = dynamic_cast<LinuxComponentPeer*> (peer)) | |||||
| linuxPeer->removeOpenGLRepaintListener (dummy); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| #if JUCE_MODAL_LOOPS_PERMITTED | #if JUCE_MODAL_LOOPS_PERMITTED | ||||
| void JUCE_CALLTYPE NativeMessageBox::showMessageBox (AlertWindow::AlertIconType iconType, | void JUCE_CALLTYPE NativeMessageBox::showMessageBox (AlertWindow::AlertIconType iconType, | ||||
| @@ -218,8 +218,6 @@ void FileChooser::showPlatformDialog (Array<File>& results, const String& title_ | |||||
| info.customComponent->enterModalState(); | info.customComponent->enterModalState(); | ||||
| } | } | ||||
| const StringRef separatorTokens (";,|"); | |||||
| const size_t filterSpaceNumChars = 2048; | const size_t filterSpaceNumChars = 2048; | ||||
| HeapBlock<WCHAR> filters; | HeapBlock<WCHAR> filters; | ||||
| filters.calloc (filterSpaceNumChars); | filters.calloc (filterSpaceNumChars); | ||||
| @@ -228,7 +226,7 @@ void FileChooser::showPlatformDialog (Array<File>& results, const String& title_ | |||||
| ((filterSpaceNumChars - 1) * sizeof (WCHAR) - bytesWritten)); | ((filterSpaceNumChars - 1) * sizeof (WCHAR) - bytesWritten)); | ||||
| for (int i = 0; i < filterSpaceNumChars; ++i) | for (int i = 0; i < filterSpaceNumChars; ++i) | ||||
| if (separatorTokens.text.indexOf ((juce_wchar) filters[i]) >= 0) | |||||
| if (filters[i] == '|') | |||||
| filters[i] = 0; | filters[i] = 0; | ||||
| OPENFILENAMEW of = { 0 }; | OPENFILENAMEW of = { 0 }; | ||||
| @@ -255,7 +253,7 @@ void FileChooser::showPlatformDialog (Array<File>& results, const String& title_ | |||||
| if (isSaveDialogue) | if (isSaveDialogue) | ||||
| { | { | ||||
| StringArray tokens; | StringArray tokens; | ||||
| tokens.addTokens (filter, separatorTokens, "\"'"); | |||||
| tokens.addTokens (filter, ";,", "\"'"); | |||||
| tokens.trim(); | tokens.trim(); | ||||
| tokens.removeEmptyStrings(); | tokens.removeEmptyStrings(); | ||||
| @@ -56,6 +56,9 @@ public: | |||||
| The graphics context has its origin at the row's top-left, and your method | The graphics context has its origin at the row's top-left, and your method | ||||
| should fill the area specified by the width and height parameters. | should fill the area specified by the width and height parameters. | ||||
| Note that the rowNumber value may be greater than the number of rows in your | |||||
| list, so be careful that you don't assume it's less than getNumRows(). | |||||
| */ | */ | ||||
| virtual void paintRowBackground (Graphics&, | virtual void paintRowBackground (Graphics&, | ||||
| int rowNumber, | int rowNumber, | ||||
| @@ -343,22 +343,24 @@ void AlertWindow::updateLayout (const bool onlyIncreaseSize) | |||||
| const int titleH = 24; | const int titleH = 24; | ||||
| const int iconWidth = 80; | const int iconWidth = 80; | ||||
| const Font font (getLookAndFeel().getAlertWindowMessageFont()); | |||||
| LookAndFeel& lookAndFeel = getLookAndFeel(); | |||||
| const int wid = jmax (font.getStringWidth (text), | |||||
| font.getStringWidth (getName())); | |||||
| const Font messageFont (lookAndFeel.getAlertWindowMessageFont()); | |||||
| const int sw = (int) std::sqrt (font.getHeight() * wid); | |||||
| const int wid = jmax (messageFont.getStringWidth (text), | |||||
| messageFont.getStringWidth (getName())); | |||||
| const int sw = (int) std::sqrt (messageFont.getHeight() * wid); | |||||
| int w = jmin (300 + sw * 2, (int) (getParentWidth() * 0.7f)); | int w = jmin (300 + sw * 2, (int) (getParentWidth() * 0.7f)); | ||||
| const int edgeGap = 10; | const int edgeGap = 10; | ||||
| const int labelHeight = 18; | const int labelHeight = 18; | ||||
| int iconSpace = 0; | int iconSpace = 0; | ||||
| AttributedString attributedText; | AttributedString attributedText; | ||||
| attributedText.append (getName(), font.withHeight (font.getHeight() * 1.1f).boldened()); | |||||
| attributedText.append (getName(), lookAndFeel.getAlertWindowTitleFont()); | |||||
| if (text.isNotEmpty()) | if (text.isNotEmpty()) | ||||
| attributedText.append ("\n\n" + text, font); | |||||
| attributedText.append ("\n\n" + text, messageFont); | |||||
| attributedText.setColour (findColour (textColourId)); | attributedText.setColour (findColour (textColourId)); | ||||
| @@ -383,18 +385,18 @@ void AlertWindow::updateLayout (const bool onlyIncreaseSize) | |||||
| int buttonW = 40; | int buttonW = 40; | ||||
| for (int i = 0; i < buttons.size(); ++i) | for (int i = 0; i < buttons.size(); ++i) | ||||
| buttonW += 16 + buttons.getUnchecked(i)->getWidth(); | |||||
| buttonW += 16 + buttons.getUnchecked (i)->getWidth(); | |||||
| w = jmax (buttonW, w); | w = jmax (buttonW, w); | ||||
| h += (textBoxes.size() + comboBoxes.size() + progressBars.size()) * 50; | h += (textBoxes.size() + comboBoxes.size() + progressBars.size()) * 50; | ||||
| if (buttons.size() > 0) | if (buttons.size() > 0) | ||||
| h += 20 + buttons.getUnchecked(0)->getHeight(); | |||||
| h += 20 + buttons.getUnchecked (0)->getHeight(); | |||||
| for (int i = customComps.size(); --i >= 0;) | for (int i = customComps.size(); --i >= 0;) | ||||
| { | { | ||||
| Component* c = customComps.getUnchecked(i); | |||||
| Component* c = customComps.getUnchecked (i); | |||||
| w = jmax (w, (c->getWidth() * 100) / 80); | w = jmax (w, (c->getWidth() * 100) / 80); | ||||
| h += 10 + c->getHeight(); | h += 10 + c->getHeight(); | ||||
| @@ -437,6 +437,7 @@ public: | |||||
| virtual int getAlertWindowButtonHeight() = 0; | virtual int getAlertWindowButtonHeight() = 0; | ||||
| virtual Font getAlertWindowTitleFont() = 0; | |||||
| virtual Font getAlertWindowMessageFont() = 0; | virtual Font getAlertWindowMessageFont() = 0; | ||||
| virtual Font getAlertWindowFont() = 0; | virtual Font getAlertWindowFont() = 0; | ||||
| }; | }; | ||||