| @@ -101,7 +101,20 @@ private: | |||
| struct TripleIndex | |||
| { | |||
| TripleIndex() noexcept : vertexIndex (-1), textureIndex (-1), normalIndex (-1) {} | |||
| bool operator< (const TripleIndex& other) const noexcept { return vertexIndex < other.vertexIndex; } | |||
| bool operator< (const TripleIndex& other) const noexcept | |||
| { | |||
| if (this == &other) | |||
| return false; | |||
| if (vertexIndex != other.vertexIndex) | |||
| return vertexIndex < other.vertexIndex; | |||
| if (textureIndex != other.textureIndex) | |||
| return textureIndex < other.textureIndex; | |||
| return normalIndex < other.normalIndex; | |||
| } | |||
| int vertexIndex, textureIndex, normalIndex; | |||
| }; | |||
| @@ -101,7 +101,20 @@ private: | |||
| struct TripleIndex | |||
| { | |||
| TripleIndex() noexcept : vertexIndex (-1), textureIndex (-1), normalIndex (-1) {} | |||
| bool operator< (const TripleIndex& other) const noexcept { return vertexIndex < other.vertexIndex; } | |||
| bool operator< (const TripleIndex& other) const noexcept | |||
| { | |||
| if (this == &other) | |||
| return false; | |||
| if (vertexIndex != other.vertexIndex) | |||
| return vertexIndex < other.vertexIndex; | |||
| if (textureIndex != other.textureIndex) | |||
| return textureIndex < other.textureIndex; | |||
| return normalIndex < other.normalIndex; | |||
| } | |||
| int vertexIndex, textureIndex, normalIndex; | |||
| }; | |||
| @@ -58,6 +58,10 @@ | |||
| //#define JUCE_INCLUDE_ZLIB_CODE | |||
| #endif | |||
| #ifndef JUCE_USE_CURL | |||
| //#define JUCE_USE_CURL | |||
| #endif | |||
| //============================================================================== | |||
| // juce_graphics flags: | |||
| @@ -184,6 +184,9 @@ private: | |||
| for (int i = 0; i < linuxLibs.size(); ++i) | |||
| out << " -l" << linuxLibs[i]; | |||
| if (getProject().isConfigFlagEnabled ("JUCE_USE_CURL")) | |||
| out << " -lcurl"; | |||
| StringArray libraries; | |||
| libraries.addTokens (getExternalLibrariesString(), ";", "\"'"); | |||
| libraries.removeEmptyStrings(); | |||
| @@ -654,6 +654,7 @@ private: | |||
| static const char* kDefaultiOSOrientationStrings[] = | |||
| { | |||
| "UIInterfaceOrientationPortrait", | |||
| "UIInterfaceOrientationPortraitUpsideDown", | |||
| "UIInterfaceOrientationLandscapeLeft", | |||
| "UIInterfaceOrientationLandscapeRight", | |||
| nullptr | |||
| @@ -918,9 +918,11 @@ struct AAXClasses | |||
| for (int parameterIndex = 0; parameterIndex < numParameters; ++parameterIndex) | |||
| { | |||
| AAX_CString paramName (audioProcessor.getParameterName (parameterIndex, 31).toRawUTF8()); | |||
| AAX_IParameter* parameter | |||
| = new AAX_CParameter<float> (IndexAsParamID (parameterIndex), | |||
| audioProcessor.getParameterName (parameterIndex, 31).toRawUTF8(), | |||
| paramName, | |||
| audioProcessor.getParameterDefaultValue (parameterIndex), | |||
| AAX_CLinearTaperDelegate<float, 0>(), | |||
| AAX_CNumberDisplayDelegate<float, 3>(), | |||
| @@ -47,6 +47,7 @@ public: | |||
| CakewalkSonarGeneric, | |||
| DigidesignProTools, | |||
| DigitalPerformer, | |||
| FinalCut, | |||
| FruityLoops, | |||
| MagixSamplitude, | |||
| MergingPyramix, | |||
| @@ -72,8 +73,8 @@ public: | |||
| StudioOne, | |||
| Tracktion3, | |||
| TracktionGeneric, | |||
| WaveBurner, | |||
| VBVSTScanner | |||
| VBVSTScanner, | |||
| WaveBurner | |||
| }; | |||
| HostType type; | |||
| @@ -87,6 +88,7 @@ public: | |||
| bool isCubase7orLater() const noexcept { return isCubase() && ! (type == SteinbergCubase4 || type == SteinbergCubase5 || type == SteinbergCubase6); } | |||
| bool isCubaseBridged() const noexcept { return type == SteinbergCubase5Bridged; } | |||
| bool isLogic() const noexcept { return type == AppleLogic; } | |||
| bool isFinalCut() const noexcept { return type == FinalCut; } | |||
| bool isFruityLoops() const noexcept { return type == FruityLoops; } | |||
| bool isNuendo() const noexcept { return type == SteinbergNuendo3 || type == SteinbergNuendo4 || type == SteinbergNuendo5 || type == SteinbergNuendoGeneric; } | |||
| bool isPremiere() const noexcept { return type == AdobePremierePro; } | |||
| @@ -120,6 +122,7 @@ public: | |||
| case CakewalkSonarGeneric: return "Cakewalk Sonar"; | |||
| case DigidesignProTools: return "ProTools"; | |||
| case DigitalPerformer: return "DigitalPerformer"; | |||
| case FinalCut: return "Final Cut"; | |||
| case FruityLoops: return "FruityLoops"; | |||
| case MagixSamplitude: return "Magix Samplitude"; | |||
| case MergingPyramix: return "Pyramix"; | |||
| @@ -167,6 +170,8 @@ private: | |||
| const String hostFilename (File (hostPath).getFileName()); | |||
| #if JUCE_MAC | |||
| if (hostPath.containsIgnoreCase ("Final Cut Pro.app")) return FinalCut; | |||
| if (hostPath.containsIgnoreCase ("Final Cut Pro Trial.app")) return FinalCut; | |||
| if (hostPath.containsIgnoreCase ("Live 6.")) return AbletonLive6; | |||
| if (hostPath.containsIgnoreCase ("Live 7.")) return AbletonLive7; | |||
| if (hostPath.containsIgnoreCase ("Live 8.")) return AbletonLive8; | |||
| @@ -977,7 +977,6 @@ bool AudioProcessorGraph::removeNode (const uint32 nodeId) | |||
| { | |||
| if (nodes.getUnchecked(i)->nodeId == nodeId) | |||
| { | |||
| nodes.getUnchecked(i)->setParentGraph (nullptr); | |||
| nodes.remove (i); | |||
| triggerAsyncUpdate(); | |||
| @@ -82,6 +82,10 @@ | |||
| #if JUCE_LINUX | |||
| #include <langinfo.h> | |||
| #include <ifaddrs.h> | |||
| #if JUCE_USE_CURL | |||
| #include <curl/curl.h> | |||
| #endif | |||
| #endif | |||
| #include <pwd.h> | |||
| @@ -210,6 +214,9 @@ namespace juce | |||
| #include "native/juce_linux_CommonFile.cpp" | |||
| #include "native/juce_linux_Files.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_Threads.cpp" | |||
| @@ -126,6 +126,17 @@ | |||
| #define JUCE_ZLIB_INCLUDE_PATH <zlib.h> | |||
| #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 | |||
| If enabled, this will add some exception-catching code to forward unhandled exceptions | |||
| to your JUCEApplicationBase::unhandledException() callback. | |||
| @@ -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; | |||
| } | |||
| //============================================================================== | |||
| #if ! JUCE_USE_CURL | |||
| class WebInputStream : public InputStream | |||
| { | |||
| public: | |||
| @@ -77,8 +77,9 @@ public: | |||
| const String& headers_, int timeOutMs_, StringPairArray* responseHeaders, | |||
| const int maxRedirects) | |||
| : 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); | |||
| @@ -104,18 +105,63 @@ public: | |||
| bool isError() const { return socketHandle < 0; } | |||
| bool isExhausted() override { return finished; } | |||
| 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 | |||
| { | |||
| if (finished || isError()) | |||
| 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_ZERO (&readbits); | |||
| FD_SET (socketHandle, &readbits); | |||
| @@ -131,7 +177,9 @@ public: | |||
| if (bytesRead == 0) | |||
| finished = true; | |||
| position += bytesRead; | |||
| if (! readingChunk) | |||
| position += bytesRead; | |||
| return bytesRead; | |||
| } | |||
| @@ -165,11 +213,13 @@ private: | |||
| StringArray headerLines; | |||
| String address, headers; | |||
| MemoryBlock postData; | |||
| int64 position; | |||
| int64 contentLength, position; | |||
| bool finished; | |||
| const bool isPost; | |||
| const int timeOutMs; | |||
| const int numRedirectsToFollow; | |||
| int64 chunkEnd; | |||
| bool isChunked, readingChunk; | |||
| void closeSocket (bool resetLevelsOfRedirection = true) | |||
| { | |||
| @@ -298,6 +348,13 @@ private: | |||
| 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; | |||
| } | |||
| @@ -345,7 +402,7 @@ private: | |||
| static void writeHost (MemoryOutputStream& dest, const bool isPost, | |||
| 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 */ | |||
| if (port != 80) | |||
| @@ -455,3 +512,4 @@ private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream) | |||
| }; | |||
| #endif | |||
| @@ -279,8 +279,12 @@ public: | |||
| //============================================================================== | |||
| /** Makes the font bold or non-bold. */ | |||
| 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; | |||
| /** Returns true if the font is bold. */ | |||
| bool isBold() const noexcept; | |||
| @@ -443,7 +443,7 @@ public: | |||
| /** Changes the component's position and size. | |||
| 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 | |||
| 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. | |||
| 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 | |||
| call to moved(). If it changes the size, it will also make a call to resized(). | |||
| @@ -85,12 +85,6 @@ struct TextEditorKeyMapper | |||
| 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) | |||
| || key == KeyPress (KeyPress::insertKey, ModifierKeys::ctrlModifier, 0)) | |||
| return target.copyToClipboard(); | |||
| @@ -103,6 +97,13 @@ struct TextEditorKeyMapper | |||
| || key == KeyPress (KeyPress::insertKey, ModifierKeys::shiftModifier, 0)) | |||
| 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)) | |||
| return target.selectAll(); | |||
| @@ -490,6 +490,12 @@ int LookAndFeel_V2::getAlertWindowButtonHeight() | |||
| return 28; | |||
| } | |||
| Font LookAndFeel_V2::getAlertWindowTitleFont() | |||
| { | |||
| Font messageFont = getAlertWindowMessageFont(); | |||
| return messageFont.withHeight (messageFont.getHeight() * 1.1f).boldened(); | |||
| } | |||
| Font LookAndFeel_V2::getAlertWindowMessageFont() | |||
| { | |||
| return Font (15.0f); | |||
| @@ -67,7 +67,22 @@ public: | |||
| void drawAlertBox (Graphics&, AlertWindow&, const Rectangle<int>& textArea, TextLayout&) override; | |||
| int getAlertBoxWindowFlags() 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 getAlertWindowFont() override; | |||
| //============================================================================== | |||
| @@ -56,6 +56,9 @@ public: | |||
| 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. | |||
| 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&, | |||
| int rowNumber, | |||
| @@ -343,22 +343,24 @@ void AlertWindow::updateLayout (const bool onlyIncreaseSize) | |||
| const int titleH = 24; | |||
| 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)); | |||
| const int edgeGap = 10; | |||
| const int labelHeight = 18; | |||
| int iconSpace = 0; | |||
| AttributedString attributedText; | |||
| attributedText.append (getName(), font.withHeight (font.getHeight() * 1.1f).boldened()); | |||
| attributedText.append (getName(), lookAndFeel.getAlertWindowTitleFont()); | |||
| if (text.isNotEmpty()) | |||
| attributedText.append ("\n\n" + text, font); | |||
| attributedText.append ("\n\n" + text, messageFont); | |||
| attributedText.setColour (findColour (textColourId)); | |||
| @@ -383,18 +385,18 @@ void AlertWindow::updateLayout (const bool onlyIncreaseSize) | |||
| int buttonW = 40; | |||
| for (int i = 0; i < buttons.size(); ++i) | |||
| buttonW += 16 + buttons.getUnchecked(i)->getWidth(); | |||
| buttonW += 16 + buttons.getUnchecked (i)->getWidth(); | |||
| w = jmax (buttonW, w); | |||
| h += (textBoxes.size() + comboBoxes.size() + progressBars.size()) * 50; | |||
| if (buttons.size() > 0) | |||
| h += 20 + buttons.getUnchecked(0)->getHeight(); | |||
| h += 20 + buttons.getUnchecked (0)->getHeight(); | |||
| for (int i = customComps.size(); --i >= 0;) | |||
| { | |||
| Component* c = customComps.getUnchecked(i); | |||
| Component* c = customComps.getUnchecked (i); | |||
| w = jmax (w, (c->getWidth() * 100) / 80); | |||
| h += 10 + c->getHeight(); | |||
| @@ -437,6 +437,7 @@ public: | |||
| virtual int getAlertWindowButtonHeight() = 0; | |||
| virtual Font getAlertWindowTitleFont() = 0; | |||
| virtual Font getAlertWindowMessageFont() = 0; | |||
| virtual Font getAlertWindowFont() = 0; | |||
| }; | |||