| @@ -688,27 +688,21 @@ FileInputStream* File::createInputStream() 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, | |||
| 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, | |||
| @@ -728,15 +722,13 @@ bool File::appendText (const String& text, | |||
| const bool asUnicode, | |||
| 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, | |||
| @@ -53,6 +53,8 @@ int64 FileInputStream::getTotalLength() | |||
| int FileInputStream::read (void* buffer, int bytesToRead) | |||
| { | |||
| jassert (buffer != nullptr && bytesToRead >= 0); | |||
| if (needToSeek) | |||
| { | |||
| 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 | |||
| 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(); | |||
| @@ -67,7 +78,6 @@ public: | |||
| int64 getPosition(); | |||
| bool setPosition (int64 pos); | |||
| private: | |||
| //============================================================================== | |||
| File file; | |||
| @@ -84,6 +84,8 @@ void FileOutputStream::flush() | |||
| bool FileOutputStream::write (const void* const src, const int numBytes) | |||
| { | |||
| jassert (src != nullptr && numBytes >= 0); | |||
| if (bytesInBuffer + numBytes < bufferSize) | |||
| { | |||
| memcpy (buffer + bytesInBuffer, src, (size_t) numBytes); | |||
| @@ -51,9 +51,6 @@ public: | |||
| use File::deleteFile() before opening the stream, or use setPosition(0) | |||
| 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 | |||
| */ | |||
| FileOutputStream (const File& fileToWriteTo, | |||
| @@ -71,12 +68,17 @@ public: | |||
| 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. | |||
| */ | |||
| 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 { 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(); | |||
| @@ -136,6 +136,8 @@ public: | |||
| int read (void* buffer, int bytesToRead) | |||
| { | |||
| jassert (buffer != nullptr && bytesToRead >= 0); | |||
| if (stream == nullptr) | |||
| return 0; | |||
| @@ -361,6 +361,8 @@ public: | |||
| int read (void* buffer, int bytesToRead) | |||
| { | |||
| jassert (buffer != nullptr && bytesToRead >= 0); | |||
| if (finished || isError()) | |||
| { | |||
| return 0; | |||
| @@ -988,6 +988,8 @@ public: | |||
| int read (void* const dest, const int numBytes) | |||
| { | |||
| jassert (dest != nullptr); | |||
| if (readHandle == 0 && childPID != 0) | |||
| readHandle = fdopen (pipeHandle, "r"); | |||
| @@ -145,6 +145,7 @@ public: | |||
| int read (void* buffer, int bytesToRead) | |||
| { | |||
| jassert (buffer != nullptr && bytesToRead >= 0); | |||
| DWORD bytesRead = 0; | |||
| if (! (finished || isError())) | |||
| @@ -131,6 +131,8 @@ void BufferedInputStream::ensureBuffered() | |||
| int BufferedInputStream::read (void* destBuffer, int maxBytesToRead) | |||
| { | |||
| jassert (destBuffer != nullptr && maxBytesToRead >= 0); | |||
| if (position >= bufferStart | |||
| && position + maxBytesToRead <= lastReadPos) | |||
| { | |||
| @@ -208,7 +208,7 @@ int InputStream::readIntoMemoryBlock (MemoryBlock& block, int numBytes) | |||
| String InputStream::readEntireStreamAsString() | |||
| { | |||
| MemoryOutputStream mo; | |||
| mo.writeFromInputStream (*this, -1); | |||
| mo << *this; | |||
| return mo.toString(); | |||
| } | |||
| @@ -59,16 +59,16 @@ public: | |||
| 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 | |||
| 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). | |||
| @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 | |||
| 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 | |||
| 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) | |||
| { | |||
| jassert (howMany >= 0); | |||
| jassert (buffer != nullptr && howMany >= 0); | |||
| const int num = jmin (howMany, (int) (dataSize - position)); | |||
| if (num <= 0) | |||
| return 0; | |||
| @@ -81,6 +81,8 @@ void MemoryOutputStream::prepareToWrite (int numBytes) | |||
| bool MemoryOutputStream::write (const void* const buffer, int howMany) | |||
| { | |||
| jassert (buffer != nullptr && howMany >= 0); | |||
| if (howMany > 0) | |||
| { | |||
| prepareToWrite (howMany); | |||
| @@ -298,11 +298,13 @@ OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const MemoryBlock& | |||
| 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; | |||
| } | |||
| @@ -83,10 +83,12 @@ public: | |||
| that needs to be overloaded - the base class has methods for writing other | |||
| 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 | |||
| */ | |||
| virtual bool write (const void* dataToWrite, | |||
| int howManyBytes) = 0; | |||
| int numberOfBytes) = 0; | |||
| //============================================================================== | |||
| /** 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. */ | |||
| 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. | |||
| You can use the predefined symbol 'newLine' to invoke this, e.g. | |||
| @code | |||
| @@ -61,6 +61,8 @@ bool SubregionStream::setPosition (int64 newPosition) | |||
| int SubregionStream::read (void* destBuffer, int maxBytesToRead) | |||
| { | |||
| jassert (destBuffer != nullptr && maxBytesToRead >= 0); | |||
| if (lengthOfSourceStream < 0) | |||
| { | |||
| return source->read (destBuffer, maxBytesToRead); | |||
| @@ -42,14 +42,12 @@ class GZIPCompressorOutputStream::GZIPCompressorHelper | |||
| { | |||
| public: | |||
| GZIPCompressorHelper (const int compressionLevel, const int windowBits) | |||
| : data (nullptr), | |||
| dataSize (0), | |||
| : buffer ((size_t) gzipCompBufferSize), | |||
| compLevel (compressionLevel), | |||
| strategy (0), | |||
| setParams (true), | |||
| isFirstDeflate (true), | |||
| streamIsValid (false), | |||
| finished (false), | |||
| shouldFinish (false) | |||
| finished (false) | |||
| { | |||
| using namespace zlibNamespace; | |||
| zerostruct (stream); | |||
| @@ -61,67 +59,74 @@ public: | |||
| ~GZIPCompressorHelper() | |||
| { | |||
| using namespace zlibNamespace; | |||
| 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; | |||
| 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) | |||
| { | |||
| 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, | |||
| const bool deleteDestStream, | |||
| const int windowBits) | |||
| : destStream (destStream_, deleteDestStream), | |||
| buffer ((size_t) GZIPCompressorHelper::gzipCompBufferSize) | |||
| : destStream (destStream_, deleteDestStream) | |||
| { | |||
| jassert (destStream_ != nullptr); | |||
| if (compressionLevel < 1 || compressionLevel > 9) | |||
| compressionLevel = -1; | |||
| @@ -140,48 +146,20 @@ GZIPCompressorOutputStream::GZIPCompressorOutputStream (OutputStream* const dest | |||
| GZIPCompressorOutputStream::~GZIPCompressorOutputStream() | |||
| { | |||
| flushInternal(); | |||
| } | |||
| //============================================================================== | |||
| void GZIPCompressorOutputStream::flushInternal() | |||
| { | |||
| if (! helper->finished) | |||
| { | |||
| helper->shouldFinish = true; | |||
| while (! helper->finished) | |||
| doNextBlock(); | |||
| } | |||
| destStream->flush(); | |||
| flush(); | |||
| } | |||
| void GZIPCompressorOutputStream::flush() | |||
| { | |||
| flushInternal(); | |||
| helper->finish (*destStream); | |||
| destStream->flush(); | |||
| } | |||
| 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() | |||
| @@ -231,7 +209,7 @@ public: | |||
| MemoryInputStream compressedInput (compressed.getData(), compressed.getDataSize(), false); | |||
| GZIPDecompressorInputStream unzipper (compressedInput); | |||
| uncompressed.writeFromInputStream (unzipper, -1); | |||
| uncompressed << unzipper; | |||
| } | |||
| expectEquals ((int) uncompressed.getDataSize(), | |||
| @@ -35,6 +35,10 @@ | |||
| /** | |||
| 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 | |||
| */ | |||
| class JUCE_API GZIPCompressorOutputStream : public OutputStream | |||
| @@ -64,7 +68,13 @@ public: | |||
| ~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(); | |||
| int64 getPosition(); | |||
| bool setPosition (int64 newPosition); | |||
| bool write (const void* destBuffer, int howMany); | |||
| @@ -81,12 +91,10 @@ public: | |||
| private: | |||
| //============================================================================== | |||
| OptionalScopedPointer<OutputStream> destStream; | |||
| HeapBlock <uint8> buffer; | |||
| class GZIPCompressorHelper; | |||
| friend class ScopedPointer <GZIPCompressorHelper>; | |||
| ScopedPointer <GZIPCompressorHelper> helper; | |||
| bool doNextBlock(); | |||
| void flushInternal(); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GZIPCompressorOutputStream); | |||
| }; | |||
| @@ -192,52 +192,49 @@ int64 GZIPDecompressorInputStream::getTotalLength() | |||
| 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; | |||
| } | |||
| 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) | |||
| 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) | |||
| { | |||
| checksum = 0; | |||
| ScopedPointer<FileInputStream> input (file.createInputStream()); | |||
| FileInputStream input (file); | |||
| if (input == nullptr) | |||
| if (input.failedToOpen()) | |||
| return false; | |||
| const int bufferSize = 2048; | |||
| 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) | |||
| return false; | |||
| @@ -145,24 +145,25 @@ public: | |||
| @param targetDirectory the root folder to uncompress to | |||
| @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. | |||
| 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. | |||
| @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 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; | |||
| MemoryOutputStream mb; | |||
| mb.writeFromInputStream (in, -1); | |||
| mb << in; | |||
| Image image; | |||
| @@ -145,16 +145,16 @@ Drawable* Drawable::createFromImageData (const void* data, const size_t numBytes | |||
| Drawable* Drawable::createFromImageDataStream (InputStream& dataSource) | |||
| { | |||
| MemoryOutputStream mo; | |||
| mo.writeFromInputStream (dataSource, -1); | |||
| mo << dataSource; | |||
| return createFromImageData (mo.getData(), mo.getDataSize()); | |||
| } | |||
| 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 wheelIncrementY) | |||
| { | |||
| float increment = vertical ? wheelIncrementY : wheelIncrementX; | |||
| float increment = 10.0f * (vertical ? wheelIncrementY : wheelIncrementX); | |||
| if (increment < 0) | |||
| increment = jmin (increment * 10.0f, -1.0f); | |||
| increment = jmin (increment, -1.0f); | |||
| else if (increment > 0) | |||
| increment = jmax (increment * 10.0f, 1.0f); | |||
| increment = jmax (increment, 1.0f); | |||
| setCurrentRange (visibleRange - singleStepSize * increment); | |||
| } | |||
| @@ -419,20 +419,15 @@ bool ScrollBar::keyPressed (const KeyPress& key) | |||
| if (! isVisible()) | |||
| 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; | |||
| } | |||
| @@ -30,7 +30,6 @@ class Slider::PopupDisplayComponent : public BubbleComponent, | |||
| public Timer | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| PopupDisplayComponent (Slider& owner_) | |||
| : owner (owner_), | |||
| font (15.0f, Font::bold) | |||
| @@ -53,12 +52,7 @@ public: | |||
| void updatePosition (const String& newText) | |||
| { | |||
| if (text != newText) | |||
| { | |||
| text = newText; | |||
| repaint(); | |||
| } | |||
| text = newText; | |||
| BubbleComponent::setPosition (&owner); | |||
| repaint(); | |||
| } | |||
| @@ -962,36 +956,18 @@ void Slider::resized() | |||
| if (incDecButtonsSideBySide) | |||
| { | |||
| decButton->setBounds (buttonRect.getX(), | |||
| buttonRect.getY(), | |||
| buttonRect.getWidth() / 2, | |||
| buttonRect.getHeight()); | |||
| decButton->setBounds (buttonRect.removeFromLeft (buttonRect.getWidth() / 2)); | |||
| decButton->setConnectedEdges (Button::ConnectedOnRight); | |||
| incButton->setBounds (buttonRect.getCentreX(), | |||
| buttonRect.getY(), | |||
| buttonRect.getWidth() / 2, | |||
| buttonRect.getHeight()); | |||
| incButton->setConnectedEdges (Button::ConnectedOnLeft); | |||
| } | |||
| 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); | |||
| incButton->setConnectedEdges (Button::ConnectedOnBottom); | |||
| } | |||
| incButton->setBounds (buttonRect); | |||
| } | |||
| } | |||
| @@ -1000,52 +976,90 @@ void Slider::focusOfChildComponentChanged (FocusChangeType) | |||
| 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) | |||
| { | |||
| mouseWasHidden = false; | |||
| incDecDragged = false; | |||
| mousePosWhenLastDragged = e.getPosition(); | |||
| mouseDragStartX = e.getMouseDownX(); | |||
| mouseDragStartY = e.getMouseDownY(); | |||
| mouseDragStartPos = mousePosWhenLastDragged = e.getPosition(); | |||
| if (isEnabled()) | |||
| { | |||
| 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) | |||
| { | |||
| @@ -1054,44 +1068,16 @@ void Slider::mouseDown (const MouseEvent& e) | |||
| if (valueBox != nullptr) | |||
| 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(); | |||
| lastAngle = rotaryStart + (rotaryEnd - rotaryStart) | |||
| * valueToProportionOfLength (currentValue.getValue()); | |||
| valueWhenLastDragged = ((sliderBeingDragged == 2) ? valueMax | |||
| : ((sliderBeingDragged == 1) ? valueMin | |||
| : currentValue)).getValue(); | |||
| valueWhenLastDragged = (sliderBeingDragged == 2 ? valueMax | |||
| : (sliderBeingDragged == 1 ? valueMin | |||
| : currentValue)).getValue(); | |||
| valueOnMouseDown = valueWhenLastDragged; | |||
| if (popupDisplayEnabled) | |||
| @@ -1108,7 +1094,6 @@ void Slider::mouseDown (const MouseEvent& e) | |||
| } | |||
| sendDragStart(); | |||
| mouseDrag (e); | |||
| } | |||
| } | |||
| @@ -1150,10 +1135,9 @@ void Slider::restoreMouseIfHidden() | |||
| for (int i = Desktop::getInstance().getNumMouseSources(); --i >= 0;) | |||
| 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; | |||
| 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 | |||
| { | |||
| if (style == LinearBar && e.mouseWasClicked() | |||
| && valueBox != nullptr && valueBox->isEditable()) | |||
| return; | |||
| if (style == IncDecButtons && ! incDecDragged) | |||
| { | |||
| if (e.getDistanceFromDragStart() < 10 || e.mouseWasClicked()) | |||
| return; | |||
| 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 | |||
| { | |||
| 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); | |||
| @@ -827,8 +827,7 @@ private: | |||
| int velocityModeThreshold; | |||
| float rotaryStart, rotaryEnd; | |||
| int numDecimalPlaces; | |||
| Point<int> mousePosWhenLastDragged; | |||
| int mouseDragStartX, mouseDragStartY; | |||
| Point<int> mouseDragStartPos, mousePosWhenLastDragged; | |||
| int sliderRegionStart, sliderRegionSize; | |||
| int sliderBeingDragged; | |||
| int pixelsForFullDragExtent; | |||
| @@ -854,6 +853,11 @@ private: | |||
| ScopedPointer <PopupDisplayComponent> popupDisplay; | |||
| Component* parentForPopupDisplay; | |||
| void showPopupMenu(); | |||
| int getThumbIndexAt (const MouseEvent&); | |||
| void handleRotaryDrag (const MouseEvent&); | |||
| void handleAbsoluteDrag (const MouseEvent&); | |||
| void handleVelocityDrag (const MouseEvent&); | |||
| float getLinearSliderPos (double value); | |||
| void restoreMouseIfHidden(); | |||
| void sendDragStart(); | |||