Signed-off-by: falkTX <falktx@falktx.com>tags/2021-03-15
@@ -1,16 +1,16 @@ | |||
diff --git a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp b/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp | |||
index 0c1138a2b..a5b42b8ef 100644 | |||
--- a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp | |||
+++ b/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp | |||
@@ -172,6 +172,7 @@ private: | |||
diff --git a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp b/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp | |||
index 0c1138a2b..a5b42b8ef 100644 | |||
--- a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp | |||
+++ b/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp | |||
@@ -172,6 +172,7 @@ private: | |||
void operator() (LPWSTR ptr) const noexcept { CoTaskMemFree (ptr); } | |||
}; | |||
+ #if JUCE_MSVC | |||
bool showDialog (IFileDialog& dialog, bool async) const | |||
bool showDialog (IFileDialog& dialog, bool async) | |||
{ | |||
FILEOPENDIALOGOPTIONS flags = {}; | |||
@@ -327,6 +328,7 @@ private: | |||
@@ -327,6 +328,7 @@ private: | |||
return result; | |||
} | |||
@@ -18,7 +18,7 @@ index 0c1138a2b..a5b42b8ef 100644 | |||
Array<URL> openDialogPreVista (bool async) | |||
{ | |||
@@ -436,11 +438,13 @@ private: | |||
@@ -436,11 +438,13 @@ private: | |||
const Remover remover (*this); | |||
@@ -1,8 +1,8 @@ | |||
diff --git a/modules/juce_gui_basics/components/juce_Component.cpp b/modules/juce_gui_basics/components/juce_Component.cpp | |||
index c4404c637..fa8d3ccf5 100644 | |||
--- a/modules/juce_gui_basics/components/juce_Component.cpp | |||
+++ b/modules/juce_gui_basics/components/juce_Component.cpp | |||
@@ -387,6 +387,10 @@ struct Component::ComponentHelpers | |||
diff --git a/modules/juce_gui_basics/components/juce_Component.cpp b/modules/juce_gui_basics/components/juce_Component.cpp | |||
index c4404c637..fa8d3ccf5 100644 | |||
--- a/modules/juce_gui_basics/components/juce_Component.cpp | |||
+++ b/modules/juce_gui_basics/components/juce_Component.cpp | |||
@@ -387,6 +387,10 @@ struct Component::ComponentHelpers | |||
template <typename PointOrRect> | |||
static PointOrRect convertCoordinate (const Component* target, const Component* source, PointOrRect p) | |||
{ | |||
@@ -13,7 +13,7 @@ index c4404c637..fa8d3ccf5 100644 | |||
while (source != nullptr) | |||
{ | |||
if (source == target) | |||
@@ -395,6 +399,9 @@ struct Component::ComponentHelpers | |||
@@ -395,6 +399,9 @@ struct Component::ComponentHelpers | |||
if (source->isParentOf (target)) | |||
return convertFromDistantParentSpace (source, *target, p); | |||
@@ -23,7 +23,7 @@ index c4404c637..fa8d3ccf5 100644 | |||
p = convertToParentSpace (*source, p); | |||
source = source->getParentComponent(); | |||
} | |||
@@ -1390,13 +1397,14 @@ bool Component::reallyContains (Point<int> point, bool returnTrueIfWithinAChild) | |||
@@ -1390,13 +1397,14 @@ bool Component::reallyContains (Point<int> point, bool returnTrueIfWithinAChild) | |||
Component* Component::getComponentAt (Point<int> position) | |||
{ | |||
@@ -39,11 +39,11 @@ index c4404c637..fa8d3ccf5 100644 | |||
if (child != nullptr) | |||
return child; | |||
diff --git a/modules/juce_gui_basics/components/juce_Component.h b/modules/juce_gui_basics/components/juce_Component.h | |||
index 6b2b0072b..ccb2681fa 100644 | |||
--- a/modules/juce_gui_basics/components/juce_Component.h | |||
+++ b/modules/juce_gui_basics/components/juce_Component.h | |||
@@ -2284,6 +2284,17 @@ public: | |||
diff --git a/modules/juce_gui_basics/components/juce_Component.h b/modules/juce_gui_basics/components/juce_Component.h | |||
index 6b2b0072b..ccb2681fa 100644 | |||
--- a/modules/juce_gui_basics/components/juce_Component.h | |||
+++ b/modules/juce_gui_basics/components/juce_Component.h | |||
@@ -2284,6 +2284,17 @@ public: | |||
*/ | |||
bool getViewportIgnoreDragFlag() const noexcept { return flags.viewportIgnoreDragFlag; } | |||
@@ -61,11 +61,11 @@ index 6b2b0072b..ccb2681fa 100644 | |||
private: | |||
//============================================================================== | |||
friend class ComponentPeer; | |||
diff --git a/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp b/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp | |||
index a8c2c283a..ddb15b88d 100644 | |||
--- a/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp | |||
+++ b/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp | |||
@@ -61,7 +61,7 @@ public: | |||
diff --git a/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp b/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp | |||
index a8c2c283a..ddb15b88d 100644 | |||
--- a/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp | |||
+++ b/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp | |||
@@ -61,7 +61,7 @@ public: | |||
{ | |||
if (auto* peer = comp.getPeer()) | |||
{ | |||
@@ -74,11 +74,11 @@ index a8c2c283a..ddb15b88d 100644 | |||
auto& peerComp = peer->getComponent(); | |||
return comp.getLocalPoint (&peerComp, ScalingHelpers::unscaledScreenPosToScaled (peerComp, pos)); | |||
} | |||
diff --git a/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp b/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp | |||
index 8d7febd4b..7ec8fbb00 100644 | |||
--- a/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp | |||
+++ b/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp | |||
@@ -474,7 +474,7 @@ bool ComponentPeer::handleDragMove (const ComponentPeer::DragInfo& info) | |||
diff --git a/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp b/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp | |||
index 8d7febd4b..7ec8fbb00 100644 | |||
--- a/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp | |||
+++ b/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp | |||
@@ -474,7 +474,7 @@ bool ComponentPeer::handleDragMove (const ComponentPeer::DragInfo& info) | |||
if (DragHelpers::isSuitableTarget (info, newTarget)) | |||
{ | |||
dragAndDropTargetComponent = newTarget; | |||
@@ -87,7 +87,7 @@ index 8d7febd4b..7ec8fbb00 100644 | |||
if (DragHelpers::isFileDrag (info)) | |||
dynamic_cast<FileDragAndDropTarget*> (newTarget)->fileDragEnter (info.files, pos.x, pos.y); | |||
@@ -491,7 +491,7 @@ bool ComponentPeer::handleDragMove (const ComponentPeer::DragInfo& info) | |||
@@ -491,7 +491,7 @@ bool ComponentPeer::handleDragMove (const ComponentPeer::DragInfo& info) | |||
if (! DragHelpers::isSuitableTarget (info, newTarget)) | |||
return false; | |||
@@ -96,11 +96,11 @@ index 8d7febd4b..7ec8fbb00 100644 | |||
if (DragHelpers::isFileDrag (info)) | |||
dynamic_cast<FileDragAndDropTarget*> (newTarget)->fileDragMove (info.files, pos.x, pos.y); | |||
diff --git a/modules/juce_opengl/native/juce_OpenGLExtensions.h b/modules/juce_opengl/native/juce_OpenGLExtensions.h | |||
index e7eab9dbf..d7039b144 100644 | |||
--- a/modules/juce_opengl/native/juce_OpenGLExtensions.h | |||
+++ b/modules/juce_opengl/native/juce_OpenGLExtensions.h | |||
@@ -83,7 +83,13 @@ namespace juce | |||
diff --git a/modules/juce_opengl/native/juce_OpenGLExtensions.h b/modules/juce_opengl/native/juce_OpenGLExtensions.h | |||
index e7eab9dbf..d7039b144 100644 | |||
--- a/modules/juce_opengl/native/juce_OpenGLExtensions.h | |||
+++ b/modules/juce_opengl/native/juce_OpenGLExtensions.h | |||
@@ -83,7 +83,13 @@ namespace juce | |||
USE_FUNCTION (glCheckFramebufferStatus, GLenum, (GLenum p1), (p1))\ | |||
USE_FUNCTION (glFramebufferTexture2D, void, (GLenum p1, GLenum p2, GLenum p3, GLuint p4, GLint p5), (p1, p2, p3, p4, p5))\ | |||
USE_FUNCTION (glFramebufferRenderbuffer, void, (GLenum p1, GLenum p2, GLenum p3, GLuint p4), (p1, p2, p3, p4))\ | |||
@@ -42,7 +42,7 @@ void MidiKeyboardState::reset() | |||
bool MidiKeyboardState::isNoteOn (const int midiChannel, const int n) const noexcept | |||
{ | |||
jassert (midiChannel >= 0 && midiChannel <= 16); | |||
jassert (midiChannel > 0 && midiChannel <= 16); | |||
return isPositiveAndBelow (n, 128) | |||
&& (noteStates[n] & (1 << (midiChannel - 1))) != 0; | |||
@@ -56,7 +56,7 @@ bool MidiKeyboardState::isNoteOnForChannels (const int midiChannelMask, const in | |||
void MidiKeyboardState::noteOn (const int midiChannel, const int midiNoteNumber, const float velocity) | |||
{ | |||
jassert (midiChannel >= 0 && midiChannel <= 16); | |||
jassert (midiChannel > 0 && midiChannel <= 16); | |||
jassert (isPositiveAndBelow (midiNoteNumber, 128)); | |||
const ScopedLock sl (lock); | |||
@@ -36,7 +36,7 @@ bool MidiRPNDetector::parseControllerMessage (int midiChannel, | |||
int controllerValue, | |||
MidiRPNMessage& result) noexcept | |||
{ | |||
jassert (midiChannel >= 1 && midiChannel <= 16); | |||
jassert (midiChannel > 0 && midiChannel <= 16); | |||
jassert (controllerNumber >= 0 && controllerNumber < 128); | |||
jassert (controllerValue >= 0 && controllerValue < 128); | |||
@@ -97,7 +97,7 @@ void MPEChannelAssigner::noteOff (int noteNumber, int midiChannel) | |||
return false; | |||
}; | |||
if (midiChannel >= 0 && midiChannel < 17) | |||
if (midiChannel >= 0 && midiChannel <= 16) | |||
{ | |||
removeNote (midiChannels[midiChannel], noteNumber); | |||
return; | |||
@@ -842,6 +842,26 @@ namespace WavFileHelpers | |||
return out.getMemoryBlock(); | |||
} | |||
}; | |||
//============================================================================== | |||
struct Clm_Chunk | |||
{ | |||
static MemoryBlock createFrom (const StringPairArray& values) | |||
{ | |||
MemoryOutputStream out; | |||
auto s = values["clm "]; | |||
if (s.isNotEmpty()) | |||
{ | |||
out.writeString (s); | |||
if ((out.getDataSize() & 1) != 0) | |||
out.writeByte(0); | |||
} | |||
return out.getMemoryBlock(); | |||
} | |||
}; | |||
//============================================================================== | |||
namespace AXMLChunk | |||
@@ -1321,6 +1341,7 @@ public: | |||
listInfoChunk = ListInfoChunk::createFrom (metadataValues); | |||
acidChunk = AcidChunk::createFrom (metadataValues); | |||
trckChunk = TracktionChunk::createFrom (metadataValues); | |||
clm_Chunk = Clm_Chunk::createFrom (metadataValues); | |||
} | |||
headerPosition = out->getPosition(); | |||
@@ -1383,7 +1404,7 @@ public: | |||
} | |||
private: | |||
MemoryBlock tempBlock, bwavChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk; | |||
MemoryBlock tempBlock, bwavChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk, clm_Chunk; | |||
uint64 lengthInSamples = 0, bytesWritten = 0; | |||
int64 headerPosition = 0; | |||
bool writeFailed = false; | |||
@@ -1421,6 +1442,7 @@ private: | |||
+ chunkSize (listInfoChunk) | |||
+ chunkSize (acidChunk) | |||
+ chunkSize (trckChunk) | |||
+ chunkSize (clm_Chunk) | |||
+ (8 + 28)); // (ds64 chunk) | |||
riffChunkSize += (riffChunkSize & 1); | |||
@@ -1503,6 +1525,7 @@ private: | |||
writeChunk (listInfoChunk, chunkName ("LIST")); | |||
writeChunk (acidChunk, chunkName ("acid")); | |||
writeChunk (trckChunk, chunkName ("Trkn")); | |||
writeChunk (clm_Chunk, chunkName ("clm ")); | |||
writeChunkHeader (chunkName ("data"), isRF64 ? -1 : (int) (lengthInSamples * bytesPerFrame)); | |||
@@ -1755,6 +1755,9 @@ private: | |||
Array<const AudioProcessorParameterGroup*> parameterGroups; | |||
//============================================================================== | |||
// According to the docs, this is the maximum size of a MIDIPacketList. | |||
static constexpr UInt32 packetListBytes = 65536; | |||
AudioUnitEvent auEvent; | |||
mutable Array<AUPreset> presetsArray; | |||
CriticalSection incomingMidiLock; | |||
@@ -1762,6 +1765,7 @@ private: | |||
AudioTimeStamp lastTimeStamp; | |||
int totalInChannels, totalOutChannels; | |||
HeapBlock<bool> pulledSucceeded; | |||
HeapBlock<MIDIPacketList> packetList { packetListBytes, 1 }; | |||
ThreadLocalValue<bool> inParameterChangedCallback; | |||
@@ -1858,37 +1862,55 @@ private: | |||
void pushMidiOutput (UInt32 nFrames) noexcept | |||
{ | |||
UInt32 numPackets = 0; | |||
size_t dataSize = 0; | |||
MIDIPacket* end = nullptr; | |||
for (const auto metadata : midiEvents) | |||
const auto init = [&] | |||
{ | |||
jassert (isPositiveAndBelow (metadata.samplePosition, nFrames)); | |||
ignoreUnused (nFrames); | |||
end = MIDIPacketListInit (packetList); | |||
}; | |||
dataSize += (size_t) metadata.numBytes; | |||
++numPackets; | |||
} | |||
MIDIPacket* p; | |||
const size_t packetMembersSize = sizeof (MIDIPacket) - sizeof (p->data); // NB: GCC chokes on "sizeof (MidiMessage::data)" | |||
const size_t packetListMembersSize = sizeof (MIDIPacketList) - sizeof (p->data); | |||
const auto send = [&] | |||
{ | |||
midiCallback.midiOutputCallback (midiCallback.userData, &lastTimeStamp, 0, packetList); | |||
}; | |||
HeapBlock<MIDIPacketList> packetList; | |||
packetList.malloc (packetListMembersSize + packetMembersSize * numPackets + dataSize, 1); | |||
packetList->numPackets = numPackets; | |||
const auto add = [&] (const MidiMessageMetadata& metadata) | |||
{ | |||
end = MIDIPacketListAdd (packetList, | |||
packetListBytes, | |||
end, | |||
static_cast<MIDITimeStamp> (metadata.samplePosition), | |||
static_cast<ByteCount> (metadata.numBytes), | |||
metadata.data); | |||
}; | |||
p = packetList->packet; | |||
init(); | |||
for (const auto metadata : midiEvents) | |||
{ | |||
p->timeStamp = (MIDITimeStamp) metadata.samplePosition; | |||
p->length = (UInt16) metadata.numBytes; | |||
memcpy (p->data, metadata.data, (size_t) metadata.numBytes); | |||
p = MIDIPacketNext (p); | |||
jassert (isPositiveAndBelow (metadata.samplePosition, nFrames)); | |||
ignoreUnused (nFrames); | |||
add (metadata); | |||
if (end == nullptr) | |||
{ | |||
send(); | |||
init(); | |||
add (metadata); | |||
if (end == nullptr) | |||
{ | |||
// If this is hit, the size of this midi packet exceeds the maximum size of | |||
// a MIDIPacketList. Large SysEx messages should be broken up into smaller | |||
// chunks. | |||
jassertfalse; | |||
init(); | |||
} | |||
} | |||
} | |||
midiCallback.midiOutputCallback (midiCallback.userData, &lastTimeStamp, 0, packetList); | |||
send(); | |||
} | |||
void GetAudioBufferList (bool isInput, int busIdx, AudioBufferList*& bufferList, bool& interleaved, int& numChannels) | |||
@@ -1158,11 +1158,6 @@ public: | |||
{ | |||
auto editorBounds = getSizeToContainChild(); | |||
#if JUCE_MAC | |||
if (wrapper.useNSView) | |||
setTopLeftPosition (0, getHeight() - editorBounds.getHeight()); | |||
#endif | |||
resizeHostWindow (editorBounds.getWidth(), editorBounds.getHeight()); | |||
{ | |||
@@ -28,6 +28,9 @@ | |||
//============================================================================== | |||
#if JucePlugin_Build_VST3 && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX) | |||
#if JUCE_LINUX^M | |||
#include <arpa/inet.h>^M | |||
#endif^M | |||
#if JUCE_PLUGINHOST_VST3 | |||
#if JUCE_MAC | |||
@@ -2618,6 +2621,9 @@ public: | |||
//============================================================================== | |||
void processParameterChanges (Vst::IParameterChanges& paramChanges) | |||
{ | |||
if (juceVST3EditController == nullptr) | |||
return; | |||
jassert (pluginInstance != nullptr); | |||
auto numParamsChanged = paramChanges.getParameterCount(); | |||
@@ -78,9 +78,6 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) | |||
//============================================================================== | |||
namespace juce | |||
{ | |||
#if JUCE_WINDOWS | |||
extern void setThreadDPIAwarenessForWindow (HWND); | |||
#endif | |||
//============================================================================== | |||
namespace | |||
@@ -2848,38 +2845,30 @@ public: | |||
if (recursiveResize) | |||
return; | |||
auto* topComp = getTopLevelComponent(); | |||
if (topComp->getPeer() != nullptr) | |||
if (auto* peer = getTopLevelComponent()->getPeer()) | |||
{ | |||
auto pos = (topComp->getLocalPoint (this, Point<int>()) * nativeScaleFactor).roundToInt(); | |||
const ScopedValueSetter<bool> recursiveResizeSetter (recursiveResize, true); | |||
recursiveResize = true; | |||
auto pos = (peer->getAreaCoveredBy (*this).toFloat() * nativeScaleFactor).toNearestInt(); | |||
#if JUCE_WINDOWS | |||
if (pluginHWND != 0) | |||
{ | |||
setThreadDPIAwarenessForWindow (pluginHWND); | |||
MoveWindow (pluginHWND, pos.getX(), pos.getY(), | |||
roundToInt (getWidth() * nativeScaleFactor), | |||
roundToInt (getHeight() * nativeScaleFactor), | |||
TRUE); | |||
ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { pluginHWND }; | |||
MoveWindow (pluginHWND, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(), TRUE); | |||
} | |||
#elif JUCE_LINUX | |||
if (pluginWindow != 0) | |||
{ | |||
X11Symbols::getInstance()->xMoveResizeWindow (display, pluginWindow, | |||
pos.getX(), pos.getY(), | |||
static_cast<unsigned int> (roundToInt ((float) getWidth() * nativeScaleFactor)), | |||
static_cast<unsigned int> (roundToInt ((float) getHeight() * nativeScaleFactor))); | |||
(unsigned int) pos.getWidth(), | |||
(unsigned int) pos.getHeight()); | |||
X11Symbols::getInstance()->xMapRaised (display, pluginWindow); | |||
X11Symbols::getInstance()->xFlush (display); | |||
} | |||
#endif | |||
recursiveResize = false; | |||
} | |||
} | |||
@@ -3107,7 +3096,12 @@ private: | |||
JUCE_END_IGNORE_WARNINGS_MSVC | |||
RECT r; | |||
GetWindowRect (pluginHWND, &r); | |||
{ | |||
ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { pluginHWND }; | |||
GetWindowRect (pluginHWND, &r); | |||
} | |||
auto w = (int) (r.right - r.left); | |||
auto h = (int) (r.bottom - r.top); | |||
@@ -3122,7 +3116,7 @@ private: | |||
// very dodgy logic to decide which size is right. | |||
if (std::abs (rw - w) > 350 || std::abs (rh - h) > 350) | |||
{ | |||
setThreadDPIAwarenessForWindow (pluginHWND); | |||
ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { pluginHWND }; | |||
SetWindowPos (pluginHWND, 0, | |||
0, 0, roundToInt (rw * nativeScaleFactor), roundToInt (rh * nativeScaleFactor), | |||
@@ -35,6 +35,7 @@ | |||
#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | |||
#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 | |||
#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 | |||
#define JUCE_GUI_BASICS_INCLUDE_SCOPED_THREAD_DPI_AWARENESS_SETTER 1 | |||
#include "juce_audio_processors.h" | |||
#include <juce_gui_extra/juce_gui_extra.h> | |||
@@ -54,38 +54,24 @@ void AudioProcessorEditor::hostMIDIControllerIsAvailable (bool) { | |||
void AudioProcessorEditor::initialise() | |||
{ | |||
resizable = false; | |||
attachConstrainer (&defaultConstrainer); | |||
setConstrainer (&defaultConstrainer); | |||
resizeListener.reset (new AudioProcessorEditorListener (*this)); | |||
addComponentListener (resizeListener.get()); | |||
} | |||
//============================================================================== | |||
void AudioProcessorEditor::setResizable (const bool shouldBeResizable, const bool useBottomRightCornerResizer) | |||
void AudioProcessorEditor::setResizable (bool allowHostToResize, bool useBottomRightCornerResizer) | |||
{ | |||
if (shouldBeResizable != resizable) | |||
{ | |||
resizable = shouldBeResizable; | |||
if (! resizable && constrainer == &defaultConstrainer) | |||
{ | |||
auto width = getWidth(); | |||
auto height = getHeight(); | |||
if (width > 0 && height > 0) | |||
defaultConstrainer.setSizeLimits (width, height, width, height); | |||
} | |||
} | |||
resizableByHost = allowHostToResize; | |||
bool shouldHaveCornerResizer = (useBottomRightCornerResizer && shouldBeResizable); | |||
const auto hasResizableCorner = (resizableCorner.get() != nullptr); | |||
if (shouldHaveCornerResizer != (resizableCorner != nullptr)) | |||
if (useBottomRightCornerResizer != hasResizableCorner) | |||
{ | |||
if (shouldHaveCornerResizer) | |||
if (useBottomRightCornerResizer) | |||
attachResizableCornerComponent(); | |||
else | |||
resizableCorner.reset(); | |||
resizableCorner = nullptr; | |||
} | |||
} | |||
@@ -94,19 +80,23 @@ void AudioProcessorEditor::setResizeLimits (int newMinimumWidth, | |||
int newMaximumWidth, | |||
int newMaximumHeight) noexcept | |||
{ | |||
// if you've set up a custom constrainer then these settings won't have any effect.. | |||
jassert (constrainer == &defaultConstrainer || constrainer == nullptr); | |||
if (constrainer != nullptr && constrainer != &defaultConstrainer) | |||
{ | |||
// if you've set up a custom constrainer then these settings won't have any effect.. | |||
jassertfalse; | |||
return; | |||
} | |||
const bool shouldEnableResize = (newMinimumWidth != newMaximumWidth || newMinimumHeight != newMaximumHeight); | |||
const bool shouldHaveCornerResizer = (shouldEnableResize != resizable || resizableCorner != nullptr); | |||
resizableByHost = (newMinimumWidth != newMaximumWidth || newMinimumHeight != newMaximumHeight); | |||
setResizable (shouldEnableResize, shouldHaveCornerResizer); | |||
defaultConstrainer.setSizeLimits (newMinimumWidth, newMinimumHeight, | |||
newMaximumWidth, newMaximumHeight); | |||
if (constrainer == nullptr) | |||
setConstrainer (&defaultConstrainer); | |||
defaultConstrainer.setSizeLimits (newMinimumWidth, newMinimumHeight, | |||
newMaximumWidth, newMaximumHeight); | |||
if (resizableCorner != nullptr) | |||
attachResizableCornerComponent(); | |||
setBoundsConstrained (getBounds()); | |||
} | |||
@@ -115,29 +105,21 @@ void AudioProcessorEditor::setConstrainer (ComponentBoundsConstrainer* newConstr | |||
{ | |||
if (constrainer != newConstrainer) | |||
{ | |||
if (newConstrainer != nullptr) | |||
resizable = (newConstrainer->getMinimumWidth() != newConstrainer->getMaximumWidth() | |||
|| newConstrainer->getMinimumHeight() != newConstrainer->getMaximumHeight()); | |||
constrainer = newConstrainer; | |||
updatePeer(); | |||
attachConstrainer (newConstrainer); | |||
if (constrainer != nullptr) | |||
resizableByHost = (newConstrainer->getMinimumWidth() != newConstrainer->getMaximumWidth() | |||
|| newConstrainer->getMinimumHeight() != newConstrainer->getMaximumHeight()); | |||
if (resizableCorner != nullptr) | |||
attachResizableCornerComponent(); | |||
} | |||
} | |||
void AudioProcessorEditor::attachConstrainer (ComponentBoundsConstrainer* newConstrainer) | |||
{ | |||
if (constrainer != newConstrainer) | |||
{ | |||
constrainer = newConstrainer; | |||
updatePeer(); | |||
} | |||
} | |||
void AudioProcessorEditor::attachResizableCornerComponent() | |||
{ | |||
resizableCorner.reset (new ResizableCornerComponent (this, constrainer)); | |||
resizableCorner = std::make_unique<ResizableCornerComponent> (this, constrainer); | |||
Component::addChildComponent (resizableCorner.get()); | |||
resizableCorner->setAlwaysOnTop (true); | |||
editorResized (true); | |||
@@ -175,11 +157,6 @@ void AudioProcessorEditor::editorResized (bool wasResized) | |||
getHeight() - resizerSize, | |||
resizerSize, resizerSize); | |||
} | |||
if (! resizable) | |||
if (auto w = getWidth()) | |||
if (auto h = getHeight()) | |||
defaultConstrainer.setSizeLimits (w, h, w, h); | |||
} | |||
} | |||
@@ -54,12 +54,12 @@ public: | |||
/** Destructor. */ | |||
~AudioProcessorEditor() override; | |||
//============================================================================== | |||
/** The AudioProcessor that this editor represents. */ | |||
AudioProcessor& processor; | |||
/** Returns a pointer to the processor that this editor represents. | |||
This method is here to support legacy code, but it's easier to just use the | |||
AudioProcessorEditor::processor member variable directly to get this object. | |||
*/ | |||
@@ -76,6 +76,7 @@ public: | |||
/** Some types of plugin can call this to suggest that the control for a particular | |||
parameter should be highlighted. | |||
Currently only AAX plugins will call this, and implementing it is optional. | |||
*/ | |||
virtual void setControlHighlight (ParameterControlHighlightInfo); | |||
@@ -117,36 +118,45 @@ public: | |||
virtual void setScaleFactor (float newScale); | |||
//============================================================================== | |||
/** Marks the host's editor window as resizable | |||
@param allowHostToResize whether the editor's parent window can be resized | |||
by the user or the host. Even if this is false, you | |||
can still resize your window yourself by calling | |||
setBounds (for example, when a user clicks on a button | |||
in your editor to drop out a panel) which will bypass any | |||
resizable/constraints checks. If you are using | |||
your own corner resizer than this will also bypass | |||
any checks. | |||
@param useBottomRightCornerResizer | |||
/** Sets whether the editor is resizable by the host and/or user. | |||
@param allowHostToResize whether the editor's parent window can be resized | |||
by the host. Even if this is false, you can still | |||
resize your window yourself by calling setBounds | |||
(for example, when a user clicks on a button in | |||
your editor to drop out a panel) which will bypass | |||
any resizable/constraints checks. | |||
@param useBottomRightCornerResizer if this is true, a ResizableCornerComponent will be | |||
added to the editor's bottom-right to allow the user | |||
to resize the editor regardless of the value of | |||
`allowHostToResize`. | |||
@see setResizeLimits, isResizable | |||
*/ | |||
void setResizable (bool allowHostToResize, bool useBottomRightCornerResizer); | |||
/** Returns true if the host is allowed to resize editor's parent window | |||
/** Returns true if the host is allowed to resize the editor's parent window. | |||
@see setResizable | |||
*/ | |||
bool isResizable() const noexcept { return resizable; } | |||
bool isResizable() const noexcept { return resizableByHost; } | |||
/** This sets the maximum and minimum sizes for the window. | |||
If the window's current size is outside these limits, it will be resized to | |||
make sure it's within them. | |||
If you pass in a different minimum and maximum size, this will mark the editor | |||
as resizable by the host. | |||
A direct call to setBounds() will bypass any constraint checks, but when the | |||
window is dragged by the user or resized by other indirect means, the constrainer | |||
will limit the numbers involved. | |||
Note that if you have set a custom constrainer for this editor then this will have | |||
no effect, and if you have removed the constrainer with `setConstrainer (nullptr);` | |||
then this will re-add the default constrainer with the new limits. | |||
@see setResizable | |||
*/ | |||
void setResizeLimits (int newMinimumWidth, | |||
@@ -154,8 +164,8 @@ public: | |||
int newMaximumWidth, | |||
int newMaximumHeight) noexcept; | |||
/** Returns the bounds constrainer object that this window is using. | |||
You can access this to change its properties. | |||
*/ | |||
ComponentBoundsConstrainer* getConstrainer() noexcept { return constrainer; } | |||
@@ -176,11 +186,14 @@ public: | |||
*/ | |||
void setBoundsConstrained (Rectangle<int> newBounds); | |||
/** The ResizableCornerComponent which is currently being used by this editor, | |||
or nullptr if it does not have one. | |||
*/ | |||
std::unique_ptr<ResizableCornerComponent> resizableCorner; | |||
private: | |||
//============================================================================== | |||
struct AudioProcessorEditorListener : ComponentListener | |||
struct AudioProcessorEditorListener : public ComponentListener | |||
{ | |||
AudioProcessorEditorListener (AudioProcessorEditor& e) : ed (e) {} | |||
@@ -198,14 +211,13 @@ private: | |||
void initialise(); | |||
void editorResized (bool wasResized); | |||
void updatePeer(); | |||
void attachConstrainer (ComponentBoundsConstrainer*); | |||
void attachResizableCornerComponent(); | |||
//============================================================================== | |||
std::unique_ptr<AudioProcessorEditorListener> resizeListener; | |||
bool resizable; | |||
bool resizableByHost = false; | |||
ComponentBoundsConstrainer defaultConstrainer; | |||
ComponentBoundsConstrainer* constrainer = {}; | |||
ComponentBoundsConstrainer* constrainer = nullptr; | |||
AffineTransform hostScaleTransform; | |||
JUCE_DECLARE_NON_COPYABLE (AudioProcessorEditor) | |||
@@ -393,6 +393,7 @@ public: | |||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, | |||
TRANS("Error when trying to open audio device!"), | |||
error); | |||
resized(); | |||
} | |||
bool showDeviceControlPanel() | |||
@@ -1120,6 +1121,8 @@ void AudioDeviceSelectorComponent::updateMidiOutput() | |||
deviceManager.setDefaultMidiOutputDevice ({}); | |||
else | |||
deviceManager.setDefaultMidiOutputDevice (currentMidiOutputs[selectedId - 1].identifier); | |||
resized(); | |||
} | |||
void AudioDeviceSelectorComponent::changeListenerCallback (ChangeBroadcaster*) | |||
@@ -332,6 +332,9 @@ public: | |||
// or 3) data is in the in buffer | |||
while ((! finished) && curlBuffer.getSize() == 0) | |||
{ | |||
if (Thread::currentThreadShouldExit()) | |||
return false; | |||
{ | |||
const ScopedLock lock (cleanupLock); | |||
@@ -203,7 +203,7 @@ bool Process::openDocument (const String& fileName, const String& parameters) | |||
for (auto browserName : { "xdg-open", "/etc/alternatives/x-www-browser", "firefox", "mozilla", | |||
"google-chrome", "chromium-browser", "opera", "konqueror" }) | |||
{ | |||
cmdLines.add (String (browserName) + " " + cmdString.trim()); | |||
cmdLines.add (String (browserName) + " " + cmdString.trim().quoted()); | |||
} | |||
cmdString = cmdLines.joinIntoString (" || "); | |||
@@ -186,7 +186,8 @@ namespace juce | |||
#define JUCE_STRINGIFY(item) JUCE_STRINGIFY_MACRO_HELPER (item) | |||
//============================================================================== | |||
/** This is a shorthand macro for declaring stubs for a class's copy constructor and operator=. | |||
/** This is a shorthand macro for deleting a class's copy constructor and | |||
copy assignment operator. | |||
For example, instead of | |||
@code | |||
@@ -214,6 +215,13 @@ namespace juce | |||
className (const className&) = delete;\ | |||
className& operator= (const className&) = delete; | |||
/** This is a shorthand macro for deleting a class's move constructor and | |||
move assignment operator. | |||
*/ | |||
#define JUCE_DECLARE_NON_MOVEABLE(className) \ | |||
className (className&&) = delete;\ | |||
className& operator= (className&&) = delete; | |||
/** This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and | |||
JUCE_LEAK_DETECTOR macro for a class. | |||
*/ | |||
@@ -74,6 +74,7 @@ struct SIMDNativeOps<float> | |||
static forcedinline __m128 JUCE_VECTOR_CALLTYPE add (__m128 a, __m128 b) noexcept { return _mm_add_ps (a, b); } | |||
static forcedinline __m128 JUCE_VECTOR_CALLTYPE sub (__m128 a, __m128 b) noexcept { return _mm_sub_ps (a, b); } | |||
static forcedinline __m128 JUCE_VECTOR_CALLTYPE mul (__m128 a, __m128 b) noexcept { return _mm_mul_ps (a, b); } | |||
static forcedinline __m128 JUCE_VECTOR_CALLTYPE div (__m128 a, __m128 b) noexcept { return _mm_div_ps (a, b); } | |||
static forcedinline __m128 JUCE_VECTOR_CALLTYPE bit_and (__m128 a, __m128 b) noexcept { return _mm_and_ps (a, b); } | |||
static forcedinline __m128 JUCE_VECTOR_CALLTYPE bit_or (__m128 a, __m128 b) noexcept { return _mm_or_ps (a, b); } | |||
static forcedinline __m128 JUCE_VECTOR_CALLTYPE bit_xor (__m128 a, __m128 b) noexcept { return _mm_xor_ps (a, b); } | |||
@@ -142,6 +143,7 @@ struct SIMDNativeOps<double> | |||
static forcedinline __m128d JUCE_VECTOR_CALLTYPE add (__m128d a, __m128d b) noexcept { return _mm_add_pd (a, b); } | |||
static forcedinline __m128d JUCE_VECTOR_CALLTYPE sub (__m128d a, __m128d b) noexcept { return _mm_sub_pd (a, b); } | |||
static forcedinline __m128d JUCE_VECTOR_CALLTYPE mul (__m128d a, __m128d b) noexcept { return _mm_mul_pd (a, b); } | |||
static forcedinline __m128d JUCE_VECTOR_CALLTYPE div (__m128d a, __m128d b) noexcept { return _mm_div_pd (a, b); } | |||
static forcedinline __m128d JUCE_VECTOR_CALLTYPE bit_and (__m128d a, __m128d b) noexcept { return _mm_and_pd (a, b); } | |||
static forcedinline __m128d JUCE_VECTOR_CALLTYPE bit_or (__m128d a, __m128d b) noexcept { return _mm_or_pd (a, b); } | |||
static forcedinline __m128d JUCE_VECTOR_CALLTYPE bit_xor (__m128d a, __m128d b) noexcept { return _mm_xor_pd (a, b); } | |||
@@ -33,300 +33,302 @@ using MenuTrackingChangedCallback = void (*)(bool); | |||
MenuTrackingChangedCallback menuTrackingChangedCallback = nullptr; | |||
//============================================================================== | |||
struct AppDelegate | |||
struct AppDelegateClass : public ObjCClass<NSObject> | |||
{ | |||
public: | |||
AppDelegate() | |||
AppDelegateClass() : ObjCClass<NSObject> ("JUCEAppDelegate_") | |||
{ | |||
static AppDelegateClass cls; | |||
delegate = [cls.createInstance() init]; | |||
addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching, "v@:@"); | |||
addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); | |||
addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); | |||
addMethod (@selector (application:openFile:), application_openFile, "c@:@@"); | |||
addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@"); | |||
addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@"); | |||
addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@"); | |||
addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@"); | |||
NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||
addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent, "v@:@@"); | |||
addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@"); | |||
addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan, "v@:@"); | |||
addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@"); | |||
addMethod (@selector (dummyMethod), dummyMethod, "v@:"); | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
#if JUCE_PUSH_NOTIFICATIONS | |||
//============================================================================== | |||
addIvar<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>*> ("pushNotificationsDelegate"); | |||
addMethod (@selector (applicationDidFinishLaunching:), applicationDidFinishLaunching, "v@:@"); | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||
[center addObserver: delegate selector: @selector (mainMenuTrackingBegan:) | |||
name: NSMenuDidBeginTrackingNotification object: nil]; | |||
[center addObserver: delegate selector: @selector (mainMenuTrackingEnded:) | |||
name: NSMenuDidEndTrackingNotification object: nil]; | |||
addMethod (@selector (setPushNotificationsDelegate:), setPushNotificationsDelegate, "v@:@"); | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
if (JUCEApplicationBase::isStandaloneApp()) | |||
{ | |||
[NSApp setDelegate: delegate]; | |||
addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications, "v@:@@"); | |||
addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications, "v@:@@"); | |||
addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification, "v@:@@"); | |||
#endif | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||
[[NSDistributedNotificationCenter defaultCenter] addObserver: delegate | |||
selector: @selector (broadcastMessageCallback:) | |||
name: getBroadcastEventName() | |||
object: nil | |||
suspensionBehavior: NSNotificationSuspensionBehaviorDeliverImmediately]; | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
} | |||
else | |||
{ | |||
[center addObserver: delegate selector: @selector (applicationDidResignActive:) | |||
name: NSApplicationDidResignActiveNotification object: NSApp]; | |||
registerClass(); | |||
} | |||
[center addObserver: delegate selector: @selector (applicationDidBecomeActive:) | |||
name: NSApplicationDidBecomeActiveNotification object: NSApp]; | |||
private: | |||
static void applicationWillFinishLaunching (id self, SEL, NSNotification*) | |||
{ | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||
[[NSAppleEventManager sharedAppleEventManager] setEventHandler: self | |||
andSelector: @selector (getUrl:withReplyEvent:) | |||
forEventClass: kInternetEventClass | |||
andEventID: kAEGetURL]; | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
} | |||
[center addObserver: delegate selector: @selector (applicationWillUnhide:) | |||
name: NSApplicationWillUnhideNotification object: NSApp]; | |||
#if JUCE_PUSH_NOTIFICATIONS | |||
static void applicationDidFinishLaunching (id self, SEL, NSNotification* notification) | |||
{ | |||
if (notification.userInfo != nil) | |||
{ | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||
// NSUserNotification is deprecated from macOS 11, but there doesn't seem to be a | |||
// replacement for NSApplicationLaunchUserNotificationKey returning a non-deprecated type | |||
NSUserNotification* userNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey]; | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
if (userNotification != nil && userNotification.userInfo != nil) | |||
didReceiveRemoteNotification (self, nil, [NSApplication sharedApplication], userNotification.userInfo); | |||
} | |||
} | |||
#endif | |||
~AppDelegate() | |||
static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*) | |||
{ | |||
[[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: delegate]; | |||
[[NSNotificationCenter defaultCenter] removeObserver: delegate]; | |||
if (JUCEApplicationBase::isStandaloneApp()) | |||
if (auto* app = JUCEApplicationBase::getInstance()) | |||
{ | |||
[NSApp setDelegate: nil]; | |||
app->systemRequestedQuit(); | |||
[[NSDistributedNotificationCenter defaultCenter] removeObserver: delegate | |||
name: getBroadcastEventName() | |||
object: nil]; | |||
if (! MessageManager::getInstance()->hasStopMessageBeenSent()) | |||
return NSTerminateCancel; | |||
} | |||
[delegate release]; | |||
return NSTerminateNow; | |||
} | |||
static NSString* getBroadcastEventName() | |||
static void applicationWillTerminate (id /*self*/, SEL, NSNotification*) | |||
{ | |||
return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64())); | |||
JUCEApplicationBase::appWillTerminateByForce(); | |||
} | |||
MessageQueue messageQueue; | |||
id delegate; | |||
private: | |||
//============================================================================== | |||
struct AppDelegateClass : public ObjCClass<NSObject> | |||
static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename) | |||
{ | |||
AppDelegateClass() : ObjCClass<NSObject> ("JUCEAppDelegate_") | |||
if (auto* app = JUCEApplicationBase::getInstance()) | |||
{ | |||
addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching, "v@:@"); | |||
addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); | |||
addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); | |||
addMethod (@selector (application:openFile:), application_openFile, "c@:@@"); | |||
addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@"); | |||
addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@"); | |||
addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@"); | |||
addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@"); | |||
app->anotherInstanceStarted (quotedIfContainsSpaces (filename)); | |||
return YES; | |||
} | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||
addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent, "v@:@@"); | |||
addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@"); | |||
addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan, "v@:@"); | |||
addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@"); | |||
addMethod (@selector (dummyMethod), dummyMethod, "v@:"); | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
return NO; | |||
} | |||
#if JUCE_PUSH_NOTIFICATIONS | |||
//============================================================================== | |||
addIvar<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>*> ("pushNotificationsDelegate"); | |||
static void application_openFiles (id /*self*/, SEL, NSApplication*, NSArray* filenames) | |||
{ | |||
if (auto* app = JUCEApplicationBase::getInstance()) | |||
{ | |||
StringArray files; | |||
addMethod (@selector (applicationDidFinishLaunching:), applicationDidFinishLaunching, "v@:@"); | |||
for (NSString* f in filenames) | |||
files.add (quotedIfContainsSpaces (f)); | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||
addMethod (@selector (setPushNotificationsDelegate:), setPushNotificationsDelegate, "v@:@"); | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
if (files.size() > 0) | |||
app->anotherInstanceStarted (files.joinIntoString (" ")); | |||
} | |||
} | |||
addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications, "v@:@@"); | |||
addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications, "v@:@@"); | |||
addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification, "v@:@@"); | |||
#endif | |||
static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } | |||
static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } | |||
static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); } | |||
registerClass(); | |||
} | |||
static void broadcastMessageCallback (id /*self*/, SEL, NSNotification* n) | |||
{ | |||
NSDictionary* dict = (NSDictionary*) [n userInfo]; | |||
auto messageString = nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")]); | |||
MessageManager::getInstance()->deliverBroadcastMessage (messageString); | |||
} | |||
private: | |||
static void applicationWillFinishLaunching (id self, SEL, NSNotification*) | |||
{ | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||
[[NSAppleEventManager sharedAppleEventManager] setEventHandler: self | |||
andSelector: @selector (getUrl:withReplyEvent:) | |||
forEventClass: kInternetEventClass | |||
andEventID: kAEGetURL]; | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
} | |||
static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*) | |||
{ | |||
if (menuTrackingChangedCallback != nullptr) | |||
(*menuTrackingChangedCallback) (true); | |||
} | |||
#if JUCE_PUSH_NOTIFICATIONS | |||
static void applicationDidFinishLaunching (id self, SEL, NSNotification* notification) | |||
{ | |||
if (notification.userInfo != nil) | |||
{ | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||
// NSUserNotification is deprecated from macOS 11, but there doesn't seem to be a | |||
// replacement for NSApplicationLaunchUserNotificationKey returning a non-deprecated type | |||
NSUserNotification* userNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey]; | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
if (userNotification != nil && userNotification.userInfo != nil) | |||
didReceiveRemoteNotification (self, nil, [NSApplication sharedApplication], userNotification.userInfo); | |||
} | |||
} | |||
#endif | |||
static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*) | |||
{ | |||
if (menuTrackingChangedCallback != nullptr) | |||
(*menuTrackingChangedCallback) (false); | |||
} | |||
static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*) | |||
{ | |||
if (auto* app = JUCEApplicationBase::getInstance()) | |||
{ | |||
app->systemRequestedQuit(); | |||
static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread) | |||
if (! MessageManager::getInstance()->hasStopMessageBeenSent()) | |||
return NSTerminateCancel; | |||
} | |||
static void focusChanged() | |||
{ | |||
if (appFocusChangeCallback != nullptr) | |||
(*appFocusChangeCallback)(); | |||
} | |||
return NSTerminateNow; | |||
} | |||
static void getUrl_withReplyEvent (id /*self*/, SEL, NSAppleEventDescriptor* event, NSAppleEventDescriptor*) | |||
{ | |||
if (auto* app = JUCEApplicationBase::getInstance()) | |||
app->anotherInstanceStarted (quotedIfContainsSpaces ([[event paramDescriptorForKeyword: keyDirectObject] stringValue])); | |||
} | |||
static void applicationWillTerminate (id /*self*/, SEL, NSNotification*) | |||
{ | |||
JUCEApplicationBase::appWillTerminateByForce(); | |||
} | |||
static String quotedIfContainsSpaces (NSString* file) | |||
{ | |||
String s (nsStringToJuce (file)); | |||
s = s.unquoted().replace ("\"", "\\\""); | |||
static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename) | |||
{ | |||
if (auto* app = JUCEApplicationBase::getInstance()) | |||
{ | |||
app->anotherInstanceStarted (quotedIfContainsSpaces (filename)); | |||
return YES; | |||
} | |||
if (s.containsChar (' ')) | |||
s = s.quoted(); | |||
return NO; | |||
} | |||
return s; | |||
} | |||
static void application_openFiles (id /*self*/, SEL, NSApplication*, NSArray* filenames) | |||
{ | |||
if (auto* app = JUCEApplicationBase::getInstance()) | |||
{ | |||
StringArray files; | |||
#if JUCE_PUSH_NOTIFICATIONS | |||
//============================================================================== | |||
static void setPushNotificationsDelegate (id self, SEL, NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>* delegate) | |||
{ | |||
object_setInstanceVariable (self, "pushNotificationsDelegate", delegate); | |||
} | |||
for (NSString* f in filenames) | |||
files.add (quotedIfContainsSpaces (f)); | |||
static NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>* getPushNotificationsDelegate (id self) | |||
{ | |||
return getIvar<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>*> (self, "pushNotificationsDelegate"); | |||
} | |||
if (files.size() > 0) | |||
app->anotherInstanceStarted (files.joinIntoString (" ")); | |||
} | |||
} | |||
static void registeredForRemoteNotifications (id self, SEL, NSApplication* application, NSData* deviceToken) | |||
{ | |||
auto* delegate = getPushNotificationsDelegate (self); | |||
static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } | |||
static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } | |||
static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); } | |||
SEL selector = @selector (application:didRegisterForRemoteNotificationsWithDeviceToken:); | |||
static void broadcastMessageCallback (id /*self*/, SEL, NSNotification* n) | |||
if (delegate != nil && [delegate respondsToSelector: selector]) | |||
{ | |||
NSDictionary* dict = (NSDictionary*) [n userInfo]; | |||
auto messageString = nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")]); | |||
MessageManager::getInstance()->deliverBroadcastMessage (messageString); | |||
} | |||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; | |||
[invocation setSelector: selector]; | |||
[invocation setTarget: delegate]; | |||
[invocation setArgument: &application atIndex:2]; | |||
[invocation setArgument: &deviceToken atIndex:3]; | |||
static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*) | |||
{ | |||
if (menuTrackingChangedCallback != nullptr) | |||
(*menuTrackingChangedCallback) (true); | |||
[invocation invoke]; | |||
} | |||
} | |||
static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*) | |||
{ | |||
if (menuTrackingChangedCallback != nullptr) | |||
(*menuTrackingChangedCallback) (false); | |||
} | |||
static void failedToRegisterForRemoteNotifications (id self, SEL, NSApplication* application, NSError* error) | |||
{ | |||
auto* delegate = getPushNotificationsDelegate (self); | |||
static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread) | |||
SEL selector = @selector (application:didFailToRegisterForRemoteNotificationsWithError:); | |||
static void focusChanged() | |||
if (delegate != nil && [delegate respondsToSelector: selector]) | |||
{ | |||
if (appFocusChangeCallback != nullptr) | |||
(*appFocusChangeCallback)(); | |||
} | |||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; | |||
[invocation setSelector: selector]; | |||
[invocation setTarget: delegate]; | |||
[invocation setArgument: &application atIndex:2]; | |||
[invocation setArgument: &error atIndex:3]; | |||
static void getUrl_withReplyEvent (id /*self*/, SEL, NSAppleEventDescriptor* event, NSAppleEventDescriptor*) | |||
{ | |||
if (auto* app = JUCEApplicationBase::getInstance()) | |||
app->anotherInstanceStarted (quotedIfContainsSpaces ([[event paramDescriptorForKeyword: keyDirectObject] stringValue])); | |||
[invocation invoke]; | |||
} | |||
} | |||
static String quotedIfContainsSpaces (NSString* file) | |||
{ | |||
String s (nsStringToJuce (file)); | |||
s = s.unquoted().replace ("\"", "\\\""); | |||
static void didReceiveRemoteNotification (id self, SEL, NSApplication* application, NSDictionary* userInfo) | |||
{ | |||
auto* delegate = getPushNotificationsDelegate (self); | |||
if (s.containsChar (' ')) | |||
s = s.quoted(); | |||
SEL selector = @selector (application:didReceiveRemoteNotification:); | |||
return s; | |||
} | |||
#if JUCE_PUSH_NOTIFICATIONS | |||
//============================================================================== | |||
static void setPushNotificationsDelegate (id self, SEL, NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>* delegate) | |||
if (delegate != nil && [delegate respondsToSelector: selector]) | |||
{ | |||
object_setInstanceVariable (self, "pushNotificationsDelegate", delegate); | |||
} | |||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; | |||
[invocation setSelector: selector]; | |||
[invocation setTarget: delegate]; | |||
[invocation setArgument: &application atIndex:2]; | |||
[invocation setArgument: &userInfo atIndex:3]; | |||
static NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>* getPushNotificationsDelegate (id self) | |||
{ | |||
return getIvar<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>*> (self, "pushNotificationsDelegate"); | |||
[invocation invoke]; | |||
} | |||
} | |||
#endif | |||
}; | |||
static void registeredForRemoteNotifications (id self, SEL, NSApplication* application, NSData* deviceToken) | |||
{ | |||
auto* delegate = getPushNotificationsDelegate (self); | |||
// This is declared at file scope, so that it's guaranteed to be | |||
// constructed before and destructed after `appDelegate` (below) | |||
static AppDelegateClass appDelegateClass; | |||
SEL selector = @selector (application:didRegisterForRemoteNotificationsWithDeviceToken:); | |||
//============================================================================== | |||
struct AppDelegate | |||
{ | |||
public: | |||
AppDelegate() | |||
{ | |||
delegate = [appDelegateClass.createInstance() init]; | |||
if (delegate != nil && [delegate respondsToSelector: selector]) | |||
{ | |||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; | |||
[invocation setSelector: selector]; | |||
[invocation setTarget: delegate]; | |||
[invocation setArgument: &application atIndex:2]; | |||
[invocation setArgument: &deviceToken atIndex:3]; | |||
NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; | |||
[invocation invoke]; | |||
} | |||
} | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||
[center addObserver: delegate selector: @selector (mainMenuTrackingBegan:) | |||
name: NSMenuDidBeginTrackingNotification object: nil]; | |||
[center addObserver: delegate selector: @selector (mainMenuTrackingEnded:) | |||
name: NSMenuDidEndTrackingNotification object: nil]; | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
static void failedToRegisterForRemoteNotifications (id self, SEL, NSApplication* application, NSError* error) | |||
if (JUCEApplicationBase::isStandaloneApp()) | |||
{ | |||
auto* delegate = getPushNotificationsDelegate (self); | |||
[NSApp setDelegate: delegate]; | |||
SEL selector = @selector (application:didFailToRegisterForRemoteNotificationsWithError:); | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||
[[NSDistributedNotificationCenter defaultCenter] addObserver: delegate | |||
selector: @selector (broadcastMessageCallback:) | |||
name: getBroadcastEventName() | |||
object: nil | |||
suspensionBehavior: NSNotificationSuspensionBehaviorDeliverImmediately]; | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
} | |||
else | |||
{ | |||
[center addObserver: delegate selector: @selector (applicationDidResignActive:) | |||
name: NSApplicationDidResignActiveNotification object: NSApp]; | |||
if (delegate != nil && [delegate respondsToSelector: selector]) | |||
{ | |||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; | |||
[invocation setSelector: selector]; | |||
[invocation setTarget: delegate]; | |||
[invocation setArgument: &application atIndex:2]; | |||
[invocation setArgument: &error atIndex:3]; | |||
[center addObserver: delegate selector: @selector (applicationDidBecomeActive:) | |||
name: NSApplicationDidBecomeActiveNotification object: NSApp]; | |||
[invocation invoke]; | |||
} | |||
[center addObserver: delegate selector: @selector (applicationWillUnhide:) | |||
name: NSApplicationWillUnhideNotification object: NSApp]; | |||
} | |||
} | |||
static void didReceiveRemoteNotification (id self, SEL, NSApplication* application, NSDictionary* userInfo) | |||
~AppDelegate() | |||
{ | |||
[[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: delegate]; | |||
[[NSNotificationCenter defaultCenter] removeObserver: delegate]; | |||
if (JUCEApplicationBase::isStandaloneApp()) | |||
{ | |||
auto* delegate = getPushNotificationsDelegate (self); | |||
[NSApp setDelegate: nil]; | |||
SEL selector = @selector (application:didReceiveRemoteNotification:); | |||
[[NSDistributedNotificationCenter defaultCenter] removeObserver: delegate | |||
name: getBroadcastEventName() | |||
object: nil]; | |||
} | |||
if (delegate != nil && [delegate respondsToSelector: selector]) | |||
{ | |||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; | |||
[invocation setSelector: selector]; | |||
[invocation setTarget: delegate]; | |||
[invocation setArgument: &application atIndex:2]; | |||
[invocation setArgument: &userInfo atIndex:3]; | |||
[delegate release]; | |||
} | |||
[invocation invoke]; | |||
} | |||
} | |||
#endif | |||
}; | |||
static NSString* getBroadcastEventName() | |||
{ | |||
return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64())); | |||
} | |||
MessageQueue messageQueue; | |||
id delegate; | |||
}; | |||
//============================================================================== | |||
@@ -367,6 +369,7 @@ void MessageManager::runDispatchLoop() | |||
static void shutdownNSApp() | |||
{ | |||
[NSApp stop: nil]; | |||
[NSEvent stopPeriodicEvents]; | |||
[NSEvent startPeriodicEventsAfterDelay: 0 withPeriod: 0.1]; | |||
} | |||
@@ -482,10 +482,7 @@ void Button::mouseDrag (const MouseEvent& e) | |||
bool Button::isMouseSourceOver (const MouseEvent& e) | |||
{ | |||
if (e.source.isTouch() || e.source.isPen()) | |||
return getLocalBounds().toFloat().contains (e.position); | |||
return isMouseOver(); | |||
return getLocalBounds().toFloat().contains (e.position); | |||
} | |||
void Button::focusGained (FocusChangeType) | |||
@@ -76,9 +76,6 @@ void ShapeButton::setShape (const Path& newShape, | |||
shape = newShape; | |||
maintainShapeProportions = maintainShapeProportions_; | |||
shadow.setShadowProperties (DropShadow (Colours::black.withAlpha (0.5f), 3, Point<int>())); | |||
setComponentEffect (hasShadow ? &shadow : nullptr); | |||
if (resizeNowToFitThisShape) | |||
{ | |||
auto newBounds = shape.getBounds(); | |||
@@ -88,6 +88,8 @@ public: | |||
Colour overColourOn, | |||
Colour downColourOn); | |||
void setShadowColour (Colour shadow) { shadowColour = shadow; } | |||
/** Set whether the button should use the 'on' set of colours when its toggle state is 'on'. | |||
By default these will be the same as the normal colours but the setOnColours method can be | |||
used to provide a different set of colours. | |||
@@ -112,9 +114,8 @@ public: | |||
private: | |||
//============================================================================== | |||
Colour normalColour, overColour, downColour, | |||
normalColourOn, overColourOn, downColourOn, outlineColour; | |||
normalColourOn, overColourOn, downColourOn, outlineColour, shadowColour; | |||
bool useOnColours; | |||
DropShadowEffect shadow; | |||
Path shape; | |||
BorderSize<int> border; | |||
bool maintainShapeProportions; | |||
@@ -3021,7 +3021,8 @@ void Component::modifierKeysChanged (const ModifierKeys& modifiers) | |||
void Component::internalModifierKeysChanged() | |||
{ | |||
sendFakeMouseMove(); | |||
auto mainMouse = Desktop::getInstance().getMainMouseSource(); | |||
mainMouse.triggerFakeMove(); | |||
modifierKeysChanged (ModifierKeys::currentModifiers); | |||
} | |||
@@ -387,6 +387,10 @@ struct Component::ComponentHelpers | |||
template <typename PointOrRect> | |||
static PointOrRect convertCoordinate (const Component* target, const Component* source, PointOrRect p) | |||
{ | |||
float total_scaling = source->getTotalPixelScaling(); | |||
Component* top = nullptr; | |||
if (source) | |||
top = source->getTopLevelComponent(); | |||
while (source != nullptr) | |||
{ | |||
if (source == target) | |||
@@ -395,6 +399,9 @@ struct Component::ComponentHelpers | |||
if (source->isParentOf (target)) | |||
return convertFromDistantParentSpace (source, *target, p); | |||
if (source == top) | |||
p /= total_scaling; | |||
p = convertToParentSpace (*source, p); | |||
source = source->getParentComponent(); | |||
} | |||
@@ -1390,13 +1397,14 @@ bool Component::reallyContains (Point<int> point, bool returnTrueIfWithinAChild) | |||
Component* Component::getComponentAt (Point<int> position) | |||
{ | |||
Point<int> scale = (position.toFloat() * getPixelScaling()).roundToInt(); | |||
if (flags.visibleFlag && ComponentHelpers::hitTest (*this, position)) | |||
{ | |||
for (int i = childComponentList.size(); --i >= 0;) | |||
{ | |||
auto* child = childComponentList.getUnchecked(i); | |||
child = child->getComponentAt (ComponentHelpers::convertFromParentSpace (*child, position)); | |||
child = child->getComponentAt (ComponentHelpers::convertFromParentSpace (*child, scale)); | |||
if (child != nullptr) | |||
return child; | |||
@@ -439,7 +439,7 @@ public: | |||
@see setBounds, ComponentListener::componentMovedOrResized | |||
*/ | |||
void setTopLeftPosition (Point<int> newTopLeftPosition); | |||
virtual void setTopLeftPosition (Point<int> newTopLeftPosition); | |||
/** Moves the component to a new position. | |||
@@ -2179,10 +2179,7 @@ public: | |||
operator ComponentType*() const noexcept { return getComponent(); } | |||
/** Returns the component that this pointer refers to, or null if the component no longer exists. */ | |||
ComponentType* operator->() noexcept { return getComponent(); } | |||
/** Returns the component that this pointer refers to, or null if the component no longer exists. */ | |||
const ComponentType* operator->() const noexcept { return getComponent(); } | |||
ComponentType* operator->() const noexcept { return getComponent(); } | |||
/** If the component is valid, this deletes it and sets this pointer to null. */ | |||
void deleteAndZero() { delete getComponent(); } | |||
@@ -158,7 +158,7 @@ bool FileChooser::showDialog (const int flags, FilePreviewComponent* const previ | |||
{ | |||
FocusRestorer focusRestorer; | |||
pimpl.reset (createPimpl (flags, previewComp)); | |||
pimpl = createPimpl (flags, previewComp); | |||
pimpl->runModally(); | |||
// ensure that the finished function was invoked | |||
@@ -179,12 +179,12 @@ void FileChooser::launchAsync (int flags, std::function<void (const FileChooser& | |||
asyncCallback = std::move (callback); | |||
pimpl.reset (createPimpl (flags, previewComp)); | |||
pimpl = createPimpl (flags, previewComp); | |||
pimpl->launch(); | |||
} | |||
FileChooser::Pimpl* FileChooser::createPimpl (int flags, FilePreviewComponent* previewComp) | |||
std::shared_ptr<FileChooser::Pimpl> FileChooser::createPimpl (int flags, FilePreviewComponent* previewComp) | |||
{ | |||
results.clear(); | |||
@@ -214,10 +214,8 @@ FileChooser::Pimpl* FileChooser::createPimpl (int flags, FilePreviewComponent* p | |||
{ | |||
return showPlatformDialog (*this, flags, previewComp); | |||
} | |||
else | |||
{ | |||
return new NonNative (*this, flags, previewComp); | |||
} | |||
return std::make_unique<NonNative> (*this, flags, previewComp); | |||
} | |||
Array<File> FileChooser::getResults() const noexcept | |||
@@ -325,12 +325,11 @@ private: | |||
virtual void runModally() = 0; | |||
}; | |||
std::unique_ptr<Pimpl> pimpl; | |||
std::shared_ptr<Pimpl> pimpl; | |||
//============================================================================== | |||
Pimpl* createPimpl (int, FilePreviewComponent*); | |||
static Pimpl* showPlatformDialog (FileChooser&, int, | |||
FilePreviewComponent*); | |||
std::shared_ptr<Pimpl> createPimpl (int, FilePreviewComponent*); | |||
static std::shared_ptr<Pimpl> showPlatformDialog (FileChooser&, int, FilePreviewComponent*); | |||
class NonNative; | |||
friend class NonNative; | |||
@@ -41,6 +41,7 @@ | |||
#define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1 | |||
#define JUCE_GRAPHICS_INCLUDE_COREGRAPHICS_HELPERS 1 | |||
#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 | |||
#define JUCE_GUI_BASICS_INCLUDE_SCOPED_THREAD_DPI_AWARENESS_SETTER 1 | |||
#include "juce_gui_basics.h" | |||
@@ -341,6 +341,10 @@ namespace juce | |||
#endif | |||
#endif | |||
#if JUCE_GUI_BASICS_INCLUDE_SCOPED_THREAD_DPI_AWARENESS_SETTER && JUCE_WINDOWS | |||
#include "native/juce_win32_ScopedThreadDPIAwarenessSetter.h" | |||
#endif | |||
#include "layout/juce_FlexItem.h" | |||
#include "layout/juce_FlexBox.h" | |||
@@ -43,14 +43,9 @@ void CaretComponent::paint (Graphics& g) | |||
g.fillRect (getLocalBounds()); | |||
} | |||
void CaretComponent::timerCallback() | |||
{ | |||
setVisible (shouldBeShown() && ! isVisible()); | |||
} | |||
void CaretComponent::setCaretPosition (const Rectangle<int>& characterArea) | |||
{ | |||
startTimer (380); | |||
setVisible (shouldBeShown()); | |||
setBounds (characterArea.withWidth (2)); | |||
} | |||
@@ -31,8 +31,7 @@ namespace juce | |||
@tags{GUI} | |||
*/ | |||
class JUCE_API CaretComponent : public Component, | |||
private Timer | |||
class JUCE_API CaretComponent : public Component | |||
{ | |||
public: | |||
//============================================================================== | |||
@@ -73,7 +72,6 @@ private: | |||
Component* owner; | |||
bool shouldBeShown() const; | |||
void timerCallback() override; | |||
JUCE_DECLARE_NON_COPYABLE (CaretComponent) | |||
}; | |||
@@ -271,23 +271,6 @@ void ComponentBoundsConstrainer::checkBounds (Rectangle<int>& bounds, | |||
bounds.setWidth (roundToInt (bounds.getHeight() * aspectRatio)); | |||
} | |||
} | |||
if ((isStretchingTop || isStretchingBottom) && ! (isStretchingLeft || isStretchingRight)) | |||
{ | |||
bounds.setX (old.getX() + (old.getWidth() - bounds.getWidth()) / 2); | |||
} | |||
else if ((isStretchingLeft || isStretchingRight) && ! (isStretchingTop || isStretchingBottom)) | |||
{ | |||
bounds.setY (old.getY() + (old.getHeight() - bounds.getHeight()) / 2); | |||
} | |||
else | |||
{ | |||
if (isStretchingLeft) | |||
bounds.setX (old.getRight() - bounds.getWidth()); | |||
if (isStretchingTop) | |||
bounds.setY (old.getBottom() - bounds.getHeight()); | |||
} | |||
} | |||
jassert (! bounds.isEmpty()); | |||
@@ -41,8 +41,6 @@ LookAndFeel_V1::LookAndFeel_V1() | |||
setColour (PopupMenu::highlightedBackgroundColourId, Colour (0xbfa4c2ce)); | |||
setColour (PopupMenu::highlightedTextColourId, Colours::black); | |||
setColour (TextEditor::focusedOutlineColourId, findColour (TextButton::buttonColourId)); | |||
scrollbarShadow.setShadowProperties (DropShadow (Colours::black.withAlpha (0.5f), 2, Point<int>())); | |||
} | |||
LookAndFeel_V1::~LookAndFeel_V1() | |||
@@ -291,7 +289,7 @@ void LookAndFeel_V1::drawScrollbar (Graphics& g, ScrollBar& bar, | |||
ImageEffectFilter* LookAndFeel_V1::getScrollbarEffect() | |||
{ | |||
return &scrollbarShadow; | |||
return nullptr; | |||
} | |||
@@ -477,7 +475,7 @@ Button* LookAndFeel_V1::createSliderButton (Slider&, const bool isIncrement) | |||
ImageEffectFilter* LookAndFeel_V1::getSliderEffect (Slider&) | |||
{ | |||
return &scrollbarShadow; | |||
return nullptr; | |||
} | |||
int LookAndFeel_V1::getSliderThumbRadius (Slider&) | |||
@@ -97,8 +97,6 @@ public: | |||
bool positionTitleBarButtonsOnLeft) override; | |||
private: | |||
DropShadowEffect scrollbarShadow; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LookAndFeel_V1) | |||
}; | |||
@@ -1292,8 +1292,6 @@ void LookAndFeel_V4::drawCallOutBoxBackground (CallOutBox& box, Graphics& g, | |||
{ | |||
cachedImage = { Image::ARGB, box.getWidth(), box.getHeight(), true }; | |||
Graphics g2 (cachedImage); | |||
DropShadow (Colours::black.withAlpha (0.7f), 8, { 0, 2 }).drawForPath (g2, path); | |||
} | |||
g.setColour (Colours::black); | |||
@@ -30,9 +30,6 @@ BubbleComponent::BubbleComponent() | |||
: allowablePlacements (above | below | left | right) | |||
{ | |||
setInterceptsMouseClicks (false, false); | |||
shadow.setShadowProperties (DropShadow (Colours::black.withAlpha (0.35f), 5, Point<int>())); | |||
setComponentEffect (&shadow); | |||
} | |||
BubbleComponent::~BubbleComponent() {} | |||
@@ -178,7 +178,6 @@ private: | |||
Rectangle<int> content; | |||
Point<int> arrowTip; | |||
int allowablePlacements; | |||
DropShadowEffect shadow; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BubbleComponent) | |||
}; | |||
@@ -258,10 +258,10 @@ bool FileChooser::isPlatformDialogAvailable() | |||
#endif | |||
} | |||
FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags, FilePreviewComponent*) | |||
std::shared_ptr<FileChooser::Pimpl> FileChooser::showPlatformDialog (FileChooser& owner, int flags, FilePreviewComponent*) | |||
{ | |||
#if JUCE_MODAL_LOOPS_PERMITTED | |||
return new Native (owner, flags); | |||
return std::make_shared<Native> (owner, flags); | |||
#else | |||
return nullptr; | |||
#endif | |||
@@ -85,8 +85,8 @@ public: | |||
updateScaleFactorFromNewBounds (bounds, false); | |||
auto physicalBounds = (parentWindow == 0 ? Desktop::getInstance().getDisplays().logicalToPhysical (bounds) | |||
: bounds * currentScaleFactor); | |||
auto physicalBounds = parentWindow == 0 ? Desktop::getInstance().getDisplays().logicalToPhysical (bounds) | |||
: bounds * currentScaleFactor; | |||
WeakReference<Component> deletionChecker (&component); | |||
@@ -103,13 +103,16 @@ public: | |||
Point<int> getScreenPosition (bool physical) const | |||
{ | |||
auto parentPosition = XWindowSystem::getInstance()->getParentScreenPosition(); | |||
auto physicalParentPosition = XWindowSystem::getInstance()->getPhysicalParentScreenPosition(); | |||
auto parentPosition = parentWindow == 0 ? Desktop::getInstance().getDisplays().physicalToLogical (physicalParentPosition) | |||
: physicalParentPosition / currentScaleFactor; | |||
auto screenBounds = (parentWindow == 0 ? bounds | |||
: bounds.translated (parentPosition.x, parentPosition.y)); | |||
auto screenBounds = parentWindow == 0 ? bounds | |||
: bounds.translated (parentPosition.x, parentPosition.y); | |||
if (physical) | |||
return Desktop::getInstance().getDisplays().logicalToPhysical (screenBounds.getTopLeft()); | |||
return parentWindow == 0 ? Desktop::getInstance().getDisplays().logicalToPhysical (screenBounds.getTopLeft()) | |||
: screenBounds.getTopLeft() * currentScaleFactor; | |||
return screenBounds.getTopLeft(); | |||
} | |||
@@ -314,8 +317,8 @@ public: | |||
updateScaleFactorFromNewBounds (physicalBounds, true); | |||
bounds = (parentWindow == 0 ? Desktop::getInstance().getDisplays().physicalToLogical (physicalBounds) | |||
: physicalBounds / currentScaleFactor); | |||
bounds = parentWindow == 0 ? Desktop::getInstance().getDisplays().physicalToLogical (physicalBounds) | |||
: physicalBounds / currentScaleFactor; | |||
} | |||
} | |||
@@ -433,9 +436,6 @@ private: | |||
//============================================================================== | |||
void updateScaleFactorFromNewBounds (const Rectangle<int>& newBounds, bool isPhysical) | |||
{ | |||
if (! JUCEApplicationBase::isStandaloneApp()) | |||
return; | |||
Point<int> translation = (parentWindow != 0 ? getScreenPosition (isPhysical) : Point<int>()); | |||
const auto& desktop = Desktop::getInstance(); | |||
@@ -377,10 +377,10 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native) | |||
}; | |||
FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags, | |||
FilePreviewComponent* preview) | |||
std::shared_ptr<FileChooser::Pimpl> FileChooser::showPlatformDialog (FileChooser& owner, int flags, | |||
FilePreviewComponent* preview) | |||
{ | |||
return new FileChooser::Native (owner, flags, preview); | |||
return std::make_shared<FileChooser::Native> (owner, flags, preview); | |||
} | |||
bool FileChooser::isPlatformDialogAvailable() | |||
@@ -26,32 +26,23 @@ | |||
namespace juce | |||
{ | |||
// Win32NativeFileChooser needs to be a reference counted object as there | |||
// is no way for the parent to know when the dialog HWND has actually been | |||
// created without pumping the message thread (which is forbidden when modal | |||
// loops are disabled). However, the HWND pointer is the only way to cancel | |||
// the dialog box. This means that the actual native FileChooser HWND may | |||
// not have been created yet when the user deletes JUCE's FileChooser class. If this | |||
// occurs the Win32NativeFileChooser will still have a reference count of 1 and will | |||
// simply delete itself immediately once the HWND will have been created a while later. | |||
class Win32NativeFileChooser : public ReferenceCountedObject, | |||
class Win32NativeFileChooser : public std::enable_shared_from_this<Win32NativeFileChooser>, | |||
private Thread | |||
{ | |||
public: | |||
using Ptr = ReferenceCountedObjectPtr<Win32NativeFileChooser>; | |||
enum { charsAvailableForResult = 32768 }; | |||
Win32NativeFileChooser (Component* parent, int flags, FilePreviewComponent* previewComp, | |||
const File& startingFile, const String& titleToUse, | |||
const String& filtersToUse) | |||
: Thread ("Native Win32 FileChooser"), | |||
owner (parent), title (titleToUse), filtersString (filtersToUse.replaceCharacter (',', ';')), | |||
owner (parent), | |||
title (titleToUse), | |||
filtersString (filtersToUse.replaceCharacter (',', ';')), | |||
selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories) != 0), | |||
isSave ((flags & FileBrowserComponent::saveMode) != 0), | |||
warnAboutOverwrite ((flags & FileBrowserComponent::warnAboutOverwriting) != 0), | |||
selectMultiple ((flags & FileBrowserComponent::canSelectMultipleItems) != 0), | |||
nativeDialogRef (nullptr), shouldCancel (0) | |||
selectMultiple ((flags & FileBrowserComponent::canSelectMultipleItems) != 0) | |||
{ | |||
auto parentDirectory = startingFile.getParentDirectory(); | |||
@@ -91,14 +82,13 @@ public: | |||
// the thread should not be running | |||
nativeDialogRef.set (nullptr); | |||
weakThis = shared_from_this(); | |||
if (async) | |||
{ | |||
jassert (! isThreadRunning()); | |||
threadHasReference.reset(); | |||
startThread(); | |||
threadHasReference.wait (-1); | |||
} | |||
else | |||
{ | |||
@@ -112,7 +102,7 @@ public: | |||
ScopedLock lock (deletingDialog); | |||
customComponent = nullptr; | |||
shouldCancel.set (1); | |||
shouldCancel = true; | |||
if (auto hwnd = nativeDialogRef.get()) | |||
EndDialog (hwnd, 0); | |||
@@ -151,12 +141,12 @@ private: | |||
}; | |||
//============================================================================== | |||
Component::SafePointer<Component> owner; | |||
const Component::SafePointer<Component> owner; | |||
std::weak_ptr<Win32NativeFileChooser> weakThis; | |||
String title, filtersString; | |||
std::unique_ptr<CustomComponentHolder> customComponent; | |||
String initialPath, returnedString; | |||
WaitableEvent threadHasReference; | |||
CriticalSection deletingDialog; | |||
bool selectsDirectories, isSave, warnAboutOverwrite, selectMultiple; | |||
@@ -164,8 +154,8 @@ private: | |||
HeapBlock<WCHAR> files; | |||
HeapBlock<WCHAR> filters; | |||
Atomic<HWND> nativeDialogRef; | |||
Atomic<int> shouldCancel; | |||
Atomic<HWND> nativeDialogRef { nullptr }; | |||
bool shouldCancel = false; | |||
struct FreeLPWSTR | |||
{ | |||
@@ -173,7 +163,7 @@ private: | |||
}; | |||
#if JUCE_MSVC | |||
bool showDialog (IFileDialog& dialog, bool async) const | |||
bool showDialog (IFileDialog& dialog, bool async) | |||
{ | |||
FILEOPENDIALOGOPTIONS flags = {}; | |||
@@ -236,7 +226,49 @@ private: | |||
if (! selectsDirectories && FAILED (dialog.SetFileTypes (numElementsInArray (spec), spec))) | |||
return false; | |||
return dialog.Show (static_cast<HWND> (async ? nullptr : owner->getWindowHandle())) == S_OK; | |||
struct Events : public ComBaseClassHelper<IFileDialogEvents> | |||
{ | |||
explicit Events (Win32NativeFileChooser& o) : owner (o) {} | |||
JUCE_COMRESULT OnTypeChange (IFileDialog* d) override | |||
{ | |||
HWND hwnd = nullptr; | |||
IUnknown_GetWindow (d, &hwnd); | |||
ScopedLock lock (owner.deletingDialog); | |||
if (hwnd != nullptr) | |||
owner.nativeDialogRef = hwnd; | |||
return owner.shouldCancel ? S_FALSE : S_OK; | |||
} | |||
JUCE_COMRESULT OnFolderChanging (IFileDialog*, IShellItem*) override { return S_OK; } | |||
JUCE_COMRESULT OnFileOk (IFileDialog*) override { return S_OK; } | |||
JUCE_COMRESULT OnFolderChange (IFileDialog*) override { return S_OK; } | |||
JUCE_COMRESULT OnSelectionChange (IFileDialog*) override { return S_OK; } | |||
JUCE_COMRESULT OnShareViolation (IFileDialog*, IShellItem*, FDE_SHAREVIOLATION_RESPONSE*) override { return S_OK; } | |||
JUCE_COMRESULT OnOverwrite (IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE*) override { return S_OK; } | |||
Win32NativeFileChooser& owner; | |||
}; | |||
DWORD cookie = 0; | |||
dialog.Advise (new Events { *this }, &cookie); | |||
{ | |||
ScopedLock lock (deletingDialog); | |||
if (shouldCancel) | |||
return false; | |||
} | |||
const auto result = dialog.Show (async ? nullptr : static_cast<HWND> (owner->getWindowHandle())) == S_OK; | |||
ScopedLock lock (deletingDialog); | |||
nativeDialogRef = nullptr; | |||
return result; | |||
} | |||
//============================================================================== | |||
@@ -451,33 +483,21 @@ private: | |||
void run() override | |||
{ | |||
// We use a functor rather than a lambda here because | |||
// we want to move ownership of the Ptr into the function | |||
// object, and C++11 doesn't support general lambda capture | |||
struct AsyncCallback | |||
{ | |||
AsyncCallback (Ptr p, Array<URL> r) | |||
: ptr (std::move (p)), | |||
results (std::move (r)) {} | |||
void operator()() | |||
{ | |||
ptr->results = std::move (results); | |||
if (ptr->owner != nullptr) | |||
ptr->owner->exitModalState (ptr->results.size() > 0 ? 1 : 0); | |||
} | |||
// IUnknown_GetWindow will only succeed when instantiated in a single-thread apartment | |||
CoInitializeEx (nullptr, COINIT_APARTMENTTHREADED); | |||
Ptr ptr; | |||
Array<URL> results; | |||
}; | |||
auto resultsCopy = openDialog (true); | |||
auto safeOwner = owner; | |||
auto weakThisCopy = weakThis; | |||
// as long as the thread is running, don't delete this class | |||
Ptr safeThis (this); | |||
threadHasReference.signal(); | |||
MessageManager::callAsync ([resultsCopy, safeOwner, weakThisCopy] | |||
{ | |||
if (auto locked = weakThisCopy.lock()) | |||
locked->results = resultsCopy; | |||
auto r = openDialog (true); | |||
MessageManager::callAsync (AsyncCallback (std::move (safeThis), std::move (r))); | |||
if (safeOwner != nullptr) | |||
safeOwner->exitModalState (resultsCopy.size() > 0 ? 1 : 0); | |||
}); | |||
} | |||
static HashMap<HWND, Win32NativeFileChooser*>& getNativeDialogList() | |||
@@ -486,9 +506,9 @@ private: | |||
return dialogs; | |||
} | |||
static Win32NativeFileChooser* getNativePointerForDialog (HWND hWnd) | |||
static Win32NativeFileChooser* getNativePointerForDialog (HWND hwnd) | |||
{ | |||
return getNativeDialogList()[hWnd]; | |||
return getNativeDialogList()[hwnd]; | |||
} | |||
//============================================================================== | |||
@@ -556,7 +576,7 @@ private: | |||
ScopedLock lock (deletingDialog); | |||
getNativeDialogList().set (hdlg, this); | |||
if (shouldCancel.get() != 0) | |||
if (shouldCancel) | |||
{ | |||
EndDialog (hdlg, 0); | |||
} | |||
@@ -621,7 +641,7 @@ private: | |||
{ | |||
ScopedLock lock (deletingDialog); | |||
if (customComponent != nullptr && shouldCancel.get() == 0) | |||
if (customComponent != nullptr && ! shouldCancel) | |||
{ | |||
if (FilePreviewComponent* comp = dynamic_cast<FilePreviewComponent*> (customComponent->getChildComponent (0))) | |||
{ | |||
@@ -719,14 +739,15 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32NativeFileChooser) | |||
}; | |||
class FileChooser::Native : public Component, | |||
class FileChooser::Native : public std::enable_shared_from_this<Native>, | |||
public Component, | |||
public FileChooser::Pimpl | |||
{ | |||
public: | |||
Native (FileChooser& fileChooser, int flags, FilePreviewComponent* previewComp) | |||
: owner (fileChooser), | |||
nativeFileChooser (new Win32NativeFileChooser (this, flags, previewComp, fileChooser.startingFile, | |||
fileChooser.title, fileChooser.filters)) | |||
nativeFileChooser (std::make_shared<Win32NativeFileChooser> (this, flags, previewComp, fileChooser.startingFile, | |||
fileChooser.title, fileChooser.filters)) | |||
{ | |||
auto mainMon = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea; | |||
@@ -743,19 +764,17 @@ public: | |||
{ | |||
exitModalState (0); | |||
nativeFileChooser->cancel(); | |||
nativeFileChooser = nullptr; | |||
} | |||
void launch() override | |||
{ | |||
SafePointer<Native> safeThis (this); | |||
std::weak_ptr<Native> safeThis = shared_from_this(); | |||
enterModalState (true, ModalCallbackFunction::create ( | |||
[safeThis] (int) | |||
{ | |||
if (safeThis != nullptr) | |||
safeThis->owner.finished (safeThis->nativeFileChooser->results); | |||
})); | |||
enterModalState (true, ModalCallbackFunction::create ([safeThis] (int) | |||
{ | |||
if (auto locked = safeThis.lock()) | |||
locked->owner.finished (locked->nativeFileChooser->results); | |||
})); | |||
nativeFileChooser->open (true); | |||
} | |||
@@ -787,7 +806,7 @@ public: | |||
private: | |||
FileChooser& owner; | |||
Win32NativeFileChooser::Ptr nativeFileChooser; | |||
std::shared_ptr<Win32NativeFileChooser> nativeFileChooser; | |||
}; | |||
//============================================================================== | |||
@@ -800,10 +819,10 @@ bool FileChooser::isPlatformDialogAvailable() | |||
#endif | |||
} | |||
FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags, | |||
FilePreviewComponent* preview) | |||
std::shared_ptr<FileChooser::Pimpl> FileChooser::showPlatformDialog (FileChooser& owner, int flags, | |||
FilePreviewComponent* preview) | |||
{ | |||
return new FileChooser::Native (owner, flags, preview); | |||
return std::make_shared<FileChooser::Native> (owner, flags, preview); | |||
} | |||
} // namespace juce |
@@ -27,7 +27,7 @@ | |||
#include <juce_audio_plugin_client/AAX/juce_AAX_Modifier_Injector.h> | |||
#endif | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE && JUCE_MODULE_AVAILABLE_juce_gui_extra | |||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra | |||
#include <juce_gui_extra/embedding/juce_ScopedDPIAwarenessDisabler.h> | |||
#endif | |||
@@ -63,6 +63,31 @@ static bool shouldDeactivateTitleBar = true; | |||
void* getUser32Function (const char*); | |||
#if JUCE_DEBUG | |||
int numActiveScopedDpiAwarenessDisablers = 0; | |||
bool isInScopedDPIAwarenessDisabler() { return numActiveScopedDpiAwarenessDisablers > 0; } | |||
extern HWND juce_messageWindowHandle; | |||
#endif | |||
struct ScopedDeviceContext | |||
{ | |||
explicit ScopedDeviceContext (HWND h) | |||
: hwnd (h), dc (GetDC (hwnd)) | |||
{ | |||
} | |||
~ScopedDeviceContext() | |||
{ | |||
ReleaseDC (hwnd, dc); | |||
} | |||
HWND hwnd; | |||
HDC dc; | |||
JUCE_DECLARE_NON_COPYABLE (ScopedDeviceContext) | |||
JUCE_DECLARE_NON_MOVEABLE (ScopedDeviceContext) | |||
}; | |||
//============================================================================== | |||
#ifndef WM_TOUCH | |||
enum | |||
@@ -408,7 +433,9 @@ static void setDPIAwareness() | |||
static bool isPerMonitorDPIAwareProcess() | |||
{ | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
#if ! JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
return false; | |||
#else | |||
static bool dpiAware = []() -> bool | |||
{ | |||
setDPIAwareness(); | |||
@@ -423,39 +450,43 @@ static bool isPerMonitorDPIAwareProcess() | |||
}(); | |||
return dpiAware; | |||
#else | |||
return false; | |||
#endif | |||
} | |||
static bool isPerMonitorDPIAwareWindow (HWND h) | |||
static bool isPerMonitorDPIAwareWindow (HWND nativeWindow) | |||
{ | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
jassert (h != nullptr); | |||
#if ! JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
ignoreUnused (nativeWindow); | |||
return false; | |||
#else | |||
setDPIAwareness(); | |||
if (getWindowDPIAwarenessContext != nullptr && getAwarenessFromDPIAwarenessContext != nullptr) | |||
return getAwarenessFromDPIAwarenessContext (getWindowDPIAwarenessContext (h)) == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware; | |||
if (getWindowDPIAwarenessContext != nullptr | |||
&& getAwarenessFromDPIAwarenessContext != nullptr) | |||
{ | |||
return (getAwarenessFromDPIAwarenessContext (getWindowDPIAwarenessContext (nativeWindow)) | |||
== DPI_Awareness::DPI_Awareness_Per_Monitor_Aware); | |||
} | |||
return isPerMonitorDPIAwareProcess(); | |||
#else | |||
ignoreUnused (h); | |||
return false; | |||
#endif | |||
} | |||
static bool isPerMonitorDPIAwareThread() | |||
{ | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
#if ! JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
return false; | |||
#else | |||
setDPIAwareness(); | |||
if (getThreadDPIAwarenessContext != nullptr && getAwarenessFromDPIAwarenessContext != nullptr) | |||
return getAwarenessFromDPIAwarenessContext (getThreadDPIAwarenessContext()) == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware; | |||
if (getThreadDPIAwarenessContext != nullptr | |||
&& getAwarenessFromDPIAwarenessContext != nullptr) | |||
{ | |||
return (getAwarenessFromDPIAwarenessContext (getThreadDPIAwarenessContext()) | |||
== DPI_Awareness::DPI_Awareness_Per_Monitor_Aware); | |||
} | |||
return isPerMonitorDPIAwareProcess(); | |||
#else | |||
return false; | |||
#endif | |||
} | |||
@@ -463,27 +494,114 @@ static double getGlobalDPI() | |||
{ | |||
setDPIAwareness(); | |||
HDC dc = GetDC (nullptr); | |||
auto dpi = (GetDeviceCaps (dc, LOGPIXELSX) + GetDeviceCaps (dc, LOGPIXELSY)) / 2.0; | |||
ReleaseDC (nullptr, dc); | |||
return dpi; | |||
ScopedDeviceContext deviceContext { nullptr }; | |||
return (GetDeviceCaps (deviceContext.dc, LOGPIXELSX) + GetDeviceCaps (deviceContext.dc, LOGPIXELSY)) / 2.0; | |||
} | |||
//============================================================================== | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE && JUCE_MODULE_AVAILABLE_juce_gui_extra | |||
class ScopedThreadDPIAwarenessSetter::NativeImpl | |||
{ | |||
public: | |||
explicit NativeImpl (HWND nativeWindow) | |||
{ | |||
ignoreUnused (nativeWindow); | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
if (auto* functionSingleton = FunctionSingleton::getInstance()) | |||
{ | |||
if (! functionSingleton->isLoaded()) | |||
return; | |||
auto dpiAwareWindow = (functionSingleton->getAwarenessFromContext (functionSingleton->getWindowAwareness (nativeWindow)) | |||
== DPI_Awareness::DPI_Awareness_Per_Monitor_Aware); | |||
auto dpiAwareThread = (functionSingleton->getAwarenessFromContext (functionSingleton->getThreadAwareness()) | |||
== DPI_Awareness::DPI_Awareness_Per_Monitor_Aware); | |||
if (dpiAwareWindow && ! dpiAwareThread) | |||
oldContext = functionSingleton->setThreadAwareness (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); | |||
else if (! dpiAwareWindow && dpiAwareThread) | |||
oldContext = functionSingleton->setThreadAwareness (DPI_AWARENESS_CONTEXT_UNAWARE); | |||
} | |||
#endif | |||
} | |||
~NativeImpl() | |||
{ | |||
if (oldContext != nullptr) | |||
if (auto* functionSingleton = FunctionSingleton::getInstance()) | |||
functionSingleton->setThreadAwareness (oldContext); | |||
} | |||
private: | |||
struct FunctionSingleton : public DeletedAtShutdown | |||
{ | |||
FunctionSingleton() = default; | |||
~FunctionSingleton() override { clearSingletonInstance(); } | |||
SetThreadDPIAwarenessContextFunc setThreadAwareness = (SetThreadDPIAwarenessContextFunc) getUser32Function ("SetThreadDpiAwarenessContext"); | |||
GetWindowDPIAwarenessContextFunc getWindowAwareness = (GetWindowDPIAwarenessContextFunc) getUser32Function ("GetWindowDpiAwarenessContext"); | |||
GetThreadDPIAwarenessContextFunc getThreadAwareness = (GetThreadDPIAwarenessContextFunc) getUser32Function ("GetThreadDpiAwarenessContext"); | |||
GetAwarenessFromDpiAwarenessContextFunc getAwarenessFromContext = (GetAwarenessFromDpiAwarenessContextFunc) getUser32Function ("GetAwarenessFromDpiAwarenessContext"); | |||
bool isLoaded() const noexcept | |||
{ | |||
return setThreadAwareness != nullptr | |||
&& getWindowAwareness != nullptr | |||
&& getThreadAwareness != nullptr | |||
&& getAwarenessFromContext != nullptr; | |||
} | |||
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (FunctionSingleton) | |||
JUCE_DECLARE_NON_COPYABLE (FunctionSingleton) | |||
JUCE_DECLARE_NON_MOVEABLE (FunctionSingleton) | |||
}; | |||
DPI_AWARENESS_CONTEXT oldContext = nullptr; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeImpl) | |||
JUCE_DECLARE_NON_MOVEABLE (NativeImpl) | |||
}; | |||
JUCE_IMPLEMENT_SINGLETON (ScopedThreadDPIAwarenessSetter::NativeImpl::FunctionSingleton) | |||
ScopedThreadDPIAwarenessSetter::ScopedThreadDPIAwarenessSetter (void* nativeWindow) | |||
{ | |||
pimpl = std::make_unique<NativeImpl> ((HWND) nativeWindow); | |||
} | |||
ScopedThreadDPIAwarenessSetter::~ScopedThreadDPIAwarenessSetter() | |||
{ | |||
} | |||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra | |||
ScopedDPIAwarenessDisabler::ScopedDPIAwarenessDisabler() | |||
{ | |||
if (! isPerMonitorDPIAwareThread()) | |||
return; | |||
if (setThreadDPIAwarenessContext != nullptr) | |||
{ | |||
previousContext = setThreadDPIAwarenessContext (DPI_AWARENESS_CONTEXT_UNAWARE); | |||
#if JUCE_DEBUG | |||
++numActiveScopedDpiAwarenessDisablers; | |||
#endif | |||
} | |||
} | |||
ScopedDPIAwarenessDisabler::~ScopedDPIAwarenessDisabler() | |||
{ | |||
if (previousContext != nullptr) | |||
{ | |||
setThreadDPIAwarenessContext ((DPI_AWARENESS_CONTEXT) previousContext); | |||
#if JUCE_DEBUG | |||
--numActiveScopedDpiAwarenessDisablers; | |||
#endif | |||
} | |||
} | |||
#endif | |||
@@ -527,6 +645,14 @@ static Point<int> convertPhysicalScreenPointToLogical (Point<int> p, HWND h) noe | |||
return p; | |||
} | |||
static Point<int> convertLogicalScreenPointToPhysical (Point<int> p, HWND h) noexcept | |||
{ | |||
if (isPerMonitorDPIAwareWindow (h)) | |||
return Desktop::getInstance().getDisplays().logicalToPhysical (p, getCurrentDisplayFromScaleFactor (h)); | |||
return p; | |||
} | |||
JUCE_API double getScaleFactorForWindow (HWND h) | |||
{ | |||
// NB. Using a local function here because we need to call this method from the plug-in wrappers | |||
@@ -549,50 +675,11 @@ JUCE_API double getScaleFactorForWindow (HWND h) | |||
return 1.0; | |||
} | |||
JUCE_API void setThreadDPIAwarenessForWindow (HWND nativeWindow) | |||
{ | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
// NB. Using local functions here because we need to call this method from the plug-in wrappers | |||
// which don't load the DPI-awareness functions on startup | |||
static SetThreadDPIAwarenessContextFunc localSetThreadDPIAwarenessContext = nullptr; | |||
static GetWindowDPIAwarenessContextFunc localGetWindowDPIAwarenessContext = nullptr; | |||
static GetThreadDPIAwarenessContextFunc localGetThreadDPIAwarenessContext = nullptr; | |||
static GetAwarenessFromDpiAwarenessContextFunc localGetAwarenessFromDPIAwarenessContext = nullptr; | |||
static bool hasChecked = false; | |||
static bool loadedOK = false; | |||
if (! hasChecked) | |||
{ | |||
hasChecked = true; | |||
localSetThreadDPIAwarenessContext = (SetThreadDPIAwarenessContextFunc) getUser32Function ("SetThreadDpiAwarenessContext"); | |||
localGetWindowDPIAwarenessContext = (GetWindowDPIAwarenessContextFunc) getUser32Function ("GetWindowDpiAwarenessContext"); | |||
localGetThreadDPIAwarenessContext = (GetThreadDPIAwarenessContextFunc) getUser32Function ("GetThreadDpiAwarenessContext"); | |||
localGetAwarenessFromDPIAwarenessContext = (GetAwarenessFromDpiAwarenessContextFunc) getUser32Function ("GetAwarenessFromDpiAwarenessContext"); | |||
loadedOK = (localSetThreadDPIAwarenessContext != nullptr && localGetWindowDPIAwarenessContext != nullptr | |||
&& localGetThreadDPIAwarenessContext != nullptr && localGetAwarenessFromDPIAwarenessContext != nullptr); | |||
} | |||
if (loadedOK) | |||
{ | |||
auto dpiAwareWindow = localGetAwarenessFromDPIAwarenessContext (localGetWindowDPIAwarenessContext (nativeWindow)) == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware; | |||
auto dpiAwareThread = localGetAwarenessFromDPIAwarenessContext (localGetThreadDPIAwarenessContext()) == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware; | |||
if (dpiAwareWindow && ! dpiAwareThread) | |||
localSetThreadDPIAwarenessContext (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); | |||
else if (! dpiAwareWindow && dpiAwareThread) | |||
localSetThreadDPIAwarenessContext (DPI_AWARENESS_CONTEXT_UNAWARE); | |||
} | |||
#else | |||
ignoreUnused (nativeWindow); | |||
#endif | |||
} | |||
//============================================================================== | |||
static void setWindowPos (HWND hwnd, Rectangle<int> bounds, UINT flags, bool adjustTopLeft = false) | |||
{ | |||
ScopedThreadDPIAwarenessSetter setter { hwnd }; | |||
if (isPerMonitorDPIAwareWindow (hwnd)) | |||
{ | |||
if (adjustTopLeft) | |||
@@ -607,9 +694,7 @@ static void setWindowPos (HWND hwnd, Rectangle<int> bounds, UINT flags, bool adj | |||
static RECT getWindowScreenRect (HWND hwnd) | |||
{ | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
setThreadDPIAwarenessForWindow (hwnd); | |||
#endif | |||
ScopedThreadDPIAwarenessSetter setter { hwnd }; | |||
RECT rect; | |||
GetWindowRect (hwnd, &rect); | |||
@@ -621,7 +706,10 @@ static RECT getWindowClientRect (HWND hwnd) | |||
auto rect = getWindowScreenRect (hwnd); | |||
if (auto parentH = GetParent (hwnd)) | |||
{ | |||
ScopedThreadDPIAwarenessSetter setter { hwnd }; | |||
MapWindowPoints (HWND_DESKTOP, parentH, (LPPOINT) &rect, 2); | |||
} | |||
return rect; | |||
} | |||
@@ -634,14 +722,8 @@ static void setWindowZOrder (HWND hwnd, HWND insertAfter) | |||
//============================================================================== | |||
double Desktop::getDefaultMasterScale() | |||
{ | |||
if (! JUCEApplicationBase::isStandaloneApp() | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
|| isPerMonitorDPIAwareProcess() | |||
#endif | |||
) | |||
{ | |||
if (! JUCEApplicationBase::isStandaloneApp() || isPerMonitorDPIAwareProcess()) | |||
return 1.0; | |||
} | |||
return getGlobalDPI() / USER_DEFAULT_SCREEN_DPI; | |||
} | |||
@@ -782,9 +864,10 @@ public: | |||
bitmapInfo.bV4V4Compression = BI_RGB; | |||
} | |||
HDC dc = GetDC (nullptr); | |||
hdc = CreateCompatibleDC (dc); | |||
ReleaseDC (nullptr, dc); | |||
{ | |||
ScopedDeviceContext deviceContext { nullptr }; | |||
hdc = CreateCompatibleDC (deviceContext.dc); | |||
} | |||
SetMapMode (hdc, MM_TEXT); | |||
@@ -877,10 +960,8 @@ public: | |||
private: | |||
static bool isGraphicsCard32Bit() | |||
{ | |||
auto dc = GetDC (nullptr); | |||
auto bitsPerPixel = GetDeviceCaps (dc, BITSPIXEL); | |||
ReleaseDC (nullptr, dc); | |||
return bitsPerPixel > 24; | |||
ScopedDeviceContext deviceContext { nullptr }; | |||
return GetDeviceCaps (deviceContext.dc, BITSPIXEL) > 24; | |||
} | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsBitmapImage) | |||
@@ -893,13 +974,13 @@ Image createSnapshotOfNativeWindow (void* nativeWindowHandle) | |||
auto hwnd = (HWND) nativeWindowHandle; | |||
auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd); | |||
const int w = r.getWidth(); | |||
const int h = r.getHeight(); | |||
const auto w = r.getWidth(); | |||
const auto h = r.getHeight(); | |||
auto nativeBitmap = new WindowsBitmapImage (Image::RGB, w, h, true); | |||
Image bitmap (nativeBitmap); | |||
HDC dc = GetDC (hwnd); | |||
ScopedDeviceContext deviceContext { hwnd }; | |||
if (isPerMonitorDPIAwareProcess()) | |||
{ | |||
@@ -908,18 +989,16 @@ Image createSnapshotOfNativeWindow (void* nativeWindowHandle) | |||
SetBrushOrgEx (nativeBitmap->hdc, 0, 0, NULL); | |||
StretchBlt (nativeBitmap->hdc, 0, 0, w, h, | |||
dc, 0, 0, roundToInt (w * scale), roundToInt (h * scale), | |||
deviceContext.dc, 0, 0, roundToInt (w * scale), roundToInt (h * scale), | |||
SRCCOPY); | |||
SetStretchBltMode (nativeBitmap->hdc, prevStretchMode); | |||
} | |||
else | |||
{ | |||
BitBlt (nativeBitmap->hdc, 0, 0, w, h, dc, 0, 0, SRCCOPY); | |||
BitBlt (nativeBitmap->hdc, 0, 0, w, h, deviceContext.dc, 0, 0, SRCCOPY); | |||
} | |||
ReleaseDC (hwnd, dc); | |||
return SoftwareImageType().convert (bitmap); | |||
} | |||
@@ -960,79 +1039,75 @@ namespace IconConverters | |||
&& bm.bmWidth > 0 && bm.bmHeight > 0)) | |||
return {}; | |||
if (auto* tempDC = ::GetDC (nullptr)) | |||
ScopedDeviceContext deviceContext { nullptr }; | |||
if (auto* dc = ::CreateCompatibleDC (deviceContext.dc)) | |||
{ | |||
if (auto* dc = ::CreateCompatibleDC (tempDC)) | |||
BITMAPV5HEADER header = {}; | |||
header.bV5Size = sizeof (BITMAPV5HEADER); | |||
header.bV5Width = bm.bmWidth; | |||
header.bV5Height = -bm.bmHeight; | |||
header.bV5Planes = 1; | |||
header.bV5Compression = BI_RGB; | |||
header.bV5BitCount = 32; | |||
header.bV5RedMask = 0x00FF0000; | |||
header.bV5GreenMask = 0x0000FF00; | |||
header.bV5BlueMask = 0x000000FF; | |||
header.bV5AlphaMask = 0xFF000000; | |||
header.bV5CSType = LCS_WINDOWS_COLOR_SPACE; | |||
header.bV5Intent = LCS_GM_IMAGES; | |||
uint32* bitmapImageData = nullptr; | |||
if (auto* dib = ::CreateDIBSection (deviceContext.dc, (BITMAPINFO*) &header, DIB_RGB_COLORS, | |||
(void**) &bitmapImageData, nullptr, 0)) | |||
{ | |||
BITMAPV5HEADER header = {}; | |||
header.bV5Size = sizeof (BITMAPV5HEADER); | |||
header.bV5Width = bm.bmWidth; | |||
header.bV5Height = -bm.bmHeight; | |||
header.bV5Planes = 1; | |||
header.bV5Compression = BI_RGB; | |||
header.bV5BitCount = 32; | |||
header.bV5RedMask = 0x00FF0000; | |||
header.bV5GreenMask = 0x0000FF00; | |||
header.bV5BlueMask = 0x000000FF; | |||
header.bV5AlphaMask = 0xFF000000; | |||
header.bV5CSType = LCS_WINDOWS_COLOR_SPACE; | |||
header.bV5Intent = LCS_GM_IMAGES; | |||
uint32* bitmapImageData = nullptr; | |||
if (auto* dib = ::CreateDIBSection (tempDC, (BITMAPINFO*) &header, DIB_RGB_COLORS, | |||
(void**) &bitmapImageData, nullptr, 0)) | |||
{ | |||
auto oldObject = ::SelectObject (dc, dib); | |||
auto oldObject = ::SelectObject (dc, dib); | |||
auto numPixels = bm.bmWidth * bm.bmHeight; | |||
auto numColourComponents = (size_t) numPixels * 4; | |||
auto numPixels = bm.bmWidth * bm.bmHeight; | |||
auto numColourComponents = (size_t) numPixels * 4; | |||
// Windows icon data comes as two layers, an XOR mask which contains the bulk | |||
// of the image data and an AND mask which provides the transparency. Annoyingly | |||
// the XOR mask can also contain an alpha channel, in which case the transparency | |||
// mask should not be applied, but there's no way to find out a priori if the XOR | |||
// mask contains an alpha channel. | |||
// Windows icon data comes as two layers, an XOR mask which contains the bulk | |||
// of the image data and an AND mask which provides the transparency. Annoyingly | |||
// the XOR mask can also contain an alpha channel, in which case the transparency | |||
// mask should not be applied, but there's no way to find out a priori if the XOR | |||
// mask contains an alpha channel. | |||
HeapBlock<bool> opacityMask (numPixels); | |||
memset (bitmapImageData, 0, numColourComponents); | |||
::DrawIconEx (dc, 0, 0, icon, bm.bmWidth, bm.bmHeight, 0, nullptr, DI_MASK); | |||
HeapBlock<bool> opacityMask (numPixels); | |||
memset (bitmapImageData, 0, numColourComponents); | |||
::DrawIconEx (dc, 0, 0, icon, bm.bmWidth, bm.bmHeight, 0, nullptr, DI_MASK); | |||
for (int i = 0; i < numPixels; ++i) | |||
opacityMask[i] = (bitmapImageData[i] == 0); | |||
Image result = Image (Image::ARGB, bm.bmWidth, bm.bmHeight, true); | |||
Image::BitmapData imageData (result, Image::BitmapData::readWrite); | |||
for (int i = 0; i < numPixels; ++i) | |||
opacityMask[i] = (bitmapImageData[i] == 0); | |||
memset (bitmapImageData, 0, numColourComponents); | |||
::DrawIconEx (dc, 0, 0, icon, bm.bmWidth, bm.bmHeight, 0, nullptr, DI_NORMAL); | |||
memcpy (imageData.data, bitmapImageData, numColourComponents); | |||
Image result = Image (Image::ARGB, bm.bmWidth, bm.bmHeight, true); | |||
Image::BitmapData imageData (result, Image::BitmapData::readWrite); | |||
auto imageHasAlphaChannel = [&imageData, numPixels]() | |||
{ | |||
for (int i = 0; i < numPixels; ++i) | |||
if (imageData.data[i * 4] != 0) | |||
return true; | |||
return false; | |||
}; | |||
memset (bitmapImageData, 0, numColourComponents); | |||
::DrawIconEx (dc, 0, 0, icon, bm.bmWidth, bm.bmHeight, 0, nullptr, DI_NORMAL); | |||
memcpy (imageData.data, bitmapImageData, numColourComponents); | |||
if (! imageHasAlphaChannel()) | |||
for (int i = 0; i < numPixels; ++i) | |||
imageData.data[i * 4] = opacityMask[i] ? 0xff : 0x00; | |||
auto imageHasAlphaChannel = [&imageData, numPixels]() | |||
{ | |||
for (int i = 0; i < numPixels; ++i) | |||
if (imageData.data[i * 4] != 0) | |||
return true; | |||
::SelectObject (dc, oldObject); | |||
::DeleteObject(dib); | |||
::DeleteDC (dc); | |||
::ReleaseDC (nullptr, tempDC); | |||
return false; | |||
}; | |||
return result; | |||
} | |||
if (! imageHasAlphaChannel()) | |||
for (int i = 0; i < numPixels; ++i) | |||
imageData.data[i * 4] = opacityMask[i] ? 0xff : 0x00; | |||
::SelectObject (dc, oldObject); | |||
::DeleteObject (dib); | |||
::DeleteDC (dc); | |||
return result; | |||
} | |||
::ReleaseDC (nullptr, tempDC); | |||
::DeleteDC (dc); | |||
} | |||
return {}; | |||
@@ -1433,10 +1508,8 @@ public: | |||
auto localBounds = rectangleFromRECT (getWindowClientRect (hwnd)); | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
if (isPerMonitorDPIAwareWindow (hwnd)) | |||
return (localBounds.toDouble() / getPlatformScaleFactor()).toNearestInt(); | |||
#endif | |||
return localBounds; | |||
}(); | |||
@@ -1554,16 +1627,10 @@ public: | |||
if (! r.withZeroOrigin().contains (localPos)) | |||
return false; | |||
auto globalPos = localPos + getScreenPosition(); | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
if (isPerMonitorDPIAwareThread() || isPerMonitorDPIAwareWindow (hwnd)) | |||
globalPos = Desktop::getInstance().getDisplays().logicalToPhysical (globalPos); | |||
#endif | |||
auto w = WindowFromPoint (POINTFromPoint (globalPos)); | |||
auto w = WindowFromPoint (POINTFromPoint (convertLogicalScreenPointToPhysical (localPos + getScreenPosition(), | |||
hwnd))); | |||
return w == hwnd || (trueIfInAChildWindow && (IsChild (hwnd, w) != 0)); | |||
return w == hwnd || (trueIfInAChildWindow && (IsChild (hwnd, w) != 0)); | |||
} | |||
BorderSize<int> getFrameSize() const override | |||
@@ -1663,18 +1730,7 @@ public: | |||
void repaint (const Rectangle<int>& area) override | |||
{ | |||
auto scale = getPlatformScaleFactor(); | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
// if the calling thread is DPI-aware but we are invalidating a non-DPI aware window RECT, we actually have to | |||
// divide the bounds by the scale factor as it will get multiplied for the virtualised paint callback... | |||
if (isPerMonitorDPIAwareThread() && ! isPerMonitorDPIAwareWindow (hwnd)) | |||
scale = 1.0 / Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale; | |||
#endif | |||
auto scaled = area.toDouble() * scale; | |||
auto r = RECTFromRectangle (scaled.getSmallestIntegerContainer()); | |||
auto r = RECTFromRectangle ((area.toDouble() * getPlatformScaleFactor()).getSmallestIntegerContainer()); | |||
InvalidateRect (hwnd, &r, FALSE); | |||
} | |||
@@ -1760,7 +1816,7 @@ public: | |||
if (peerIsDeleted) | |||
return S_FALSE; | |||
peer.handleDragExit (dragInfo); | |||
peer.handleDragDrop (dragInfo); | |||
return S_OK; | |||
} | |||
@@ -1795,19 +1851,8 @@ public: | |||
private: | |||
Point<float> getMousePos (POINTL mousePos) const | |||
{ | |||
auto screenPos = pointFromPOINT ({ mousePos.x, mousePos.y }).toFloat(); | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
auto h = (HWND) peer.getNativeHandle(); | |||
if (isPerMonitorDPIAwareWindow (h)) | |||
screenPos = convertPhysicalScreenPointToLogical (screenPos.roundToInt(), h).toFloat(); | |||
#else | |||
if (JUCEApplication::isStandaloneApp()) | |||
screenPos /= static_cast<float> (getGlobalDPI() / USER_DEFAULT_SCREEN_DPI); | |||
#endif | |||
return peer.getComponent().getLocalPoint (nullptr, screenPos); | |||
return peer.getComponent().getLocalPoint (nullptr, convertPhysicalScreenPointToLogical (pointFromPOINT ({ mousePos.x, mousePos.y }), | |||
(HWND) peer.getNativeHandle()).toFloat()); | |||
} | |||
struct DroppedData | |||
@@ -1902,7 +1947,9 @@ public: | |||
double getPlatformScaleFactor() const noexcept override | |||
{ | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
#if ! JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
return 1.0; | |||
#else | |||
if (! isPerMonitorDPIAwareWindow (hwnd)) | |||
return 1.0; | |||
@@ -1916,8 +1963,6 @@ public: | |||
} | |||
return scaleFactor; | |||
#else | |||
return 1.0; | |||
#endif | |||
} | |||
@@ -2153,6 +2198,14 @@ private: | |||
L"", type, 0, 0, 0, 0, parentToAddTo, nullptr, | |||
(HINSTANCE) Process::getCurrentModuleInstanceHandle(), nullptr); | |||
#if JUCE_DEBUG | |||
// The DPI-awareness context of this window and JUCE's hidden message window are different. | |||
// You normally want these to match otherwise timer events and async messages will happen | |||
// in a different context to normal HWND messages which can cause issues with UI scaling. | |||
jassert (isPerMonitorDPIAwareWindow (hwnd) == isPerMonitorDPIAwareWindow (juce_messageWindowHandle) | |||
|| isInScopedDPIAwarenessDisabler()); | |||
#endif | |||
if (hwnd != nullptr) | |||
{ | |||
SetWindowLongPtr (hwnd, 0, 0); | |||
@@ -2179,19 +2232,8 @@ private: | |||
setDPIAwareness(); | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
if (isPerMonitorDPIAwareThread()) | |||
{ | |||
auto bounds = component.getBounds(); | |||
if (bounds.isEmpty()) | |||
scaleFactor = Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale; | |||
else | |||
scaleFactor = Desktop::getInstance().getDisplays().getDisplayForRect (bounds)->scale; | |||
scaleFactor /= Desktop::getInstance().getGlobalScaleFactor(); | |||
} | |||
#endif | |||
scaleFactor = getScaleFactorForWindow (hwnd); | |||
setMessageFilter(); | |||
updateBorderSize(); | |||
@@ -3508,20 +3550,18 @@ private: | |||
Point<float> getPointFromLocalLParam (LPARAM lParam) noexcept | |||
{ | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
auto p = pointFromPOINT (getPOINTFromLParam (lParam)); | |||
if (isPerMonitorDPIAwareWindow (hwnd)) | |||
{ | |||
// LPARAM is relative to this window's top-left but may be on a different monitor so we need to calculate the | |||
// physical screen position and then convert this to local logical coordinates | |||
auto localPos = getPOINTFromLParam (lParam); | |||
auto r = getWindowScreenRect (hwnd); | |||
return globalToLocal (Desktop::getInstance().getDisplays().physicalToLogical (pointFromPOINT ({ r.left + localPos.x + roundToInt (windowBorder.getLeft() * scaleFactor), | |||
r.top + localPos.y + roundToInt (windowBorder.getTop() * scaleFactor) })).toFloat()); | |||
return globalToLocal (Desktop::getInstance().getDisplays().physicalToLogical (pointFromPOINT ({ r.left + p.x + roundToInt (windowBorder.getLeft() * scaleFactor), | |||
r.top + p.y + roundToInt (windowBorder.getTop() * scaleFactor) })).toFloat()); | |||
} | |||
#endif | |||
return { static_cast<float> (GET_X_LPARAM (lParam)), static_cast<float> (GET_Y_LPARAM (lParam)) }; | |||
return p.toFloat(); | |||
} | |||
Point<float> getCurrentMousePos() noexcept | |||
@@ -4431,10 +4471,8 @@ Point<float> MouseInputSource::getCurrentRawMousePosition() | |||
auto p = pointFromPOINT (mousePos); | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
if (isPerMonitorDPIAwareThread()) | |||
p = Desktop::getInstance().getDisplays().physicalToLogical (p); | |||
#endif | |||
return p.toFloat(); | |||
} | |||
@@ -1849,14 +1849,14 @@ Rectangle<int> XWindowSystem::getWindowBounds (::Window windowH, ::Window parent | |||
} | |||
else | |||
{ | |||
parentScreenPosition = Desktop::getInstance().getDisplays().physicalToLogical (Point<int> (rootX, rootY)); | |||
parentScreenPosition = Point<int> (rootX, rootY); | |||
} | |||
} | |||
return { wx, wy, (int) ww, (int) wh }; | |||
} | |||
Point<int> XWindowSystem::getParentScreenPosition() const | |||
Point<int> XWindowSystem::getPhysicalParentScreenPosition() const | |||
{ | |||
return parentScreenPosition; | |||
} | |||
@@ -2474,7 +2474,7 @@ Array<Displays::Display> XWindowSystem::findDisplays (float masterScale) const | |||
+ ((static_cast<double> (crtc->height) * 25.4 * 0.5) / static_cast<double> (output->mm_height)); | |||
auto scale = DisplayHelpers::getDisplayScale (output->name, d.dpi); | |||
scale = (scale <= 0.1 ? 1.0 : scale); | |||
scale = (scale <= 0.1 || ! JUCEApplicationBase::isStandaloneApp()) ? 1.0 : scale; | |||
d.scale = masterScale * scale; | |||
@@ -109,7 +109,7 @@ public: | |||
BorderSize<int> getBorderSize (::Window) const; | |||
Rectangle<int> getWindowBounds (::Window, ::Window parentWindow); | |||
Point<int> getParentScreenPosition() const; | |||
Point<int> getPhysicalParentScreenPosition() const; | |||
bool contains (::Window, Point<int> localPos) const; | |||
@@ -749,7 +749,7 @@ public: | |||
? e.position.x - mouseDragStartPos.x | |||
: mouseDragStartPos.y - e.position.y; | |||
newPos = owner.valueToProportionOfLength (valueOnMouseDown) | |||
newPos = owner.valueToProportionOfLength (valueWhenLastDragged) | |||
+ mouseDiff * (1.0 / pixelsForFullDragExtent); | |||
if (style == IncDecButtons) | |||
@@ -763,7 +763,7 @@ public: | |||
auto mouseDiff = (e.position.x - mouseDragStartPos.x) | |||
+ (mouseDragStartPos.y - e.position.y); | |||
newPos = owner.valueToProportionOfLength (valueOnMouseDown) | |||
newPos = owner.valueToProportionOfLength (valueWhenLastDragged) | |||
+ mouseDiff * (1.0 / pixelsForFullDragExtent); | |||
} | |||
else | |||
@@ -774,6 +774,7 @@ public: | |||
newPos = 1.0 - newPos; | |||
} | |||
mouseDragStartPos = e.position; | |||
newPos = (isRotary() && ! rotaryParams.stopAtEnd) ? newPos - std::floor (newPos) | |||
: jlimit (0.0, 1.0, newPos); | |||
valueWhenLastDragged = owner.proportionOfLengthToValue (newPos); | |||
@@ -833,6 +833,11 @@ struct TextEditor::TextHolderComponent : public Component, | |||
{ | |||
owner.drawContent (g); | |||
} | |||
void setTopLeftPosition(Point<int> new_position) override { | |||
Component::setTopLeftPosition(new_position); | |||
owner.textChanged(); | |||
} | |||
void restartTimer() | |||
{ | |||
@@ -1558,6 +1563,9 @@ void TextEditor::moveCaretTo (const int newPosition, const bool isSelecting) | |||
moveCaret (newPosition); | |||
selection = Range<int>::emptyRange (getCaretPosition()); | |||
} | |||
if (listeners.size() != 0 || onTextChange != nullptr) | |||
postCommandMessage (TextEditorDefs::textChangeMessageId); | |||
} | |||
int TextEditor::getTextIndexAt (const int x, const int y) | |||
@@ -2141,6 +2149,9 @@ void TextEditor::focusGained (FocusChangeType cause) | |||
repaint(); | |||
updateCaretPosition(); | |||
if (listeners.size() != 0 || onTextChange != nullptr) | |||
postCommandMessage (TextEditorDefs::textChangeMessageId); | |||
} | |||
void TextEditor::focusLost (FocusChangeType) | |||
@@ -26,8 +26,6 @@ | |||
namespace juce | |||
{ | |||
#if (JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE) || DOXYGEN | |||
//============================================================================== | |||
/** | |||
A Windows-specific class that temporarily sets the DPI awareness context of | |||
@@ -52,6 +50,5 @@ public: | |||
private: | |||
void* previousContext = nullptr; | |||
}; | |||
#endif | |||
} // namespace juce |
@@ -39,6 +39,7 @@ | |||
#define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1 | |||
#define JUCE_GRAPHICS_INCLUDE_COREGRAPHICS_HELPERS 1 | |||
#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 | |||
#define JUCE_GUI_BASICS_INCLUDE_SCOPED_THREAD_DPI_AWARENESS_SETTER 1 | |||
#ifndef JUCE_PUSH_NOTIFICATIONS | |||
#define JUCE_PUSH_NOTIFICATIONS 0 | |||
@@ -192,3 +193,9 @@ | |||
#include "native/juce_android_WebBrowserComponent.cpp" | |||
#endif | |||
#endif | |||
//============================================================================== | |||
#if ! JUCE_WINDOWS | |||
juce::ScopedDPIAwarenessDisabler::ScopedDPIAwarenessDisabler() { ignoreUnused (previousContext); } | |||
juce::ScopedDPIAwarenessDisabler::~ScopedDPIAwarenessDisabler() {} | |||
#endif |
@@ -26,8 +26,6 @@ | |||
namespace juce | |||
{ | |||
void setThreadDPIAwarenessForWindow (HWND); | |||
class HWNDComponent::Pimpl : public ComponentMovementWatcher | |||
{ | |||
public: | |||
@@ -52,13 +50,13 @@ public: | |||
{ | |||
auto area = (peer->getAreaCoveredBy (owner).toFloat() * peer->getPlatformScaleFactor()).getSmallestIntegerContainer(); | |||
setThreadDPIAwarenessForWindow (hwnd); | |||
UINT flagsToSend = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER; | |||
if (! wasMoved) flagsToSend |= SWP_NOMOVE; | |||
if (! wasResized) flagsToSend |= SWP_NOSIZE; | |||
ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { hwnd }; | |||
SetWindowPos (hwnd, nullptr, area.getX(), area.getY(), area.getWidth(), area.getHeight(), flagsToSend); | |||
} | |||
} | |||
@@ -101,7 +99,7 @@ public: | |||
{ | |||
if (auto* peer = owner.getPeer()) | |||
{ | |||
setThreadDPIAwarenessForWindow (hwnd); | |||
ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { hwnd }; | |||
RECT r; | |||
GetWindowRect (hwnd, &r); | |||
@@ -37,6 +37,7 @@ | |||
#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | |||
#define JUCE_GRAPHICS_INCLUDE_COREGRAPHICS_HELPERS 1 | |||
#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 | |||
#define JUCE_GUI_BASICS_INCLUDE_SCOPED_THREAD_DPI_AWARENESS_SETTER 1 | |||
#include "juce_opengl.h" | |||
@@ -124,7 +124,7 @@ | |||
It's mandatory in OpenGL 3.0 to specify the GLSL version. | |||
*/ | |||
#if JUCE_OPENGL3 | |||
#if JUCE_OPENGL_ES | |||
#if JUCE_OPENGL_ES || OPENGL_ES | |||
#define JUCE_GLSL_VERSION "#version 300 es" | |||
#else | |||
#define JUCE_GLSL_VERSION "#version 150" | |||
@@ -126,6 +126,14 @@ enum MissingOpenGLDefinitions | |||
GL_DYNAMIC_DRAW = 0x88E8, | |||
GL_STREAM_DRAW = 0x88E0, | |||
GL_GEOMETRY_SHADER = 0x8DD9, | |||
GL_LINE_STRIP_ADJACENCY = 0x000B, | |||
GL_INTERLEAVED_ATTRIBS = 0x8C8C, | |||
GL_STATIC_READ = 0x88E5, | |||
GL_TRANSFORM_FEEDBACK_BUFFER = 0x8C8E, | |||
GL_RASTERIZER_DISCARD = 0x8C89, | |||
GL_MAP_READ_BIT = 0x0001, | |||
WGL_NUMBER_PIXEL_FORMATS_ARB = 0x2000, | |||
WGL_DRAW_TO_WINDOW_ARB = 0x2001, | |||
WGL_ACCELERATION_ARB = 0x2003, | |||
@@ -69,8 +69,8 @@ public: | |||
GLint attribs[] = | |||
{ | |||
GLX_RGBA, | |||
GLX_DOUBLEBUFFER, | |||
GLX_RENDER_TYPE, GLX_RGBA_BIT, | |||
GLX_DOUBLEBUFFER, True, | |||
GLX_RED_SIZE, cPixelFormat.redBits, | |||
GLX_GREEN_SIZE, cPixelFormat.greenBits, | |||
GLX_BLUE_SIZE, cPixelFormat.blueBits, | |||
@@ -81,13 +81,21 @@ public: | |||
GLX_ACCUM_GREEN_SIZE, cPixelFormat.accumulationBufferGreenBits, | |||
GLX_ACCUM_BLUE_SIZE, cPixelFormat.accumulationBufferBlueBits, | |||
GLX_ACCUM_ALPHA_SIZE, cPixelFormat.accumulationBufferAlphaBits, | |||
GLX_X_RENDERABLE, True, | |||
None | |||
}; | |||
bestVisual = glXChooseVisual (display, X11Symbols::getInstance()->xDefaultScreen (display), attribs); | |||
if (bestVisual == nullptr) | |||
int countFbConfigs; | |||
fbConfig = glXChooseFBConfig (display, DefaultScreen (display), attribs, &countFbConfigs); | |||
if (fbConfig == nullptr) | |||
return; | |||
bestVisual = glXGetVisualFromFBConfig (display, *fbConfig); | |||
if (bestVisual == nullptr) { | |||
X11Symbols::getInstance()->xFree (fbConfig); | |||
return; | |||
} | |||
auto* peer = component.getPeer(); | |||
jassert (peer != nullptr); | |||
@@ -139,6 +147,9 @@ public: | |||
} | |||
} | |||
if (fbConfig != nullptr) | |||
X11Symbols::getInstance()->xFree (fbConfig); | |||
if (bestVisual != nullptr) | |||
X11Symbols::getInstance()->xFree (bestVisual); | |||
} | |||
@@ -146,7 +157,18 @@ public: | |||
bool initialiseOnRenderThread (OpenGLContext& c) | |||
{ | |||
XWindowSystemUtilities::ScopedXLock xLock; | |||
renderContext = glXCreateContext (display, bestVisual, (GLXContext) contextToShareWith, GL_TRUE); | |||
PFNGLXCREATECONTEXTATTRIBSARBPROC createContextAttribs; | |||
int attribs[] = { | |||
GLX_CONTEXT_MAJOR_VERSION_ARB, 3, | |||
GLX_CONTEXT_MINOR_VERSION_ARB, 2, | |||
GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, | |||
0 | |||
}; | |||
createContextAttribs = (PFNGLXCREATECONTEXTATTRIBSARBPROC) | |||
OpenGLHelpers::getExtensionFunction("glXCreateContextAttribsARB"); | |||
renderContext = createContextAttribs (display, *fbConfig, (GLXContext) contextToShareWith, GL_TRUE, attribs); | |||
c.makeActive(); | |||
context = &c; | |||
@@ -240,6 +262,7 @@ private: | |||
int swapFrames = 1; | |||
Rectangle<int> bounds; | |||
XVisualInfo* bestVisual = nullptr; | |||
GLXFBConfig* fbConfig = nullptr; | |||
void* contextToShareWith; | |||
OpenGLContext* context = nullptr; | |||
@@ -28,10 +28,6 @@ namespace juce | |||
extern ComponentPeer* createNonRepaintingEmbeddedWindowsPeer (Component&, void* parent); | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
extern void setThreadDPIAwarenessForWindow (HWND); | |||
#endif | |||
//============================================================================== | |||
class OpenGLContext::NativeContext | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
@@ -97,15 +93,17 @@ public: | |||
bool initialiseOnRenderThread (OpenGLContext& c) | |||
{ | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
setThreadDPIAwarenessForWindow ((HWND) nativeWindow->getNativeHandle()); | |||
#endif | |||
threadAwarenessSetter = std::make_unique<ScopedThreadDPIAwarenessSetter> (nativeWindow->getNativeHandle()); | |||
context = &c; | |||
return true; | |||
} | |||
void shutdownOnRenderThread() { deactivateCurrentContext(); context = nullptr; } | |||
void shutdownOnRenderThread() | |||
{ | |||
deactivateCurrentContext(); | |||
context = nullptr; | |||
threadAwarenessSetter = nullptr; | |||
} | |||
static void deactivateCurrentContext() { wglMakeCurrent (nullptr, nullptr); } | |||
bool makeActive() const noexcept { return isActive() || wglMakeCurrent (dc, renderContext) != FALSE; } | |||
@@ -170,6 +168,7 @@ private: | |||
std::unique_ptr<DummyComponent> dummyComponent; | |||
std::unique_ptr<ComponentPeer> nativeWindow; | |||
std::unique_ptr<ScopedThreadDPIAwarenessSetter> threadAwarenessSetter; | |||
HGLRC renderContext; | |||
HDC dc; | |||
OpenGLContext* context = {}; | |||
@@ -219,7 +218,13 @@ private: | |||
void createNativeWindow (Component& component) | |||
{ | |||
auto* topComp = component.getTopLevelComponent(); | |||
nativeWindow.reset (createNonRepaintingEmbeddedWindowsPeer (*dummyComponent, topComp->getWindowHandle())); | |||
{ | |||
auto* parentHWND = topComp->getWindowHandle(); | |||
ScopedThreadDPIAwarenessSetter setter { parentHWND }; | |||
nativeWindow.reset (createNonRepaintingEmbeddedWindowsPeer (*dummyComponent, parentHWND)); | |||
} | |||
if (auto* peer = topComp->getPeer()) | |||
{ | |||
@@ -285,6 +290,8 @@ private: | |||
atts[n++] = WGL_DRAW_TO_WINDOW_ARB; atts[n++] = GL_TRUE; | |||
atts[n++] = WGL_SUPPORT_OPENGL_ARB; atts[n++] = GL_TRUE; | |||
atts[n++] = WGL_CONTEXT_MAJOR_VERSION_ARB; atts[n++] = 3; | |||
atts[n++] = WGL_CONTEXT_MINOR_VERSION_ARB; atts[n++] = 2; | |||
atts[n++] = WGL_DOUBLE_BUFFER_ARB; atts[n++] = GL_TRUE; | |||
atts[n++] = WGL_PIXEL_TYPE_ARB; atts[n++] = WGL_TYPE_RGBA_ARB; | |||
atts[n++] = WGL_ACCELERATION_ARB; | |||
@@ -83,7 +83,7 @@ void OpenGLHelpers::enableScissorTest (Rectangle<int> clip) | |||
String OpenGLHelpers::translateVertexShaderToV3 (const String& code) | |||
{ | |||
#if JUCE_OPENGL3 | |||
#if JUCE_OPENGL3 || OPENGL_ES | |||
if (OpenGLShaderProgram::getLanguageVersion() > 1.2) | |||
{ | |||
String output; | |||
@@ -119,7 +119,7 @@ String OpenGLHelpers::translateVertexShaderToV3 (const String& code) | |||
String OpenGLHelpers::translateFragmentShaderToV3 (const String& code) | |||
{ | |||
#if JUCE_OPENGL3 | |||
#if JUCE_OPENGL3 || OPENGL_ES | |||
if (OpenGLShaderProgram::getLanguageVersion() > 1.2) | |||
return JUCE_GLSL_VERSION "\n" | |||
"out " JUCE_MEDIUMP " vec4 fragColor;\n" | |||