| @@ -688,27 +688,21 @@ FileInputStream* File::createInputStream() const | |||||
| FileOutputStream* File::createOutputStream (const int bufferSize) const | FileOutputStream* File::createOutputStream (const int bufferSize) const | ||||
| { | { | ||||
| ScopedPointer <FileOutputStream> out (new FileOutputStream (*this, bufferSize)); | |||||
| ScopedPointer<FileOutputStream> out (new FileOutputStream (*this, bufferSize)); | |||||
| if (out->failedToOpen()) | |||||
| return nullptr; | |||||
| return out.release(); | |||||
| return out->failedToOpen() ? nullptr | |||||
| : out.release(); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| bool File::appendData (const void* const dataToAppend, | bool File::appendData (const void* const dataToAppend, | ||||
| const int numberOfBytes) const | const int numberOfBytes) const | ||||
| { | { | ||||
| if (numberOfBytes > 0) | |||||
| { | |||||
| FileOutputStream out (*this, 8192); | |||||
| return (! out.failedToOpen()) | |||||
| && out.write (dataToAppend, numberOfBytes); | |||||
| } | |||||
| if (numberOfBytes <= 0) | |||||
| return true; | |||||
| return true; | |||||
| FileOutputStream out (*this, 8192); | |||||
| return out.openedOk() && out.write (dataToAppend, numberOfBytes); | |||||
| } | } | ||||
| bool File::replaceWithData (const void* const dataToWrite, | bool File::replaceWithData (const void* const dataToWrite, | ||||
| @@ -728,15 +722,13 @@ bool File::appendText (const String& text, | |||||
| const bool asUnicode, | const bool asUnicode, | ||||
| const bool writeUnicodeHeaderBytes) const | const bool writeUnicodeHeaderBytes) const | ||||
| { | { | ||||
| const ScopedPointer <FileOutputStream> out (createOutputStream()); | |||||
| FileOutputStream out (*this); | |||||
| if (out != nullptr) | |||||
| { | |||||
| out->writeText (text, asUnicode, writeUnicodeHeaderBytes); | |||||
| return true; | |||||
| } | |||||
| if (out.failedToOpen()) | |||||
| return false; | |||||
| return false; | |||||
| out.writeText (text, asUnicode, writeUnicodeHeaderBytes); | |||||
| return true; | |||||
| } | } | ||||
| bool File::replaceWithText (const String& textToWrite, | bool File::replaceWithText (const String& textToWrite, | ||||
| @@ -53,6 +53,8 @@ int64 FileInputStream::getTotalLength() | |||||
| int FileInputStream::read (void* buffer, int bytesToRead) | int FileInputStream::read (void* buffer, int bytesToRead) | ||||
| { | { | ||||
| jassert (buffer != nullptr && bytesToRead >= 0); | |||||
| if (needToSeek) | if (needToSeek) | ||||
| { | { | ||||
| if (juce_fileSetPosition (fileHandle, currentPosition) < 0) | if (juce_fileSetPosition (fileHandle, currentPosition) < 0) | ||||
| @@ -58,7 +58,18 @@ public: | |||||
| The result will be ok if the file opened successfully. If an error occurs while | The result will be ok if the file opened successfully. If an error occurs while | ||||
| opening or reading from the file, this will contain an error message. | opening or reading from the file, this will contain an error message. | ||||
| */ | */ | ||||
| Result getStatus() const { return status; } | |||||
| const Result& getStatus() const noexcept { return status; } | |||||
| /** Returns true if the stream couldn't be opened for some reason. | |||||
| @see getResult() | |||||
| */ | |||||
| bool failedToOpen() const noexcept { return status.failed(); } | |||||
| /** Returns true if the stream opened without problems. | |||||
| @see getResult() | |||||
| */ | |||||
| bool openedOk() const noexcept { return status.wasOk(); } | |||||
| //============================================================================== | //============================================================================== | ||||
| int64 getTotalLength(); | int64 getTotalLength(); | ||||
| @@ -67,7 +78,6 @@ public: | |||||
| int64 getPosition(); | int64 getPosition(); | ||||
| bool setPosition (int64 pos); | bool setPosition (int64 pos); | ||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| File file; | File file; | ||||
| @@ -84,6 +84,8 @@ void FileOutputStream::flush() | |||||
| bool FileOutputStream::write (const void* const src, const int numBytes) | bool FileOutputStream::write (const void* const src, const int numBytes) | ||||
| { | { | ||||
| jassert (src != nullptr && numBytes >= 0); | |||||
| if (bytesInBuffer + numBytes < bufferSize) | if (bytesInBuffer + numBytes < bufferSize) | ||||
| { | { | ||||
| memcpy (buffer + bytesInBuffer, src, (size_t) numBytes); | memcpy (buffer + bytesInBuffer, src, (size_t) numBytes); | ||||
| @@ -51,9 +51,6 @@ public: | |||||
| use File::deleteFile() before opening the stream, or use setPosition(0) | use File::deleteFile() before opening the stream, or use setPosition(0) | ||||
| after it's opened (although this won't truncate the file). | after it's opened (although this won't truncate the file). | ||||
| It's better to use File::createOutputStream() to create one of these, rather | |||||
| than using the class directly. | |||||
| @see TemporaryFile | @see TemporaryFile | ||||
| */ | */ | ||||
| FileOutputStream (const File& fileToWriteTo, | FileOutputStream (const File& fileToWriteTo, | ||||
| @@ -71,12 +68,17 @@ public: | |||||
| The result will be ok if the file opened successfully. If an error occurs while | The result will be ok if the file opened successfully. If an error occurs while | ||||
| opening or writing to the file, this will contain an error message. | opening or writing to the file, this will contain an error message. | ||||
| */ | */ | ||||
| Result getStatus() const { return status; } | |||||
| const Result& getStatus() const noexcept { return status; } | |||||
| /** Returns true if the stream couldn't be opened for some reason. | /** Returns true if the stream couldn't be opened for some reason. | ||||
| @see getResult() | @see getResult() | ||||
| */ | */ | ||||
| bool failedToOpen() const { return status.failed(); } | |||||
| bool failedToOpen() const noexcept { return status.failed(); } | |||||
| /** Returns true if the stream opened without problems. | |||||
| @see getResult() | |||||
| */ | |||||
| bool openedOk() const noexcept { return status.wasOk(); } | |||||
| //============================================================================== | //============================================================================== | ||||
| void flush(); | void flush(); | ||||
| @@ -136,6 +136,8 @@ public: | |||||
| int read (void* buffer, int bytesToRead) | int read (void* buffer, int bytesToRead) | ||||
| { | { | ||||
| jassert (buffer != nullptr && bytesToRead >= 0); | |||||
| if (stream == nullptr) | if (stream == nullptr) | ||||
| return 0; | return 0; | ||||
| @@ -361,6 +361,8 @@ public: | |||||
| int read (void* buffer, int bytesToRead) | int read (void* buffer, int bytesToRead) | ||||
| { | { | ||||
| jassert (buffer != nullptr && bytesToRead >= 0); | |||||
| if (finished || isError()) | if (finished || isError()) | ||||
| { | { | ||||
| return 0; | return 0; | ||||
| @@ -988,6 +988,8 @@ public: | |||||
| int read (void* const dest, const int numBytes) | int read (void* const dest, const int numBytes) | ||||
| { | { | ||||
| jassert (dest != nullptr); | |||||
| if (readHandle == 0 && childPID != 0) | if (readHandle == 0 && childPID != 0) | ||||
| readHandle = fdopen (pipeHandle, "r"); | readHandle = fdopen (pipeHandle, "r"); | ||||
| @@ -145,6 +145,7 @@ public: | |||||
| int read (void* buffer, int bytesToRead) | int read (void* buffer, int bytesToRead) | ||||
| { | { | ||||
| jassert (buffer != nullptr && bytesToRead >= 0); | |||||
| DWORD bytesRead = 0; | DWORD bytesRead = 0; | ||||
| if (! (finished || isError())) | if (! (finished || isError())) | ||||
| @@ -131,6 +131,8 @@ void BufferedInputStream::ensureBuffered() | |||||
| int BufferedInputStream::read (void* destBuffer, int maxBytesToRead) | int BufferedInputStream::read (void* destBuffer, int maxBytesToRead) | ||||
| { | { | ||||
| jassert (destBuffer != nullptr && maxBytesToRead >= 0); | |||||
| if (position >= bufferStart | if (position >= bufferStart | ||||
| && position + maxBytesToRead <= lastReadPos) | && position + maxBytesToRead <= lastReadPos) | ||||
| { | { | ||||
| @@ -208,7 +208,7 @@ int InputStream::readIntoMemoryBlock (MemoryBlock& block, int numBytes) | |||||
| String InputStream::readEntireStreamAsString() | String InputStream::readEntireStreamAsString() | ||||
| { | { | ||||
| MemoryOutputStream mo; | MemoryOutputStream mo; | ||||
| mo.writeFromInputStream (*this, -1); | |||||
| mo << *this; | |||||
| return mo.toString(); | return mo.toString(); | ||||
| } | } | ||||
| @@ -59,16 +59,16 @@ public: | |||||
| virtual bool isExhausted() = 0; | virtual bool isExhausted() = 0; | ||||
| //============================================================================== | //============================================================================== | ||||
| /** Reads a set of bytes from the stream into a memory buffer. | |||||
| /** Reads some data from the stream into a memory buffer. | |||||
| This is the only read method that subclasses actually need to implement, as the | This is the only read method that subclasses actually need to implement, as the | ||||
| InputStream base class implements the other read methods in terms of this one (although | InputStream base class implements the other read methods in terms of this one (although | ||||
| it's often more efficient for subclasses to implement them directly). | it's often more efficient for subclasses to implement them directly). | ||||
| @param destBuffer the destination buffer for the data | |||||
| @param destBuffer the destination buffer for the data. This must not be null. | |||||
| @param maxBytesToRead the maximum number of bytes to read - make sure the | @param maxBytesToRead the maximum number of bytes to read - make sure the | ||||
| memory block passed in is big enough to contain this | memory block passed in is big enough to contain this | ||||
| many bytes. | |||||
| many bytes. This value must not be negative. | |||||
| @returns the actual number of bytes that were read, which may be less than | @returns the actual number of bytes that were read, which may be less than | ||||
| maxBytesToRead if the stream is exhausted before it gets that far | maxBytesToRead if the stream is exhausted before it gets that far | ||||
| @@ -65,7 +65,8 @@ int64 MemoryInputStream::getTotalLength() | |||||
| int MemoryInputStream::read (void* const buffer, const int howMany) | int MemoryInputStream::read (void* const buffer, const int howMany) | ||||
| { | { | ||||
| jassert (howMany >= 0); | |||||
| jassert (buffer != nullptr && howMany >= 0); | |||||
| const int num = jmin (howMany, (int) (dataSize - position)); | const int num = jmin (howMany, (int) (dataSize - position)); | ||||
| if (num <= 0) | if (num <= 0) | ||||
| return 0; | return 0; | ||||
| @@ -81,6 +81,8 @@ void MemoryOutputStream::prepareToWrite (int numBytes) | |||||
| bool MemoryOutputStream::write (const void* const buffer, int howMany) | bool MemoryOutputStream::write (const void* const buffer, int howMany) | ||||
| { | { | ||||
| jassert (buffer != nullptr && howMany >= 0); | |||||
| if (howMany > 0) | if (howMany > 0) | ||||
| { | { | ||||
| prepareToWrite (howMany); | prepareToWrite (howMany); | ||||
| @@ -298,11 +298,13 @@ OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const MemoryBlock& | |||||
| OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const File& fileToRead) | OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const File& fileToRead) | ||||
| { | { | ||||
| const ScopedPointer<FileInputStream> in (fileToRead.createInputStream()); | |||||
| if (in != nullptr) | |||||
| stream.writeFromInputStream (*in, -1); | |||||
| FileInputStream in (fileToRead); | |||||
| return stream << in; | |||||
| } | |||||
| OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, InputStream& streamToRead) | |||||
| { | |||||
| stream.writeFromInputStream (streamToRead, -1); | |||||
| return stream; | return stream; | ||||
| } | } | ||||
| @@ -83,10 +83,12 @@ public: | |||||
| that needs to be overloaded - the base class has methods for writing other | that needs to be overloaded - the base class has methods for writing other | ||||
| types of data which use this to do the work. | types of data which use this to do the work. | ||||
| @param dataToWrite the target buffer to receive the data. This must not be null. | |||||
| @param numberOfBytes the number of bytes to write. This must not be negative. | |||||
| @returns false if the write operation fails for some reason | @returns false if the write operation fails for some reason | ||||
| */ | */ | ||||
| virtual bool write (const void* dataToWrite, | virtual bool write (const void* dataToWrite, | ||||
| int howManyBytes) = 0; | |||||
| int numberOfBytes) = 0; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Writes a single byte to the stream. | /** Writes a single byte to the stream. | ||||
| @@ -243,6 +245,9 @@ OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const MemoryBlock& | |||||
| /** Writes the contents of a file to a stream. */ | /** Writes the contents of a file to a stream. */ | ||||
| OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const File& fileToRead); | OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const File& fileToRead); | ||||
| /** Writes the complete contents of an input stream to an output stream. */ | |||||
| OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, InputStream& streamToRead); | |||||
| /** Writes a new-line to a stream. | /** Writes a new-line to a stream. | ||||
| You can use the predefined symbol 'newLine' to invoke this, e.g. | You can use the predefined symbol 'newLine' to invoke this, e.g. | ||||
| @code | @code | ||||
| @@ -61,6 +61,8 @@ bool SubregionStream::setPosition (int64 newPosition) | |||||
| int SubregionStream::read (void* destBuffer, int maxBytesToRead) | int SubregionStream::read (void* destBuffer, int maxBytesToRead) | ||||
| { | { | ||||
| jassert (destBuffer != nullptr && maxBytesToRead >= 0); | |||||
| if (lengthOfSourceStream < 0) | if (lengthOfSourceStream < 0) | ||||
| { | { | ||||
| return source->read (destBuffer, maxBytesToRead); | return source->read (destBuffer, maxBytesToRead); | ||||
| @@ -42,14 +42,12 @@ class GZIPCompressorOutputStream::GZIPCompressorHelper | |||||
| { | { | ||||
| public: | public: | ||||
| GZIPCompressorHelper (const int compressionLevel, const int windowBits) | GZIPCompressorHelper (const int compressionLevel, const int windowBits) | ||||
| : data (nullptr), | |||||
| dataSize (0), | |||||
| : buffer ((size_t) gzipCompBufferSize), | |||||
| compLevel (compressionLevel), | compLevel (compressionLevel), | ||||
| strategy (0), | strategy (0), | ||||
| setParams (true), | |||||
| isFirstDeflate (true), | |||||
| streamIsValid (false), | streamIsValid (false), | ||||
| finished (false), | |||||
| shouldFinish (false) | |||||
| finished (false) | |||||
| { | { | ||||
| using namespace zlibNamespace; | using namespace zlibNamespace; | ||||
| zerostruct (stream); | zerostruct (stream); | ||||
| @@ -61,67 +59,74 @@ public: | |||||
| ~GZIPCompressorHelper() | ~GZIPCompressorHelper() | ||||
| { | { | ||||
| using namespace zlibNamespace; | |||||
| if (streamIsValid) | if (streamIsValid) | ||||
| deflateEnd (&stream); | |||||
| zlibNamespace::deflateEnd (&stream); | |||||
| } | } | ||||
| bool needsInput() const noexcept | |||||
| bool write (const uint8* data, int dataSize, OutputStream& destStream) | |||||
| { | { | ||||
| return dataSize <= 0; | |||||
| // When you call flush() on a gzip stream, the stream is closed, and you can | |||||
| // no longer continue to write data to it! | |||||
| jassert (! finished); | |||||
| while (dataSize > 0) | |||||
| if (! doNextBlock (data, dataSize, destStream, Z_NO_FLUSH)) | |||||
| return false; | |||||
| return true; | |||||
| } | } | ||||
| void setInput (const uint8* const newData, const size_t size) noexcept | |||||
| void finish (OutputStream& destStream) | |||||
| { | { | ||||
| data = newData; | |||||
| dataSize = size; | |||||
| const uint8* data = nullptr; | |||||
| int dataSize = 0; | |||||
| while (! finished) | |||||
| doNextBlock (data, dataSize, destStream, Z_FINISH); | |||||
| } | } | ||||
| int doNextBlock (uint8* const dest, const int destSize) noexcept | |||||
| private: | |||||
| enum { gzipCompBufferSize = 32768 }; | |||||
| HeapBlock <zlibNamespace::Bytef> buffer; | |||||
| zlibNamespace::z_stream stream; | |||||
| int compLevel, strategy; | |||||
| bool isFirstDeflate, streamIsValid, finished; | |||||
| bool doNextBlock (const uint8*& data, int& dataSize, OutputStream& destStream, const int flushMode) | |||||
| { | { | ||||
| using namespace zlibNamespace; | using namespace zlibNamespace; | ||||
| if (streamIsValid) | if (streamIsValid) | ||||
| { | { | ||||
| stream.next_in = const_cast <uint8*> (data); | |||||
| stream.next_out = dest; | |||||
| stream.avail_in = (z_uInt) dataSize; | |||||
| stream.avail_out = (z_uInt) destSize; | |||||
| stream.next_in = const_cast <uint8*> (data); | |||||
| stream.next_out = buffer; | |||||
| stream.avail_in = (z_uInt) dataSize; | |||||
| stream.avail_out = (z_uInt) gzipCompBufferSize; | |||||
| const int result = setParams ? deflateParams (&stream, compLevel, strategy) | |||||
| : deflate (&stream, shouldFinish ? Z_FINISH : Z_NO_FLUSH); | |||||
| setParams = false; | |||||
| const int result = isFirstDeflate ? deflateParams (&stream, compLevel, strategy) | |||||
| : deflate (&stream, flushMode); | |||||
| isFirstDeflate = false; | |||||
| switch (result) | switch (result) | ||||
| { | { | ||||
| case Z_STREAM_END: | |||||
| finished = true; | |||||
| // Deliberate fall-through.. | |||||
| case Z_OK: | |||||
| data += dataSize - stream.avail_in; | |||||
| dataSize = stream.avail_in; | |||||
| return (int) (destSize - stream.avail_out); | |||||
| case Z_STREAM_END: | |||||
| finished = true; | |||||
| // Deliberate fall-through.. | |||||
| case Z_OK: | |||||
| { | |||||
| data += dataSize - stream.avail_in; | |||||
| dataSize = (int) stream.avail_in; | |||||
| const int bytesDone = (int) (gzipCompBufferSize - stream.avail_out); | |||||
| return bytesDone <= 0 || destStream.write (buffer, bytesDone); | |||||
| } | |||||
| default: | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | } | ||||
| } | } | ||||
| return 0; | |||||
| return false; | |||||
| } | } | ||||
| enum { gzipCompBufferSize = 32768 }; | |||||
| private: | |||||
| zlibNamespace::z_stream stream; | |||||
| const uint8* data; | |||||
| size_t dataSize; | |||||
| int compLevel, strategy; | |||||
| bool setParams, streamIsValid; | |||||
| public: | |||||
| bool finished, shouldFinish; | |||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -129,9 +134,10 @@ GZIPCompressorOutputStream::GZIPCompressorOutputStream (OutputStream* const dest | |||||
| int compressionLevel, | int compressionLevel, | ||||
| const bool deleteDestStream, | const bool deleteDestStream, | ||||
| const int windowBits) | const int windowBits) | ||||
| : destStream (destStream_, deleteDestStream), | |||||
| buffer ((size_t) GZIPCompressorHelper::gzipCompBufferSize) | |||||
| : destStream (destStream_, deleteDestStream) | |||||
| { | { | ||||
| jassert (destStream_ != nullptr); | |||||
| if (compressionLevel < 1 || compressionLevel > 9) | if (compressionLevel < 1 || compressionLevel > 9) | ||||
| compressionLevel = -1; | compressionLevel = -1; | ||||
| @@ -140,48 +146,20 @@ GZIPCompressorOutputStream::GZIPCompressorOutputStream (OutputStream* const dest | |||||
| GZIPCompressorOutputStream::~GZIPCompressorOutputStream() | GZIPCompressorOutputStream::~GZIPCompressorOutputStream() | ||||
| { | { | ||||
| flushInternal(); | |||||
| } | |||||
| //============================================================================== | |||||
| void GZIPCompressorOutputStream::flushInternal() | |||||
| { | |||||
| if (! helper->finished) | |||||
| { | |||||
| helper->shouldFinish = true; | |||||
| while (! helper->finished) | |||||
| doNextBlock(); | |||||
| } | |||||
| destStream->flush(); | |||||
| flush(); | |||||
| } | } | ||||
| void GZIPCompressorOutputStream::flush() | void GZIPCompressorOutputStream::flush() | ||||
| { | { | ||||
| flushInternal(); | |||||
| helper->finish (*destStream); | |||||
| destStream->flush(); | |||||
| } | } | ||||
| bool GZIPCompressorOutputStream::write (const void* destBuffer, int howMany) | bool GZIPCompressorOutputStream::write (const void* destBuffer, int howMany) | ||||
| { | { | ||||
| if (! helper->finished) | |||||
| { | |||||
| helper->setInput (static_cast <const uint8*> (destBuffer), (size_t) howMany); | |||||
| while (! helper->needsInput()) | |||||
| { | |||||
| if (! doNextBlock()) | |||||
| return false; | |||||
| } | |||||
| } | |||||
| jassert (destBuffer != nullptr && howMany >= 0); | |||||
| return true; | |||||
| } | |||||
| bool GZIPCompressorOutputStream::doNextBlock() | |||||
| { | |||||
| const int len = helper->doNextBlock (buffer, (int) GZIPCompressorHelper::gzipCompBufferSize); | |||||
| return len <= 0 || destStream->write (buffer, len); | |||||
| return helper->write (static_cast <const uint8*> (destBuffer), howMany, *destStream); | |||||
| } | } | ||||
| int64 GZIPCompressorOutputStream::getPosition() | int64 GZIPCompressorOutputStream::getPosition() | ||||
| @@ -231,7 +209,7 @@ public: | |||||
| MemoryInputStream compressedInput (compressed.getData(), compressed.getDataSize(), false); | MemoryInputStream compressedInput (compressed.getData(), compressed.getDataSize(), false); | ||||
| GZIPDecompressorInputStream unzipper (compressedInput); | GZIPDecompressorInputStream unzipper (compressedInput); | ||||
| uncompressed.writeFromInputStream (unzipper, -1); | |||||
| uncompressed << unzipper; | |||||
| } | } | ||||
| expectEquals ((int) uncompressed.getDataSize(), | expectEquals ((int) uncompressed.getDataSize(), | ||||
| @@ -35,6 +35,10 @@ | |||||
| /** | /** | ||||
| A stream which uses zlib to compress the data written into it. | A stream which uses zlib to compress the data written into it. | ||||
| Important note: When you call flush() on a GZIPCompressorOutputStream, | |||||
| the gzip data is closed - this means that no more data can be written to | |||||
| it, and any subsequent attempts to call write() will cause an assertion. | |||||
| @see GZIPDecompressorInputStream | @see GZIPDecompressorInputStream | ||||
| */ | */ | ||||
| class JUCE_API GZIPCompressorOutputStream : public OutputStream | class JUCE_API GZIPCompressorOutputStream : public OutputStream | ||||
| @@ -64,7 +68,13 @@ public: | |||||
| ~GZIPCompressorOutputStream(); | ~GZIPCompressorOutputStream(); | ||||
| //============================================================================== | //============================================================================== | ||||
| /** Flushes and closes the stream. | |||||
| Note that unlike most streams, when you call flush() on a GZIPCompressorOutputStream, | |||||
| the stream is closed - this means that no more data can be written to it, and any | |||||
| subsequent attempts to call write() will cause an assertion. | |||||
| */ | |||||
| void flush(); | void flush(); | ||||
| int64 getPosition(); | int64 getPosition(); | ||||
| bool setPosition (int64 newPosition); | bool setPosition (int64 newPosition); | ||||
| bool write (const void* destBuffer, int howMany); | bool write (const void* destBuffer, int howMany); | ||||
| @@ -81,12 +91,10 @@ public: | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| OptionalScopedPointer<OutputStream> destStream; | OptionalScopedPointer<OutputStream> destStream; | ||||
| HeapBlock <uint8> buffer; | |||||
| class GZIPCompressorHelper; | class GZIPCompressorHelper; | ||||
| friend class ScopedPointer <GZIPCompressorHelper>; | friend class ScopedPointer <GZIPCompressorHelper>; | ||||
| ScopedPointer <GZIPCompressorHelper> helper; | ScopedPointer <GZIPCompressorHelper> helper; | ||||
| bool doNextBlock(); | |||||
| void flushInternal(); | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GZIPCompressorOutputStream); | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GZIPCompressorOutputStream); | ||||
| }; | }; | ||||
| @@ -192,52 +192,49 @@ int64 GZIPDecompressorInputStream::getTotalLength() | |||||
| int GZIPDecompressorInputStream::read (void* destBuffer, int howMany) | int GZIPDecompressorInputStream::read (void* destBuffer, int howMany) | ||||
| { | { | ||||
| if ((howMany > 0) && ! isEof) | |||||
| jassert (destBuffer != nullptr && howMany >= 0); | |||||
| if (howMany > 0 && ! isEof) | |||||
| { | { | ||||
| jassert (destBuffer != nullptr); | |||||
| int numRead = 0; | |||||
| uint8* d = static_cast <uint8*> (destBuffer); | |||||
| if (destBuffer != nullptr) | |||||
| while (! helper->error) | |||||
| { | { | ||||
| int numRead = 0; | |||||
| uint8* d = static_cast <uint8*> (destBuffer); | |||||
| const int n = helper->doNextBlock (d, howMany); | |||||
| currentPos += n; | |||||
| while (! helper->error) | |||||
| if (n == 0) | |||||
| { | { | ||||
| const int n = helper->doNextBlock (d, howMany); | |||||
| currentPos += n; | |||||
| if (helper->finished || helper->needsDictionary) | |||||
| { | |||||
| isEof = true; | |||||
| return numRead; | |||||
| } | |||||
| if (n == 0) | |||||
| if (helper->needsInput()) | |||||
| { | { | ||||
| if (helper->finished || helper->needsDictionary) | |||||
| activeBufferSize = sourceStream->read (buffer, (int) GZIPDecompressHelper::gzipDecompBufferSize); | |||||
| if (activeBufferSize > 0) | |||||
| { | { | ||||
| isEof = true; | |||||
| return numRead; | |||||
| helper->setInput (buffer, (size_t) activeBufferSize); | |||||
| } | } | ||||
| if (helper->needsInput()) | |||||
| else | |||||
| { | { | ||||
| activeBufferSize = sourceStream->read (buffer, (int) GZIPDecompressHelper::gzipDecompBufferSize); | |||||
| if (activeBufferSize > 0) | |||||
| { | |||||
| helper->setInput (buffer, (size_t) activeBufferSize); | |||||
| } | |||||
| else | |||||
| { | |||||
| isEof = true; | |||||
| return numRead; | |||||
| } | |||||
| isEof = true; | |||||
| return numRead; | |||||
| } | } | ||||
| } | } | ||||
| else | |||||
| { | |||||
| numRead += n; | |||||
| howMany -= n; | |||||
| d += n; | |||||
| } | |||||
| else | |||||
| { | |||||
| numRead += n; | |||||
| howMany -= n; | |||||
| d += n; | |||||
| if (howMany <= 0) | |||||
| return numRead; | |||||
| } | |||||
| if (howMany <= 0) | |||||
| return numRead; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -354,60 +354,65 @@ int ZipFile::findEndOfZipEntryTable (InputStream& input, int& numEntries) | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| bool ZipFile::uncompressTo (const File& targetDirectory, | |||||
| const bool shouldOverwriteFiles) | |||||
| Result ZipFile::uncompressTo (const File& targetDirectory, | |||||
| const bool shouldOverwriteFiles) | |||||
| { | { | ||||
| for (int i = 0; i < entries.size(); ++i) | for (int i = 0; i < entries.size(); ++i) | ||||
| if (! uncompressEntry (i, targetDirectory, shouldOverwriteFiles)) | |||||
| return false; | |||||
| { | |||||
| Result result (uncompressEntry (i, targetDirectory, shouldOverwriteFiles)); | |||||
| if (result.failed()) | |||||
| return result; | |||||
| } | |||||
| return true; | |||||
| return Result::ok(); | |||||
| } | } | ||||
| bool ZipFile::uncompressEntry (const int index, | |||||
| const File& targetDirectory, | |||||
| bool shouldOverwriteFiles) | |||||
| Result ZipFile::uncompressEntry (const int index, | |||||
| const File& targetDirectory, | |||||
| bool shouldOverwriteFiles) | |||||
| { | { | ||||
| const ZipEntryInfo* zei = entries [index]; | |||||
| const ZipEntryInfo* zei = entries.getUnchecked (index); | |||||
| if (zei != nullptr) | |||||
| const File targetFile (targetDirectory.getChildFile (zei->entry.filename)); | |||||
| if (zei->entry.filename.endsWithChar ('/')) | |||||
| { | { | ||||
| const File targetFile (targetDirectory.getChildFile (zei->entry.filename)); | |||||
| return targetFile.createDirectory(); // (entry is a directory, not a file) | |||||
| } | |||||
| else | |||||
| { | |||||
| ScopedPointer<InputStream> in (createStreamForEntry (index)); | |||||
| if (zei->entry.filename.endsWithChar ('/')) | |||||
| { | |||||
| return targetFile.createDirectory(); // (entry is a directory, not a file) | |||||
| } | |||||
| else | |||||
| if (in == nullptr) | |||||
| return Result::fail ("Failed to open the zip file for reading"); | |||||
| if (targetFile.exists()) | |||||
| { | { | ||||
| ScopedPointer<InputStream> in (createStreamForEntry (index)); | |||||
| if (! shouldOverwriteFiles) | |||||
| return Result::ok(); | |||||
| if (in != nullptr) | |||||
| { | |||||
| if (shouldOverwriteFiles && ! targetFile.deleteFile()) | |||||
| return false; | |||||
| if (! targetFile.deleteFile()) | |||||
| return Result::fail ("Failed to write to target file: " + targetFile.getFullPathName()); | |||||
| } | |||||
| if ((! targetFile.exists()) && targetFile.getParentDirectory().createDirectory()) | |||||
| { | |||||
| ScopedPointer<FileOutputStream> out (targetFile.createOutputStream()); | |||||
| if (! targetFile.getParentDirectory().createDirectory()) | |||||
| return Result::fail ("Failed to create target folder: " + targetFile.getParentDirectory().getFullPathName()); | |||||
| if (out != nullptr) | |||||
| { | |||||
| out->writeFromInputStream (*in, -1); | |||||
| out = nullptr; | |||||
| { | |||||
| FileOutputStream out (targetFile); | |||||
| targetFile.setCreationTime (zei->entry.fileTime); | |||||
| targetFile.setLastModificationTime (zei->entry.fileTime); | |||||
| targetFile.setLastAccessTime (zei->entry.fileTime); | |||||
| if (out.failedToOpen()) | |||||
| return Result::fail ("Failed to write to target file: " + targetFile.getFullPathName()); | |||||
| return true; | |||||
| } | |||||
| } | |||||
| } | |||||
| out << *in; | |||||
| } | } | ||||
| } | |||||
| return false; | |||||
| targetFile.setCreationTime (zei->entry.fileTime); | |||||
| targetFile.setLastModificationTime (zei->entry.fileTime); | |||||
| targetFile.setLastAccessTime (zei->entry.fileTime); | |||||
| return Result::ok(); | |||||
| } | |||||
| } | } | ||||
| @@ -485,17 +490,17 @@ private: | |||||
| bool writeSource (OutputStream& target) | bool writeSource (OutputStream& target) | ||||
| { | { | ||||
| checksum = 0; | checksum = 0; | ||||
| ScopedPointer<FileInputStream> input (file.createInputStream()); | |||||
| FileInputStream input (file); | |||||
| if (input == nullptr) | |||||
| if (input.failedToOpen()) | |||||
| return false; | return false; | ||||
| const int bufferSize = 2048; | const int bufferSize = 2048; | ||||
| HeapBlock<unsigned char> buffer (bufferSize); | HeapBlock<unsigned char> buffer (bufferSize); | ||||
| while (! input->isExhausted()) | |||||
| while (! input.isExhausted()) | |||||
| { | { | ||||
| const int bytesRead = input->read (buffer, bufferSize); | |||||
| const int bytesRead = input.read (buffer, bufferSize); | |||||
| if (bytesRead < 0) | if (bytesRead < 0) | ||||
| return false; | return false; | ||||
| @@ -145,24 +145,25 @@ public: | |||||
| @param targetDirectory the root folder to uncompress to | @param targetDirectory the root folder to uncompress to | ||||
| @param shouldOverwriteFiles whether to overwrite existing files with similarly-named ones | @param shouldOverwriteFiles whether to overwrite existing files with similarly-named ones | ||||
| @returns true if all the files are successfully unzipped | |||||
| @returns success if the file is successfully unzipped | |||||
| */ | */ | ||||
| bool uncompressTo (const File& targetDirectory, | |||||
| bool shouldOverwriteFiles = true); | |||||
| Result uncompressTo (const File& targetDirectory, | |||||
| bool shouldOverwriteFiles = true); | |||||
| /** Uncompresses one of the entries from the zip file. | /** Uncompresses one of the entries from the zip file. | ||||
| This will expand the entry and write it in a target directory. The entry's path is used to | This will expand the entry and write it in a target directory. The entry's path is used to | ||||
| determine which subfolder of the target should contain the new file. | determine which subfolder of the target should contain the new file. | ||||
| @param index the index of the entry to uncompress | |||||
| @param index the index of the entry to uncompress - this must be a valid index | |||||
| between 0 and (getNumEntries() - 1). | |||||
| @param targetDirectory the root folder to uncompress into | @param targetDirectory the root folder to uncompress into | ||||
| @param shouldOverwriteFiles whether to overwrite existing files with similarly-named ones | @param shouldOverwriteFiles whether to overwrite existing files with similarly-named ones | ||||
| @returns true if the files is successfully unzipped | |||||
| @returns success if all the files are successfully unzipped | |||||
| */ | */ | ||||
| bool uncompressEntry (int index, | |||||
| const File& targetDirectory, | |||||
| bool shouldOverwriteFiles = true); | |||||
| Result uncompressEntry (int index, | |||||
| const File& targetDirectory, | |||||
| bool shouldOverwriteFiles = true); | |||||
| //============================================================================== | //============================================================================== | ||||
| @@ -241,7 +241,7 @@ Image JPEGImageFormat::decodeImage (InputStream& in) | |||||
| using namespace JPEGHelpers; | using namespace JPEGHelpers; | ||||
| MemoryOutputStream mb; | MemoryOutputStream mb; | ||||
| mb.writeFromInputStream (in, -1); | |||||
| mb << in; | |||||
| Image image; | Image image; | ||||
| @@ -145,16 +145,16 @@ Drawable* Drawable::createFromImageData (const void* data, const size_t numBytes | |||||
| Drawable* Drawable::createFromImageDataStream (InputStream& dataSource) | Drawable* Drawable::createFromImageDataStream (InputStream& dataSource) | ||||
| { | { | ||||
| MemoryOutputStream mo; | MemoryOutputStream mo; | ||||
| mo.writeFromInputStream (dataSource, -1); | |||||
| mo << dataSource; | |||||
| return createFromImageData (mo.getData(), mo.getDataSize()); | return createFromImageData (mo.getData(), mo.getDataSize()); | ||||
| } | } | ||||
| Drawable* Drawable::createFromImageFile (const File& file) | Drawable* Drawable::createFromImageFile (const File& file) | ||||
| { | { | ||||
| const ScopedPointer <FileInputStream> fin (file.createInputStream()); | |||||
| FileInputStream fin (file); | |||||
| return fin != nullptr ? createFromImageDataStream (*fin) : nullptr; | |||||
| return fin.openedOk() ? createFromImageDataStream (fin) : nullptr; | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -387,12 +387,12 @@ void ScrollBar::mouseWheelMove (const MouseEvent&, | |||||
| float wheelIncrementX, | float wheelIncrementX, | ||||
| float wheelIncrementY) | float wheelIncrementY) | ||||
| { | { | ||||
| float increment = vertical ? wheelIncrementY : wheelIncrementX; | |||||
| float increment = 10.0f * (vertical ? wheelIncrementY : wheelIncrementX); | |||||
| if (increment < 0) | if (increment < 0) | ||||
| increment = jmin (increment * 10.0f, -1.0f); | |||||
| increment = jmin (increment, -1.0f); | |||||
| else if (increment > 0) | else if (increment > 0) | ||||
| increment = jmax (increment * 10.0f, 1.0f); | |||||
| increment = jmax (increment, 1.0f); | |||||
| setCurrentRange (visibleRange - singleStepSize * increment); | setCurrentRange (visibleRange - singleStepSize * increment); | ||||
| } | } | ||||
| @@ -419,20 +419,15 @@ bool ScrollBar::keyPressed (const KeyPress& key) | |||||
| if (! isVisible()) | if (! isVisible()) | ||||
| return false; | return false; | ||||
| if (key.isKeyCode (KeyPress::upKey) || key.isKeyCode (KeyPress::leftKey)) | |||||
| moveScrollbarInSteps (-1); | |||||
| else if (key.isKeyCode (KeyPress::downKey) || key.isKeyCode (KeyPress::rightKey)) | |||||
| moveScrollbarInSteps (1); | |||||
| else if (key.isKeyCode (KeyPress::pageUpKey)) | |||||
| moveScrollbarInPages (-1); | |||||
| else if (key.isKeyCode (KeyPress::pageDownKey)) | |||||
| moveScrollbarInPages (1); | |||||
| else if (key.isKeyCode (KeyPress::homeKey)) | |||||
| scrollToTop(); | |||||
| else if (key.isKeyCode (KeyPress::endKey)) | |||||
| scrollToBottom(); | |||||
| else | |||||
| return false; | |||||
| if (key.isKeyCode (KeyPress::upKey) | |||||
| || key.isKeyCode (KeyPress::leftKey)) moveScrollbarInSteps (-1); | |||||
| else if (key.isKeyCode (KeyPress::downKey) | |||||
| || key.isKeyCode (KeyPress::rightKey)) moveScrollbarInSteps (1); | |||||
| else if (key.isKeyCode (KeyPress::pageUpKey)) moveScrollbarInPages (-1); | |||||
| else if (key.isKeyCode (KeyPress::pageDownKey)) moveScrollbarInPages (1); | |||||
| else if (key.isKeyCode (KeyPress::homeKey)) scrollToTop(); | |||||
| else if (key.isKeyCode (KeyPress::endKey)) scrollToBottom(); | |||||
| else return false; | |||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -30,7 +30,6 @@ class Slider::PopupDisplayComponent : public BubbleComponent, | |||||
| public Timer | public Timer | ||||
| { | { | ||||
| public: | public: | ||||
| //============================================================================== | |||||
| PopupDisplayComponent (Slider& owner_) | PopupDisplayComponent (Slider& owner_) | ||||
| : owner (owner_), | : owner (owner_), | ||||
| font (15.0f, Font::bold) | font (15.0f, Font::bold) | ||||
| @@ -53,12 +52,7 @@ public: | |||||
| void updatePosition (const String& newText) | void updatePosition (const String& newText) | ||||
| { | { | ||||
| if (text != newText) | |||||
| { | |||||
| text = newText; | |||||
| repaint(); | |||||
| } | |||||
| text = newText; | |||||
| BubbleComponent::setPosition (&owner); | BubbleComponent::setPosition (&owner); | ||||
| repaint(); | repaint(); | ||||
| } | } | ||||
| @@ -962,36 +956,18 @@ void Slider::resized() | |||||
| if (incDecButtonsSideBySide) | if (incDecButtonsSideBySide) | ||||
| { | { | ||||
| decButton->setBounds (buttonRect.getX(), | |||||
| buttonRect.getY(), | |||||
| buttonRect.getWidth() / 2, | |||||
| buttonRect.getHeight()); | |||||
| decButton->setBounds (buttonRect.removeFromLeft (buttonRect.getWidth() / 2)); | |||||
| decButton->setConnectedEdges (Button::ConnectedOnRight); | decButton->setConnectedEdges (Button::ConnectedOnRight); | ||||
| incButton->setBounds (buttonRect.getCentreX(), | |||||
| buttonRect.getY(), | |||||
| buttonRect.getWidth() / 2, | |||||
| buttonRect.getHeight()); | |||||
| incButton->setConnectedEdges (Button::ConnectedOnLeft); | incButton->setConnectedEdges (Button::ConnectedOnLeft); | ||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| incButton->setBounds (buttonRect.getX(), | |||||
| buttonRect.getY(), | |||||
| buttonRect.getWidth(), | |||||
| buttonRect.getHeight() / 2); | |||||
| incButton->setConnectedEdges (Button::ConnectedOnBottom); | |||||
| decButton->setBounds (buttonRect.getX(), | |||||
| buttonRect.getCentreY(), | |||||
| buttonRect.getWidth(), | |||||
| buttonRect.getHeight() / 2); | |||||
| decButton->setBounds (buttonRect.removeFromBottom (buttonRect.getHeight() / 2)); | |||||
| decButton->setConnectedEdges (Button::ConnectedOnTop); | decButton->setConnectedEdges (Button::ConnectedOnTop); | ||||
| incButton->setConnectedEdges (Button::ConnectedOnBottom); | |||||
| } | } | ||||
| incButton->setBounds (buttonRect); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1000,52 +976,90 @@ void Slider::focusOfChildComponentChanged (FocusChangeType) | |||||
| repaint(); | repaint(); | ||||
| } | } | ||||
| static void sliderMenuCallback (int result, Slider* slider) | |||||
| namespace SliderHelpers | |||||
| { | { | ||||
| if (slider != nullptr) | |||||
| double smallestAngleBetween (double a1, double a2) noexcept | |||||
| { | |||||
| return jmin (std::abs (a1 - a2), | |||||
| std::abs (a1 + double_Pi * 2.0 - a2), | |||||
| std::abs (a2 + double_Pi * 2.0 - a1)); | |||||
| } | |||||
| void sliderMenuCallback (int result, Slider* slider) | |||||
| { | { | ||||
| switch (result) | |||||
| if (slider != nullptr) | |||||
| { | { | ||||
| case 1: slider->setVelocityBasedMode (! slider->getVelocityBasedMode()); break; | |||||
| case 2: slider->setSliderStyle (Slider::Rotary); break; | |||||
| case 3: slider->setSliderStyle (Slider::RotaryHorizontalDrag); break; | |||||
| case 4: slider->setSliderStyle (Slider::RotaryVerticalDrag); break; | |||||
| default: break; | |||||
| switch (result) | |||||
| { | |||||
| case 1: slider->setVelocityBasedMode (! slider->getVelocityBasedMode()); break; | |||||
| case 2: slider->setSliderStyle (Slider::Rotary); break; | |||||
| case 3: slider->setSliderStyle (Slider::RotaryHorizontalDrag); break; | |||||
| case 4: slider->setSliderStyle (Slider::RotaryVerticalDrag); break; | |||||
| default: break; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| void Slider::showPopupMenu() | |||||
| { | |||||
| menuShown = true; | |||||
| PopupMenu m; | |||||
| m.setLookAndFeel (&getLookAndFeel()); | |||||
| m.addItem (1, TRANS ("velocity-sensitive mode"), true, isVelocityBased); | |||||
| m.addSeparator(); | |||||
| if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag) | |||||
| { | |||||
| PopupMenu rotaryMenu; | |||||
| rotaryMenu.addItem (2, TRANS ("use circular dragging"), true, style == Rotary); | |||||
| rotaryMenu.addItem (3, TRANS ("use left-right dragging"), true, style == RotaryHorizontalDrag); | |||||
| rotaryMenu.addItem (4, TRANS ("use up-down dragging"), true, style == RotaryVerticalDrag); | |||||
| m.addSubMenu (TRANS ("rotary mode"), rotaryMenu); | |||||
| } | |||||
| m.showMenuAsync (PopupMenu::Options(), | |||||
| ModalCallbackFunction::forComponent (SliderHelpers::sliderMenuCallback, this)); | |||||
| } | |||||
| int Slider::getThumbIndexAt (const MouseEvent& e) | |||||
| { | |||||
| const bool isTwoValue = (style == TwoValueHorizontal || style == TwoValueVertical); | |||||
| const bool isThreeValue = (style == ThreeValueHorizontal || style == ThreeValueVertical); | |||||
| if (isTwoValue || isThreeValue) | |||||
| { | |||||
| const float mousePos = (float) (isVertical() ? e.y : e.x); | |||||
| const float normalPosDistance = std::abs (getLinearSliderPos (currentValue.getValue()) - mousePos); | |||||
| const float minPosDistance = std::abs (getLinearSliderPos (valueMin.getValue()) - 0.1f - mousePos); | |||||
| const float maxPosDistance = std::abs (getLinearSliderPos (valueMax.getValue()) + 0.1f - mousePos); | |||||
| if (isTwoValue) | |||||
| return maxPosDistance <= minPosDistance ? 2 : 1; | |||||
| if (normalPosDistance >= minPosDistance && maxPosDistance >= minPosDistance) | |||||
| return 1; | |||||
| else if (normalPosDistance >= maxPosDistance) | |||||
| return 2; | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| void Slider::mouseDown (const MouseEvent& e) | void Slider::mouseDown (const MouseEvent& e) | ||||
| { | { | ||||
| mouseWasHidden = false; | mouseWasHidden = false; | ||||
| incDecDragged = false; | incDecDragged = false; | ||||
| mousePosWhenLastDragged = e.getPosition(); | |||||
| mouseDragStartX = e.getMouseDownX(); | |||||
| mouseDragStartY = e.getMouseDownY(); | |||||
| mouseDragStartPos = mousePosWhenLastDragged = e.getPosition(); | |||||
| if (isEnabled()) | if (isEnabled()) | ||||
| { | { | ||||
| if (e.mods.isPopupMenu() && menuEnabled) | if (e.mods.isPopupMenu() && menuEnabled) | ||||
| { | { | ||||
| menuShown = true; | |||||
| PopupMenu m; | |||||
| m.setLookAndFeel (&getLookAndFeel()); | |||||
| m.addItem (1, TRANS ("velocity-sensitive mode"), true, isVelocityBased); | |||||
| m.addSeparator(); | |||||
| if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag) | |||||
| { | |||||
| PopupMenu rotaryMenu; | |||||
| rotaryMenu.addItem (2, TRANS ("use circular dragging"), true, style == Rotary); | |||||
| rotaryMenu.addItem (3, TRANS ("use left-right dragging"), true, style == RotaryHorizontalDrag); | |||||
| rotaryMenu.addItem (4, TRANS ("use up-down dragging"), true, style == RotaryVerticalDrag); | |||||
| m.addSubMenu (TRANS ("rotary mode"), rotaryMenu); | |||||
| } | |||||
| m.showMenuAsync (PopupMenu::Options(), | |||||
| ModalCallbackFunction::forComponent (sliderMenuCallback, this)); | |||||
| showPopupMenu(); | |||||
| } | } | ||||
| else if (maximum > minimum) | else if (maximum > minimum) | ||||
| { | { | ||||
| @@ -1054,44 +1068,16 @@ void Slider::mouseDown (const MouseEvent& e) | |||||
| if (valueBox != nullptr) | if (valueBox != nullptr) | ||||
| valueBox->hideEditor (true); | valueBox->hideEditor (true); | ||||
| sliderBeingDragged = 0; | |||||
| if (style == TwoValueHorizontal | |||||
| || style == TwoValueVertical | |||||
| || style == ThreeValueHorizontal | |||||
| || style == ThreeValueVertical) | |||||
| { | |||||
| const float mousePos = (float) (isVertical() ? e.y : e.x); | |||||
| const float normalPosDistance = std::abs (getLinearSliderPos (currentValue.getValue()) - mousePos); | |||||
| const float minPosDistance = std::abs (getLinearSliderPos (valueMin.getValue()) - 0.1f - mousePos); | |||||
| const float maxPosDistance = std::abs (getLinearSliderPos (valueMax.getValue()) + 0.1f - mousePos); | |||||
| if (style == TwoValueHorizontal || style == TwoValueVertical) | |||||
| { | |||||
| if (maxPosDistance <= minPosDistance) | |||||
| sliderBeingDragged = 2; | |||||
| else | |||||
| sliderBeingDragged = 1; | |||||
| } | |||||
| else if (style == ThreeValueHorizontal || style == ThreeValueVertical) | |||||
| { | |||||
| if (normalPosDistance >= minPosDistance && maxPosDistance >= minPosDistance) | |||||
| sliderBeingDragged = 1; | |||||
| else if (normalPosDistance >= maxPosDistance) | |||||
| sliderBeingDragged = 2; | |||||
| } | |||||
| } | |||||
| sliderBeingDragged = getThumbIndexAt (e); | |||||
| minMaxDiff = (double) valueMax.getValue() - (double) valueMin.getValue(); | minMaxDiff = (double) valueMax.getValue() - (double) valueMin.getValue(); | ||||
| lastAngle = rotaryStart + (rotaryEnd - rotaryStart) | lastAngle = rotaryStart + (rotaryEnd - rotaryStart) | ||||
| * valueToProportionOfLength (currentValue.getValue()); | * valueToProportionOfLength (currentValue.getValue()); | ||||
| valueWhenLastDragged = ((sliderBeingDragged == 2) ? valueMax | |||||
| : ((sliderBeingDragged == 1) ? valueMin | |||||
| : currentValue)).getValue(); | |||||
| valueWhenLastDragged = (sliderBeingDragged == 2 ? valueMax | |||||
| : (sliderBeingDragged == 1 ? valueMin | |||||
| : currentValue)).getValue(); | |||||
| valueOnMouseDown = valueWhenLastDragged; | valueOnMouseDown = valueWhenLastDragged; | ||||
| if (popupDisplayEnabled) | if (popupDisplayEnabled) | ||||
| @@ -1108,7 +1094,6 @@ void Slider::mouseDown (const MouseEvent& e) | |||||
| } | } | ||||
| sendDragStart(); | sendDragStart(); | ||||
| mouseDrag (e); | mouseDrag (e); | ||||
| } | } | ||||
| } | } | ||||
| @@ -1150,10 +1135,9 @@ void Slider::restoreMouseIfHidden() | |||||
| for (int i = Desktop::getInstance().getNumMouseSources(); --i >= 0;) | for (int i = Desktop::getInstance().getNumMouseSources(); --i >= 0;) | ||||
| Desktop::getInstance().getMouseSource(i)->enableUnboundedMouseMovement (false); | Desktop::getInstance().getMouseSource(i)->enableUnboundedMouseMovement (false); | ||||
| const double pos = (sliderBeingDragged == 2) ? getMaxValue() | |||||
| : ((sliderBeingDragged == 1) ? getMinValue() | |||||
| : (double) currentValue.getValue()); | |||||
| const double pos = sliderBeingDragged == 2 ? getMaxValue() | |||||
| : (sliderBeingDragged == 1 ? getMinValue() | |||||
| : (double) currentValue.getValue()); | |||||
| Point<int> mousePos; | Point<int> mousePos; | ||||
| if (style == RotaryHorizontalDrag || style == RotaryVerticalDrag) | if (style == RotaryHorizontalDrag || style == RotaryVerticalDrag) | ||||
| @@ -1194,158 +1178,154 @@ void Slider::modifierKeysChanged (const ModifierKeys& modifiers) | |||||
| } | } | ||||
| } | } | ||||
| namespace SliderHelpers | |||||
| void Slider::handleRotaryDrag (const MouseEvent& e) | |||||
| { | { | ||||
| double smallestAngleBetween (double a1, double a2) noexcept | |||||
| const int dx = e.x - sliderRect.getCentreX(); | |||||
| const int dy = e.y - sliderRect.getCentreY(); | |||||
| if (dx * dx + dy * dy > 25) | |||||
| { | { | ||||
| return jmin (std::abs (a1 - a2), | |||||
| std::abs (a1 + double_Pi * 2.0 - a2), | |||||
| std::abs (a2 + double_Pi * 2.0 - a1)); | |||||
| double angle = std::atan2 ((double) dx, (double) -dy); | |||||
| while (angle < 0.0) | |||||
| angle += double_Pi * 2.0; | |||||
| if (rotaryStop && ! e.mouseWasClicked()) | |||||
| { | |||||
| if (std::abs (angle - lastAngle) > double_Pi) | |||||
| { | |||||
| if (angle >= lastAngle) | |||||
| angle -= double_Pi * 2.0; | |||||
| else | |||||
| angle += double_Pi * 2.0; | |||||
| } | |||||
| if (angle >= lastAngle) | |||||
| angle = jmin (angle, (double) jmax (rotaryStart, rotaryEnd)); | |||||
| else | |||||
| angle = jmax (angle, (double) jmin (rotaryStart, rotaryEnd)); | |||||
| } | |||||
| else | |||||
| { | |||||
| while (angle < rotaryStart) | |||||
| angle += double_Pi * 2.0; | |||||
| if (angle > rotaryEnd) | |||||
| { | |||||
| if (SliderHelpers::smallestAngleBetween (angle, rotaryStart) | |||||
| <= SliderHelpers::smallestAngleBetween (angle, rotaryEnd)) | |||||
| angle = rotaryStart; | |||||
| else | |||||
| angle = rotaryEnd; | |||||
| } | |||||
| } | |||||
| const double proportion = (angle - rotaryStart) / (rotaryEnd - rotaryStart); | |||||
| valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, proportion)); | |||||
| lastAngle = angle; | |||||
| } | } | ||||
| } | } | ||||
| void Slider::mouseDrag (const MouseEvent& e) | |||||
| void Slider::handleAbsoluteDrag (const MouseEvent& e) | |||||
| { | { | ||||
| if (isEnabled() | |||||
| && (! menuShown) | |||||
| && (maximum > minimum)) | |||||
| const int mousePos = (isHorizontal() || style == RotaryHorizontalDrag) ? e.x : e.y; | |||||
| double scaledMousePos = (mousePos - sliderRegionStart) / (double) sliderRegionSize; | |||||
| if (style == RotaryHorizontalDrag | |||||
| || style == RotaryVerticalDrag | |||||
| || style == IncDecButtons | |||||
| || ((style == LinearHorizontal || style == LinearVertical || style == LinearBar) | |||||
| && ! snapsToMousePos)) | |||||
| { | { | ||||
| if (style == Rotary) | |||||
| const int mouseDiff = (style == RotaryHorizontalDrag | |||||
| || style == LinearHorizontal | |||||
| || style == LinearBar | |||||
| || (style == IncDecButtons && incDecDragDirectionIsHorizontal())) | |||||
| ? e.x - mouseDragStartPos.getX() | |||||
| : mouseDragStartPos.getY() - e.y; | |||||
| double newPos = valueToProportionOfLength (valueOnMouseDown) | |||||
| + mouseDiff * (1.0 / pixelsForFullDragExtent); | |||||
| valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, newPos)); | |||||
| if (style == IncDecButtons) | |||||
| { | { | ||||
| const int dx = e.x - sliderRect.getCentreX(); | |||||
| const int dy = e.y - sliderRect.getCentreY(); | |||||
| incButton->setState (mouseDiff < 0 ? Button::buttonNormal : Button::buttonDown); | |||||
| decButton->setState (mouseDiff > 0 ? Button::buttonNormal : Button::buttonDown); | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| if (isVertical()) | |||||
| scaledMousePos = 1.0 - scaledMousePos; | |||||
| if (dx * dx + dy * dy > 25) | |||||
| { | |||||
| double angle = std::atan2 ((double) dx, (double) -dy); | |||||
| while (angle < 0.0) | |||||
| angle += double_Pi * 2.0; | |||||
| valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, scaledMousePos)); | |||||
| } | |||||
| } | |||||
| if (rotaryStop && ! e.mouseWasClicked()) | |||||
| { | |||||
| if (std::abs (angle - lastAngle) > double_Pi) | |||||
| { | |||||
| if (angle >= lastAngle) | |||||
| angle -= double_Pi * 2.0; | |||||
| else | |||||
| angle += double_Pi * 2.0; | |||||
| } | |||||
| if (angle >= lastAngle) | |||||
| angle = jmin (angle, (double) jmax (rotaryStart, rotaryEnd)); | |||||
| else | |||||
| angle = jmax (angle, (double) jmin (rotaryStart, rotaryEnd)); | |||||
| } | |||||
| else | |||||
| { | |||||
| while (angle < rotaryStart) | |||||
| angle += double_Pi * 2.0; | |||||
| void Slider::handleVelocityDrag (const MouseEvent& e) | |||||
| { | |||||
| const int mouseDiff = (isHorizontal() || style == RotaryHorizontalDrag | |||||
| || (style == IncDecButtons && incDecDragDirectionIsHorizontal())) | |||||
| ? e.x - mousePosWhenLastDragged.getX() | |||||
| : e.y - mousePosWhenLastDragged.getY(); | |||||
| const double maxSpeed = jmax (200, sliderRegionSize); | |||||
| double speed = jlimit (0.0, maxSpeed, (double) abs (mouseDiff)); | |||||
| if (angle > rotaryEnd) | |||||
| { | |||||
| if (SliderHelpers::smallestAngleBetween (angle, rotaryStart) | |||||
| <= SliderHelpers::smallestAngleBetween (angle, rotaryEnd)) | |||||
| angle = rotaryStart; | |||||
| else | |||||
| angle = rotaryEnd; | |||||
| } | |||||
| } | |||||
| if (speed != 0) | |||||
| { | |||||
| speed = 0.2 * velocityModeSensitivity | |||||
| * (1.0 + std::sin (double_Pi * (1.5 + jmin (0.5, velocityModeOffset | |||||
| + jmax (0.0, (double) (speed - velocityModeThreshold)) | |||||
| / maxSpeed)))); | |||||
| const double proportion = (angle - rotaryStart) / (rotaryEnd - rotaryStart); | |||||
| if (mouseDiff < 0) | |||||
| speed = -speed; | |||||
| valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, proportion)); | |||||
| if (isVertical() || style == RotaryVerticalDrag | |||||
| || (style == IncDecButtons && ! incDecDragDirectionIsHorizontal())) | |||||
| speed = -speed; | |||||
| lastAngle = angle; | |||||
| } | |||||
| const double currentPos = valueToProportionOfLength (valueWhenLastDragged); | |||||
| valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + speed)); | |||||
| e.source.enableUnboundedMouseMovement (true, false); | |||||
| mouseWasHidden = true; | |||||
| } | |||||
| } | |||||
| void Slider::mouseDrag (const MouseEvent& e) | |||||
| { | |||||
| if (isEnabled() | |||||
| && (! menuShown) | |||||
| && (maximum > minimum) | |||||
| && ! (style == LinearBar && e.mouseWasClicked() && valueBox != nullptr && valueBox->isEditable())) | |||||
| { | |||||
| if (style == Rotary) | |||||
| { | |||||
| handleRotaryDrag (e); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| if (style == LinearBar && e.mouseWasClicked() | |||||
| && valueBox != nullptr && valueBox->isEditable()) | |||||
| return; | |||||
| if (style == IncDecButtons && ! incDecDragged) | if (style == IncDecButtons && ! incDecDragged) | ||||
| { | { | ||||
| if (e.getDistanceFromDragStart() < 10 || e.mouseWasClicked()) | if (e.getDistanceFromDragStart() < 10 || e.mouseWasClicked()) | ||||
| return; | return; | ||||
| incDecDragged = true; | incDecDragged = true; | ||||
| mouseDragStartX = e.x; | |||||
| mouseDragStartY = e.y; | |||||
| mouseDragStartPos = e.getPosition(); | |||||
| } | } | ||||
| if ((isVelocityBased == (userKeyOverridesVelocity ? e.mods.testFlags (ModifierKeys::ctrlModifier | ModifierKeys::commandModifier | ModifierKeys::altModifier) | |||||
| : false)) | |||||
| || ((maximum - minimum) / sliderRegionSize < interval)) | |||||
| { | |||||
| const int mousePos = (isHorizontal() || style == RotaryHorizontalDrag) ? e.x : e.y; | |||||
| double scaledMousePos = (mousePos - sliderRegionStart) / (double) sliderRegionSize; | |||||
| if (style == RotaryHorizontalDrag | |||||
| || style == RotaryVerticalDrag | |||||
| || style == IncDecButtons | |||||
| || ((style == LinearHorizontal || style == LinearVertical || style == LinearBar) | |||||
| && ! snapsToMousePos)) | |||||
| { | |||||
| const int mouseDiff = (style == RotaryHorizontalDrag | |||||
| || style == LinearHorizontal | |||||
| || style == LinearBar | |||||
| || (style == IncDecButtons && incDecDragDirectionIsHorizontal())) | |||||
| ? e.x - mouseDragStartX | |||||
| : mouseDragStartY - e.y; | |||||
| double newPos = valueToProportionOfLength (valueOnMouseDown) | |||||
| + mouseDiff * (1.0 / pixelsForFullDragExtent); | |||||
| valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, newPos)); | |||||
| if (style == IncDecButtons) | |||||
| { | |||||
| incButton->setState (mouseDiff < 0 ? Button::buttonNormal : Button::buttonDown); | |||||
| decButton->setState (mouseDiff > 0 ? Button::buttonNormal : Button::buttonDown); | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| if (isVertical()) | |||||
| scaledMousePos = 1.0 - scaledMousePos; | |||||
| valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, scaledMousePos)); | |||||
| } | |||||
| } | |||||
| if (isVelocityBased == (userKeyOverridesVelocity && e.mods.testFlags (ModifierKeys::ctrlModifier | |||||
| | ModifierKeys::commandModifier | |||||
| | ModifierKeys::altModifier)) | |||||
| || (maximum - minimum) / sliderRegionSize < interval) | |||||
| handleAbsoluteDrag (e); | |||||
| else | else | ||||
| { | |||||
| const int mouseDiff = (isHorizontal() || style == RotaryHorizontalDrag | |||||
| || (style == IncDecButtons && incDecDragDirectionIsHorizontal())) | |||||
| ? e.x - mousePosWhenLastDragged.getX() | |||||
| : e.y - mousePosWhenLastDragged.getY(); | |||||
| const double maxSpeed = jmax (200, sliderRegionSize); | |||||
| double speed = jlimit (0.0, maxSpeed, (double) abs (mouseDiff)); | |||||
| if (speed != 0) | |||||
| { | |||||
| speed = 0.2 * velocityModeSensitivity | |||||
| * (1.0 + std::sin (double_Pi * (1.5 + jmin (0.5, velocityModeOffset | |||||
| + jmax (0.0, (double) (speed - velocityModeThreshold)) | |||||
| / maxSpeed)))); | |||||
| if (mouseDiff < 0) | |||||
| speed = -speed; | |||||
| if (isVertical() || style == RotaryVerticalDrag | |||||
| || (style == IncDecButtons && ! incDecDragDirectionIsHorizontal())) | |||||
| speed = -speed; | |||||
| const double currentPos = valueToProportionOfLength (valueWhenLastDragged); | |||||
| valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + speed)); | |||||
| e.source.enableUnboundedMouseMovement (true, false); | |||||
| mouseWasHidden = true; | |||||
| } | |||||
| } | |||||
| handleVelocityDrag (e); | |||||
| } | } | ||||
| valueWhenLastDragged = jlimit (minimum, maximum, valueWhenLastDragged); | valueWhenLastDragged = jlimit (minimum, maximum, valueWhenLastDragged); | ||||
| @@ -827,8 +827,7 @@ private: | |||||
| int velocityModeThreshold; | int velocityModeThreshold; | ||||
| float rotaryStart, rotaryEnd; | float rotaryStart, rotaryEnd; | ||||
| int numDecimalPlaces; | int numDecimalPlaces; | ||||
| Point<int> mousePosWhenLastDragged; | |||||
| int mouseDragStartX, mouseDragStartY; | |||||
| Point<int> mouseDragStartPos, mousePosWhenLastDragged; | |||||
| int sliderRegionStart, sliderRegionSize; | int sliderRegionStart, sliderRegionSize; | ||||
| int sliderBeingDragged; | int sliderBeingDragged; | ||||
| int pixelsForFullDragExtent; | int pixelsForFullDragExtent; | ||||
| @@ -854,6 +853,11 @@ private: | |||||
| ScopedPointer <PopupDisplayComponent> popupDisplay; | ScopedPointer <PopupDisplayComponent> popupDisplay; | ||||
| Component* parentForPopupDisplay; | Component* parentForPopupDisplay; | ||||
| void showPopupMenu(); | |||||
| int getThumbIndexAt (const MouseEvent&); | |||||
| void handleRotaryDrag (const MouseEvent&); | |||||
| void handleAbsoluteDrag (const MouseEvent&); | |||||
| void handleVelocityDrag (const MouseEvent&); | |||||
| float getLinearSliderPos (double value); | float getLinearSliderPos (double value); | ||||
| void restoreMouseIfHidden(); | void restoreMouseIfHidden(); | ||||
| void sendDragStart(); | void sendDragStart(); | ||||