Signed-off-by: falkTX <falktx@falktx.com>tags/2021-05-28
| @@ -155,5 +155,124 @@ bool isOnTouchDevice() | |||
| return isTouch; | |||
| } | |||
| //============================================================================== | |||
| static AutoScale autoScaleFromString (StringRef str) | |||
| { | |||
| if (str.isEmpty()) return AutoScale::useDefault; | |||
| if (str == CharPointer_ASCII { "0" }) return AutoScale::scaled; | |||
| if (str == CharPointer_ASCII { "1" }) return AutoScale::unscaled; | |||
| jassertfalse; | |||
| return AutoScale::useDefault; | |||
| } | |||
| static const char* autoScaleToString (AutoScale autoScale) | |||
| { | |||
| if (autoScale == AutoScale::scaled) return "0"; | |||
| if (autoScale == AutoScale::unscaled) return "1"; | |||
| return {}; | |||
| } | |||
| AutoScale getAutoScaleValueForPlugin (const String& identifier) | |||
| { | |||
| if (identifier.isNotEmpty()) | |||
| { | |||
| auto plugins = StringArray::fromLines (getAppProperties().getUserSettings()->getValue ("autoScalePlugins")); | |||
| plugins.removeEmptyStrings(); | |||
| for (auto& plugin : plugins) | |||
| { | |||
| auto fromIdentifier = plugin.fromFirstOccurrenceOf (identifier, false, false); | |||
| if (fromIdentifier.isNotEmpty()) | |||
| return autoScaleFromString (fromIdentifier.fromFirstOccurrenceOf (":", false, false)); | |||
| } | |||
| } | |||
| return AutoScale::useDefault; | |||
| } | |||
| void setAutoScaleValueForPlugin (const String& identifier, AutoScale s) | |||
| { | |||
| auto plugins = StringArray::fromLines (getAppProperties().getUserSettings()->getValue ("autoScalePlugins")); | |||
| plugins.removeEmptyStrings(); | |||
| auto index = [identifier, plugins] | |||
| { | |||
| auto it = std::find_if (plugins.begin(), plugins.end(), | |||
| [&] (const String& str) { return str.startsWith (identifier); }); | |||
| return (int) std::distance (plugins.begin(), it); | |||
| }(); | |||
| if (s == AutoScale::useDefault && index != plugins.size()) | |||
| { | |||
| plugins.remove (index); | |||
| } | |||
| else | |||
| { | |||
| auto str = identifier + ":" + autoScaleToString (s); | |||
| if (index != plugins.size()) | |||
| plugins.getReference (index) = str; | |||
| else | |||
| plugins.add (str); | |||
| } | |||
| getAppProperties().getUserSettings()->setValue ("autoScalePlugins", plugins.joinIntoString ("\n")); | |||
| } | |||
| static bool isAutoScaleAvailableForPlugin (const PluginDescription& description) | |||
| { | |||
| return autoScaleOptionAvailable | |||
| && description.pluginFormatName.containsIgnoreCase ("VST"); | |||
| } | |||
| bool shouldAutoScalePlugin (const PluginDescription& description) | |||
| { | |||
| if (! isAutoScaleAvailableForPlugin (description)) | |||
| return false; | |||
| const auto scaleValue = getAutoScaleValueForPlugin (description.fileOrIdentifier); | |||
| return (scaleValue == AutoScale::scaled | |||
| || (scaleValue == AutoScale::useDefault | |||
| && getAppProperties().getUserSettings()->getBoolValue ("autoScalePluginWindows"))); | |||
| } | |||
| void addPluginAutoScaleOptionsSubMenu (AudioPluginInstance* pluginInstance, | |||
| PopupMenu& menu) | |||
| { | |||
| if (pluginInstance == nullptr) | |||
| return; | |||
| auto description = pluginInstance->getPluginDescription(); | |||
| if (! isAutoScaleAvailableForPlugin (description)) | |||
| return; | |||
| auto identifier = description.fileOrIdentifier; | |||
| PopupMenu autoScaleMenu; | |||
| autoScaleMenu.addItem ("Default", | |||
| true, | |||
| getAutoScaleValueForPlugin (identifier) == AutoScale::useDefault, | |||
| [identifier] { setAutoScaleValueForPlugin (identifier, AutoScale::useDefault); }); | |||
| autoScaleMenu.addItem ("Enabled", | |||
| true, | |||
| getAutoScaleValueForPlugin (identifier) == AutoScale::scaled, | |||
| [identifier] { setAutoScaleValueForPlugin (identifier, AutoScale::scaled); }); | |||
| autoScaleMenu.addItem ("Disabled", | |||
| true, | |||
| getAutoScaleValueForPlugin (identifier) == AutoScale::unscaled, | |||
| [identifier] { setAutoScaleValueForPlugin (identifier, AutoScale::unscaled); }); | |||
| menu.addSubMenu ("Auto-scale window", autoScaleMenu); | |||
| } | |||
| // This kicks the whole thing off.. | |||
| START_JUCE_APPLICATION (PluginHostApp) | |||
| @@ -29,6 +29,11 @@ | |||
| #include "InternalPlugins.h" | |||
| #include "../UI/GraphEditorPanel.h" | |||
| static std::unique_ptr<ScopedDPIAwarenessDisabler> makeDPIAwarenessDisablerForPlugin (const PluginDescription& desc) | |||
| { | |||
| return shouldAutoScalePlugin (desc) ? std::make_unique<ScopedDPIAwarenessDisabler>() | |||
| : nullptr; | |||
| } | |||
| //============================================================================== | |||
| PluginGraph::PluginGraph (AudioPluginFormatManager& fm) | |||
| @@ -76,10 +81,12 @@ AudioProcessorGraph::Node::Ptr PluginGraph::getNodeForName (const String& name) | |||
| void PluginGraph::addPlugin (const PluginDescription& desc, Point<double> pos) | |||
| { | |||
| std::shared_ptr<ScopedDPIAwarenessDisabler> dpiDisabler = makeDPIAwarenessDisablerForPlugin (desc); | |||
| formatManager.createPluginInstanceAsync (desc, | |||
| graph.getSampleRate(), | |||
| graph.getBlockSize(), | |||
| [this, pos] (std::unique_ptr<AudioPluginInstance> instance, const String& error) | |||
| [this, pos, dpiDisabler] (std::unique_ptr<AudioPluginInstance> instance, const String& error) | |||
| { | |||
| addPluginCallback (std::move (instance), error, pos); | |||
| }); | |||
| @@ -156,18 +163,10 @@ PluginWindow* PluginGraph::getOrCreateWindowFor (AudioProcessorGraph::Node* node | |||
| getCommandManager().invokeDirectly (CommandIDs::showAudioSettings, false); | |||
| return nullptr; | |||
| } | |||
| } | |||
| #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
| if (! node->properties["DPIAware"] | |||
| && ! node->getProcessor()->getName().contains ("Kontakt")) // Kontakt doesn't behave correctly in DPI unaware mode... | |||
| { | |||
| ScopedDPIAwarenessDisabler disableDPIAwareness; | |||
| auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (description); | |||
| return activePluginWindows.add (new PluginWindow (node, type, activePluginWindows)); | |||
| } | |||
| #endif | |||
| return activePluginWindows.add (new PluginWindow (node, type, activePluginWindows)); | |||
| } | |||
| return nullptr; | |||
| @@ -332,9 +331,6 @@ static XmlElement* createNodeXml (AudioProcessorGraph::Node* const node) noexcep | |||
| e->setAttribute ("uid", (int) node->nodeID.uid); | |||
| e->setAttribute ("x", node->properties ["x"].toString()); | |||
| e->setAttribute ("y", node->properties ["y"].toString()); | |||
| #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
| e->setAttribute ("DPIAware", node->properties["DPIAware"].toString()); | |||
| #endif | |||
| for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i) | |||
| { | |||
| @@ -383,10 +379,16 @@ void PluginGraph::createNodeFromXml (const XmlElement& xml) | |||
| break; | |||
| } | |||
| String errorMessage; | |||
| auto createInstance = [this, pd] | |||
| { | |||
| String errorMessage; | |||
| auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (pd); | |||
| return formatManager.createPluginInstance (pd, graph.getSampleRate(), | |||
| graph.getBlockSize(), errorMessage); | |||
| }; | |||
| if (auto instance = formatManager.createPluginInstance (pd, graph.getSampleRate(), | |||
| graph.getBlockSize(), errorMessage)) | |||
| if (auto instance = createInstance()) | |||
| { | |||
| if (auto* layoutEntity = xml.getChildByName ("LAYOUT")) | |||
| { | |||
| @@ -408,11 +410,8 @@ void PluginGraph::createNodeFromXml (const XmlElement& xml) | |||
| node->getProcessor()->setStateInformation (m.getData(), (int) m.getSize()); | |||
| } | |||
| node->properties.set ("x", xml.getDoubleAttribute ("x")); | |||
| node->properties.set ("y", xml.getDoubleAttribute ("y")); | |||
| #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
| node->properties.set ("DPIAware", xml.getDoubleAttribute ("DPIAware")); | |||
| #endif | |||
| node->properties.set ("x", xml.getDoubleAttribute ("x")); | |||
| node->properties.set ("y", xml.getDoubleAttribute ("y")); | |||
| for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i) | |||
| { | |||
| @@ -403,21 +403,14 @@ struct GraphEditorPanel::PluginComponent : public Component, | |||
| menu->addItem (2, "Disconnect all pins"); | |||
| menu->addItem (3, "Toggle Bypass"); | |||
| if (getProcessor()->hasEditor()) | |||
| { | |||
| menu->addSeparator(); | |||
| menu->addItem (10, "Show plugin GUI"); | |||
| menu->addItem (11, "Show all programs"); | |||
| menu->addItem (12, "Show all parameters"); | |||
| #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
| auto isTicked = false; | |||
| if (auto* node = graph.graph.getNodeForId (pluginID)) | |||
| isTicked = node->properties["DPIAware"]; | |||
| menu->addItem (13, "Enable DPI awareness", true, isTicked); | |||
| #endif | |||
| menu->addItem (14, "Show debug log"); | |||
| } | |||
| menu->addSeparator(); | |||
| menu->addItem (10, "Show plugin GUI"); | |||
| menu->addItem (11, "Show all programs"); | |||
| menu->addItem (12, "Show all parameters"); | |||
| menu->addItem (13, "Show debug log"); | |||
| if (autoScaleOptionAvailable) | |||
| addPluginAutoScaleOptionsSubMenu (dynamic_cast<AudioPluginInstance*> (getProcessor()), *menu); | |||
| menu->addSeparator(); | |||
| menu->addItem (20, "Configure Audio I/O"); | |||
| @@ -441,15 +434,7 @@ struct GraphEditorPanel::PluginComponent : public Component, | |||
| case 10: showWindow (PluginWindow::Type::normal); break; | |||
| case 11: showWindow (PluginWindow::Type::programs); break; | |||
| case 12: showWindow (PluginWindow::Type::generic) ; break; | |||
| #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
| case 13: | |||
| { | |||
| if (auto* node = graph.graph.getNodeForId (pluginID)) | |||
| node->properties.set ("DPIAware", ! node->properties ["DPIAware"]); | |||
| break; | |||
| } | |||
| #endif | |||
| case 14: showWindow (PluginWindow::Type::debug); break; | |||
| case 13: showWindow (PluginWindow::Type::debug); break; | |||
| case 20: showWindow (PluginWindow::Type::audioIO); break; | |||
| case 21: testStateSaveLoad(); break; | |||
| @@ -277,9 +277,9 @@ PopupMenu MainHostWindow::getMenuForIndex (int topLevelMenuIndex, const String& | |||
| // "Plugins" menu | |||
| PopupMenu pluginsMenu; | |||
| addPluginsToMenu (pluginsMenu); | |||
| menu.addSubMenu ("Create plugin", pluginsMenu); | |||
| menu.addSubMenu ("Create Plug-in", pluginsMenu); | |||
| menu.addSeparator(); | |||
| menu.addItem (250, "Delete all plugins"); | |||
| menu.addItem (250, "Delete All Plug-ins"); | |||
| } | |||
| else if (topLevelMenuIndex == 2) | |||
| { | |||
| @@ -288,17 +288,20 @@ PopupMenu MainHostWindow::getMenuForIndex (int topLevelMenuIndex, const String& | |||
| menu.addCommandItem (&getCommandManager(), CommandIDs::showPluginListEditor); | |||
| PopupMenu sortTypeMenu; | |||
| sortTypeMenu.addItem (200, "List plugins in default order", true, pluginSortMethod == KnownPluginList::defaultOrder); | |||
| sortTypeMenu.addItem (201, "List plugins in alphabetical order", true, pluginSortMethod == KnownPluginList::sortAlphabetically); | |||
| sortTypeMenu.addItem (202, "List plugins by category", true, pluginSortMethod == KnownPluginList::sortByCategory); | |||
| sortTypeMenu.addItem (203, "List plugins by manufacturer", true, pluginSortMethod == KnownPluginList::sortByManufacturer); | |||
| sortTypeMenu.addItem (204, "List plugins based on the directory structure", true, pluginSortMethod == KnownPluginList::sortByFileSystemLocation); | |||
| menu.addSubMenu ("Plugin menu type", sortTypeMenu); | |||
| sortTypeMenu.addItem (200, "List Plug-ins in Default Order", true, pluginSortMethod == KnownPluginList::defaultOrder); | |||
| sortTypeMenu.addItem (201, "List Plug-ins in Alphabetical Order", true, pluginSortMethod == KnownPluginList::sortAlphabetically); | |||
| sortTypeMenu.addItem (202, "List Plug-ins by Category", true, pluginSortMethod == KnownPluginList::sortByCategory); | |||
| sortTypeMenu.addItem (203, "List Plug-ins by Manufacturer", true, pluginSortMethod == KnownPluginList::sortByManufacturer); | |||
| sortTypeMenu.addItem (204, "List Plug-ins Based on the Directory Structure", true, pluginSortMethod == KnownPluginList::sortByFileSystemLocation); | |||
| menu.addSubMenu ("Plug-in Menu Type", sortTypeMenu); | |||
| menu.addSeparator(); | |||
| menu.addCommandItem (&getCommandManager(), CommandIDs::showAudioSettings); | |||
| menu.addCommandItem (&getCommandManager(), CommandIDs::toggleDoublePrecision); | |||
| if (autoScaleOptionAvailable) | |||
| menu.addCommandItem (&getCommandManager(), CommandIDs::autoScalePluginWindows); | |||
| menu.addSeparator(); | |||
| menu.addCommandItem (&getCommandManager(), CommandIDs::aboutBox); | |||
| } | |||
| @@ -414,7 +417,8 @@ void MainHostWindow::getAllCommands (Array<CommandID>& commands) | |||
| CommandIDs::showAudioSettings, | |||
| CommandIDs::toggleDoublePrecision, | |||
| CommandIDs::aboutBox, | |||
| CommandIDs::allWindowsForward | |||
| CommandIDs::allWindowsForward, | |||
| CommandIDs::autoScalePluginWindows | |||
| }; | |||
| commands.addArray (ids, numElementsInArray (ids)); | |||
| @@ -451,12 +455,12 @@ void MainHostWindow::getCommandInfo (const CommandID commandID, ApplicationComma | |||
| #endif | |||
| case CommandIDs::showPluginListEditor: | |||
| result.setInfo ("Edit the list of available plug-Ins...", String(), category, 0); | |||
| result.setInfo ("Edit the List of Available Plug-ins...", {}, category, 0); | |||
| result.addDefaultKeypress ('p', ModifierKeys::commandModifier); | |||
| break; | |||
| case CommandIDs::showAudioSettings: | |||
| result.setInfo ("Change the audio device settings", String(), category, 0); | |||
| result.setInfo ("Change the Audio Device Settings", {}, category, 0); | |||
| result.addDefaultKeypress ('a', ModifierKeys::commandModifier); | |||
| break; | |||
| @@ -465,7 +469,7 @@ void MainHostWindow::getCommandInfo (const CommandID commandID, ApplicationComma | |||
| break; | |||
| case CommandIDs::aboutBox: | |||
| result.setInfo ("About...", String(), category, 0); | |||
| result.setInfo ("About...", {}, category, 0); | |||
| break; | |||
| case CommandIDs::allWindowsForward: | |||
| @@ -473,6 +477,10 @@ void MainHostWindow::getCommandInfo (const CommandID commandID, ApplicationComma | |||
| result.addDefaultKeypress ('w', ModifierKeys::commandModifier); | |||
| break; | |||
| case CommandIDs::autoScalePluginWindows: | |||
| updateAutoScaleMenuItem (result); | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| @@ -518,20 +526,30 @@ bool MainHostWindow::perform (const InvocationInfo& info) | |||
| case CommandIDs::toggleDoublePrecision: | |||
| if (auto* props = getAppProperties().getUserSettings()) | |||
| { | |||
| bool newIsDoublePrecision = ! isDoublePrecisionProcessing(); | |||
| auto newIsDoublePrecision = ! isDoublePrecisionProcessingEnabled(); | |||
| props->setValue ("doublePrecisionProcessing", var (newIsDoublePrecision)); | |||
| { | |||
| ApplicationCommandInfo cmdInfo (info.commandID); | |||
| updatePrecisionMenuItem (cmdInfo); | |||
| menuItemsChanged(); | |||
| } | |||
| ApplicationCommandInfo cmdInfo (info.commandID); | |||
| updatePrecisionMenuItem (cmdInfo); | |||
| menuItemsChanged(); | |||
| if (graphHolder != nullptr) | |||
| graphHolder->setDoublePrecision (newIsDoublePrecision); | |||
| } | |||
| break; | |||
| case CommandIDs::autoScalePluginWindows: | |||
| if (auto* props = getAppProperties().getUserSettings()) | |||
| { | |||
| auto newAutoScale = ! isAutoScalePluginWindowsEnabled(); | |||
| props->setValue ("autoScalePluginWindows", var (newAutoScale)); | |||
| ApplicationCommandInfo cmdInfo (info.commandID); | |||
| updateAutoScaleMenuItem (cmdInfo); | |||
| menuItemsChanged(); | |||
| } | |||
| break; | |||
| case CommandIDs::aboutBox: | |||
| // TODO | |||
| break; | |||
| @@ -633,7 +651,7 @@ void MainHostWindow::filesDropped (const StringArray& files, int x, int y) | |||
| } | |||
| } | |||
| bool MainHostWindow::isDoublePrecisionProcessing() | |||
| bool MainHostWindow::isDoublePrecisionProcessingEnabled() | |||
| { | |||
| if (auto* props = getAppProperties().getUserSettings()) | |||
| return props->getBoolValue ("doublePrecisionProcessing", false); | |||
| @@ -641,8 +659,22 @@ bool MainHostWindow::isDoublePrecisionProcessing() | |||
| return false; | |||
| } | |||
| bool MainHostWindow::isAutoScalePluginWindowsEnabled() | |||
| { | |||
| if (auto* props = getAppProperties().getUserSettings()) | |||
| return props->getBoolValue ("autoScalePluginWindows", false); | |||
| return false; | |||
| } | |||
| void MainHostWindow::updatePrecisionMenuItem (ApplicationCommandInfo& info) | |||
| { | |||
| info.setInfo ("Double floating point precision rendering", String(), "General", 0); | |||
| info.setTicked (isDoublePrecisionProcessing()); | |||
| info.setInfo ("Double Floating-Point Precision Rendering", {}, "General", 0); | |||
| info.setTicked (isDoublePrecisionProcessingEnabled()); | |||
| } | |||
| void MainHostWindow::updateAutoScaleMenuItem (ApplicationCommandInfo& info) | |||
| { | |||
| info.setInfo ("Auto-Scale Plug-in Windows", {}, "General", 0); | |||
| info.setTicked (isAutoScalePluginWindowsEnabled()); | |||
| } | |||
| @@ -43,12 +43,34 @@ namespace CommandIDs | |||
| static const int aboutBox = 0x30300; | |||
| static const int allWindowsForward = 0x30400; | |||
| static const int toggleDoublePrecision = 0x30500; | |||
| static const int autoScalePluginWindows = 0x30600; | |||
| } | |||
| //============================================================================== | |||
| ApplicationCommandManager& getCommandManager(); | |||
| ApplicationProperties& getAppProperties(); | |||
| bool isOnTouchDevice(); | |||
| //============================================================================== | |||
| enum class AutoScale | |||
| { | |||
| scaled, | |||
| unscaled, | |||
| useDefault | |||
| }; | |||
| constexpr bool autoScaleOptionAvailable = | |||
| #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
| true; | |||
| #else | |||
| false; | |||
| #endif | |||
| AutoScale getAutoScaleValueForPlugin (const String&); | |||
| void setAutoScaleValueForPlugin (const String&, AutoScale); | |||
| bool shouldAutoScalePlugin (const PluginDescription&); | |||
| void addPluginAutoScaleOptionsSubMenu (AudioPluginInstance*, PopupMenu&); | |||
| //============================================================================== | |||
| class MainHostWindow : public DocumentWindow, | |||
| public MenuBarModel, | |||
| @@ -88,12 +110,18 @@ public: | |||
| void addPluginsToMenu (PopupMenu&); | |||
| PluginDescription getChosenType (int menuID) const; | |||
| bool isDoublePrecisionProcessing(); | |||
| void updatePrecisionMenuItem (ApplicationCommandInfo& info); | |||
| std::unique_ptr<GraphDocumentComponent> graphHolder; | |||
| private: | |||
| //============================================================================== | |||
| static bool isDoublePrecisionProcessingEnabled(); | |||
| static bool isAutoScalePluginWindowsEnabled(); | |||
| static void updatePrecisionMenuItem (ApplicationCommandInfo& info); | |||
| static void updateAutoScaleMenuItem (ApplicationCommandInfo& info); | |||
| void showAudioSettings(); | |||
| //============================================================================== | |||
| AudioDeviceManager deviceManager; | |||
| AudioPluginFormatManager formatManager; | |||
| @@ -106,7 +134,5 @@ private: | |||
| class PluginListWindow; | |||
| std::unique_ptr<PluginListWindow> pluginListWindow; | |||
| void showAudioSettings(); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainHostWindow) | |||
| }; | |||
| @@ -495,7 +495,7 @@ int main (int argc, char** argv) | |||
| std::transform (argv, argv + argc, std::back_inserter (arguments), getString); | |||
| juce::ArgumentList argumentList { arguments.front(), | |||
| juce::StringArray (arguments.data() + 1, arguments.size() - 1) }; | |||
| juce::StringArray (arguments.data() + 1, (int) arguments.size() - 1) }; | |||
| using Fn = typename std::add_lvalue_reference<decltype (writeBinaryData)>::type; | |||
| @@ -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; | |||
| @@ -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()); | |||
| { | |||
| @@ -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) | |||
| @@ -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. | |||
| */ | |||
| @@ -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; | |||
| }; | |||
| //============================================================================== | |||
| @@ -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" | |||
| @@ -219,11 +219,11 @@ private: | |||
| FileChooser::Native* FileChooser::Native::currentFileChooser = nullptr; | |||
| FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags, | |||
| FilePreviewComponent*) | |||
| std::shared_ptr<FileChooser::Pimpl> FileChooser::showPlatformDialog (FileChooser& owner, int flags, | |||
| FilePreviewComponent*) | |||
| { | |||
| if (FileChooser::Native::currentFileChooser == nullptr) | |||
| return new FileChooser::Native (owner, flags); | |||
| return std::make_shared<FileChooser::Native> (owner, flags); | |||
| // there can only be one file chooser on Android at a once | |||
| jassertfalse; | |||
| @@ -379,10 +379,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*) | |||
| { | |||
| return new FileChooser::Native (owner, flags); | |||
| return std::make_shared<FileChooser::Native> (owner, flags); | |||
| } | |||
| #if JUCE_DEPRECATION_IGNORED | |||
| @@ -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 | |||
| @@ -0,0 +1,43 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2020 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||
| End User License Agreement: www.juce.com/juce-6-licence | |||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| class ScopedThreadDPIAwarenessSetter | |||
| { | |||
| public: | |||
| explicit ScopedThreadDPIAwarenessSetter (void* nativeWindow); | |||
| ~ScopedThreadDPIAwarenessSetter(); | |||
| private: | |||
| class NativeImpl; | |||
| std::unique_ptr<NativeImpl> pimpl; | |||
| JUCE_LEAK_DETECTOR (ScopedThreadDPIAwarenessSetter) | |||
| }; | |||
| } // 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); | |||
| } | |||
| @@ -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; | |||
| @@ -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" | |||
| @@ -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()) | |||
| { | |||