1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

FileChooser: Fix deprecation warnings for iOS 14

This commit is contained in:
reuk 2024-10-27 16:50:26 +00:00
parent a901b55b0b
commit a4ba0c1b1c
No known key found for this signature in database
GPG key ID: FCB43929F012EE5C
8 changed files with 136 additions and 38 deletions

View file

@ -21,6 +21,7 @@
2F76CA28C8C0EFC7453D0EB8 /* include_juce_data_structures.mm */ = {isa = PBXBuildFile; fileRef = F5F2EA2238973488632FC322; };
34A4931AF1DD424D3A400EEF /* CoreGraphics.framework */ = {isa = PBXBuildFile; fileRef = 76A157A111866670A4678F04; };
36E115D98311F12AA06710E6 /* DemoPIPs2.cpp */ = {isa = PBXBuildFile; fileRef = 061AECBF1CC7056F4155812D; };
41AE56F8671DFE80720A6A18 /* UniformTypeIdentifiers.framework */ = {isa = PBXBuildFile; fileRef = AAF88452B7774FB605990B31; settings = { ATTRIBUTES = (Weak, ); }; };
46071CE2B98B562B7BF27CB1 /* CoreMedia.framework */ = {isa = PBXBuildFile; fileRef = 1CFE3935A3B810D5D68A2504; };
47ED2C78B05B8A6A00E36C46 /* Assets */ = {isa = PBXBuildFile; fileRef = 685A261BE78585293F3EAD36; };
48CF0B02E1D06E5DA51E6270 /* Accelerate.framework */ = {isa = PBXBuildFile; fileRef = A04E4408525F24F7DCBA000E; };
@ -130,6 +131,7 @@
A5256778E2EBD206B337B555 /* juce_video */ /* juce_video */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_video; path = ../../../../modules/juce_video; sourceTree = SOURCE_ROOT; };
A6F555BE0DDF01C285BD8BF5 /* juce_dsp */ /* juce_dsp */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_dsp; path = ../../../../modules/juce_dsp; sourceTree = SOURCE_ROOT; };
A9315F8368A5771EC39631CB /* juce_gui_extra */ /* juce_gui_extra */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_gui_extra; path = ../../../../modules/juce_gui_extra; sourceTree = SOURCE_ROOT; };
AAF88452B7774FB605990B31 /* UniformTypeIdentifiers.framework */ /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
B28EFB9D1DF0B6D6499A7DEF /* CoreImage.framework */ /* CoreImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreImage.framework; path = System/Library/Frameworks/CoreImage.framework; sourceTree = SDKROOT; };
B2BC383CE102EECCF49C7AF7 /* IntroScreen.h */ /* IntroScreen.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = IntroScreen.h; path = ../../Source/Demos/IntroScreen.h; sourceTree = SOURCE_ROOT; };
B4389672DA4CC8E0A531062D /* CoreAudioKit.framework */ /* CoreAudioKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudioKit.framework; path = System/Library/Frameworks/CoreAudioKit.framework; sourceTree = SDKROOT; };
@ -180,6 +182,7 @@
89AD16514B1F4133FFEA1DF9,
EFD00925ED57B2C5EB5412FC,
10D769051F1431A67AD2CB40,
41AE56F8671DFE80720A6A18,
9641E7E4F0B5C2A1B3E8709A,
);
runOnlyForDeploymentPostprocessing = 0;
@ -210,6 +213,7 @@
96D99A08027CA35D6A4E5CFD,
3644EF58D9EB1AB436A04E77,
2992DB69DCFB7DADDE907385,
AAF88452B7774FB605990B31,
40D006CCDB1D33FF94B6ECAE,
);
name = Frameworks;

View file

@ -18,6 +18,7 @@
48ADBEF873A610909D727C97 /* include_juce_audio_formats.mm */ = {isa = PBXBuildFile; fileRef = 9E05B63699A307598B66F829; };
537E779F6008999191B2920A /* WebKit.framework */ = {isa = PBXBuildFile; fileRef = 3058871156B921B9E5946C4F; };
5482AA8D0FC9214839FD96A4 /* include_juce_graphics_Sheenbidi.c */ = {isa = PBXBuildFile; fileRef = A6DEFD86172F7F8BA64A77CC; };
56BEF006A35699533F758612 /* UniformTypeIdentifiers.framework */ = {isa = PBXBuildFile; fileRef = 7ED3F6975B2EBF16EEF12B3B; settings = { ATTRIBUTES = (Weak, ); }; };
5923A711C0020F2CDD598714 /* CoreMIDI.framework */ = {isa = PBXBuildFile; fileRef = 12C680C68A15B9A590264B18; };
5AFD011031C266431687C922 /* CoreAudio.framework */ = {isa = PBXBuildFile; fileRef = 9F28F179EF6B90EB9F4DBEE9; };
65FC2E13B65977FED63BDDE3 /* include_juce_graphics.mm */ = {isa = PBXBuildFile; fileRef = 7E951216B6138C76653B1460; };
@ -71,6 +72,7 @@
732FE1B1B951AB410C8153BD /* Metal.framework */ /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; };
77AA9722BAADD4108205501A /* juce_data_structures */ /* juce_data_structures */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_data_structures; path = ../../../../modules/juce_data_structures; sourceTree = SOURCE_ROOT; };
7E951216B6138C76653B1460 /* include_juce_graphics.mm */ /* include_juce_graphics.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_graphics.mm; path = ../../JuceLibraryCode/include_juce_graphics.mm; sourceTree = SOURCE_ROOT; };
7ED3F6975B2EBF16EEF12B3B /* UniformTypeIdentifiers.framework */ /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
81017699F857F5BBFCA6E055 /* juce_events */ /* juce_events */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_events; path = ../../../../modules/juce_events; sourceTree = SOURCE_ROOT; };
8693552B5FA53C2003A66302 /* Images.xcassets */ /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = AudioPerformanceTest/Images.xcassets; sourceTree = SOURCE_ROOT; };
89B3243200BAA6BD72905DBB /* include_juce_audio_basics.mm */ /* include_juce_audio_basics.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_audio_basics.mm; path = ../../JuceLibraryCode/include_juce_audio_basics.mm; sourceTree = SOURCE_ROOT; };
@ -121,6 +123,7 @@
537E779F6008999191B2920A,
A545463A201EC7C2F79A330A,
0CB3EC8D832F5781D3BD7827,
56BEF006A35699533F758612,
E4CFFE22717FF2B0A12BAB32,
);
runOnlyForDeploymentPostprocessing = 0;
@ -147,6 +150,7 @@
3058871156B921B9E5946C4F,
732FE1B1B951AB410C8153BD,
19CEFCBBBEBC6F0DAE2376BB,
7ED3F6975B2EBF16EEF12B3B,
F454EA288AEBA354CF288214,
);
name = Frameworks;

View file

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
0240E0ECC2771B8B854C49C2 /* UniformTypeIdentifiers.framework */ = {isa = PBXBuildFile; fileRef = 8C6CD9119127C4AEBADABA25; settings = { ATTRIBUTES = (Weak, ); }; };
025B22813EA4E34CE3630B9A /* IOConfigurationWindow.cpp */ = {isa = PBXBuildFile; fileRef = C37B2E77AAB6C9E13729BF99; };
075C54DDDBDEA5AAD2F60154 /* include_juce_graphics.mm */ = {isa = PBXBuildFile; fileRef = 82800DBA287EF4BAB13B42FB; };
08E08AEF1DFE00F06F5ED160 /* include_juce_core_CompilationTime.cpp */ = {isa = PBXBuildFile; fileRef = DB6C39B4C628E31A58A28675; };
@ -104,6 +105,7 @@
86CA337014D3F67E906FFD28 /* Accelerate.framework */ /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
87A7AAB053051C49EAF4EE88 /* InternalPlugins.cpp */ /* InternalPlugins.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = InternalPlugins.cpp; path = ../../Source/Plugins/InternalPlugins.cpp; sourceTree = SOURCE_ROOT; };
89309C0C5F3269BD06BE7F27 /* QuartzCore.framework */ /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
8C6CD9119127C4AEBADABA25 /* UniformTypeIdentifiers.framework */ /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
8D8BBC353637DA442C5575DA /* App */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Plugin Host.app"; sourceTree = BUILT_PRODUCTS_DIR; };
8FE7B37CDE0818DB27BDDEBD /* include_juce_gui_basics.mm */ /* include_juce_gui_basics.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_gui_basics.mm; path = ../../JuceLibraryCode/include_juce_gui_basics.mm; sourceTree = SOURCE_ROOT; };
9320A145F2A8ACD687D6608E /* juce_dsp */ /* juce_dsp */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_dsp; path = ../../../../modules/juce_dsp; sourceTree = SOURCE_ROOT; };
@ -166,6 +168,7 @@
4DB15177DDC357F4503F88CF,
1AD3A3C7CD2D1F6DC4B65205,
8390CF6AEF2090680E4535F7,
0240E0ECC2771B8B854C49C2,
A6A7B686501E826EB999A03C,
);
runOnlyForDeploymentPostprocessing = 0;
@ -316,6 +319,7 @@
B457EE687507BF1DEEA7581F,
3E94492697BD64D0F185D60E,
118ABD8E91DF2E400358D8CD,
8C6CD9119127C4AEBADABA25,
44D9D7F12D565AC038E17E2F,
);
name = Frameworks;

View file

@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
006DF460F8DF66EFFA80D968 /* Icon.icns */ = {isa = PBXBuildFile; fileRef = 70F1CAF3C4C561DD81E6AFC1; };
0977FEC02DAF29438583198A /* include_juce_core.mm */ = {isa = PBXBuildFile; fileRef = 01E0EEF68A11C1CAF180E173; };
0E041BED84BAC24200949A78 /* UniformTypeIdentifiers.framework */ = {isa = PBXBuildFile; fileRef = 961965555B4DAA5BE2361933; settings = { ATTRIBUTES = (Weak, ); }; };
0E39AB2B15DCE39E1055A646 /* include_juce_audio_processors_lv2_libs.cpp */ = {isa = PBXBuildFile; fileRef = AB2DE62887E2F58D821F3217; };
0FA2A3321630EBE83E439D99 /* include_juce_cryptography.mm */ = {isa = PBXBuildFile; fileRef = AFF729977947528F3E4AAA96; };
1282A62308CD1AC3F88A5D03 /* Images.xcassets */ = {isa = PBXBuildFile; fileRef = 5273768FBB55D0DD57A5E70C; };
@ -85,6 +86,7 @@
9193D2A3C463BEAA07FD424D /* CoreText.framework */ /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; };
92800676AF753D1A60108F11 /* BinaryData.h */ /* BinaryData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BinaryData.h; path = ../../JuceLibraryCode/BinaryData.h; sourceTree = SOURCE_ROOT; };
935CA85EF98714D3A17AE737 /* QuartzCore.framework */ /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
961965555B4DAA5BE2361933 /* UniformTypeIdentifiers.framework */ /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
9982F39121710EFFD5FEEAEF /* MasterComponent.h */ /* MasterComponent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MasterComponent.h; path = ../../Source/MasterComponent.h; sourceTree = SOURCE_ROOT; };
9C67BD1915C7FD5747C2BA8F /* juce_audio_formats */ /* juce_audio_formats */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_audio_formats; path = ../../../../modules/juce_audio_formats; sourceTree = SOURCE_ROOT; };
9C689AFBF364CB167C422D29 /* juce_gui_extra */ /* juce_gui_extra */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_gui_extra; path = ../../../../modules/juce_gui_extra; sourceTree = SOURCE_ROOT; };
@ -142,6 +144,7 @@
8ECB0767EE340DD83869E37D,
AC37CDB417E2D3FE619E5944,
64DEB67F9523F28D899D1821,
0E041BED84BAC24200949A78,
78CB229C1BA5093078BC6195,
);
runOnlyForDeploymentPostprocessing = 0;
@ -192,6 +195,7 @@
EC794872987FEA2E129C589A,
B57F11A8AC0C8D3A8BF3BDCF,
C8C4E9A4028028FF1F5B76F2,
961965555B4DAA5BE2361933,
E8976208A3585295BF93D50D,
);
name = Frameworks;

View file

@ -564,4 +564,36 @@ private:
Class klass = nullptr;
};
#if JUCE_IOS
// Defines a function that will check the requested version both at
// build time, and, if necessary, at runtime.
// The function's first template argument is a trait type containing
// two static member functions named newFn and oldFn.
// When the deployment target is at least equal to major.minor,
// newFn will be selected at compile time.
// When the build sdk does not support iOS SDK major.minor,
// oldFn will be selected at compile time.
// Otherwise, the OS version will be checked at runtime and newFn
// will be called if the OS version is at least equal to major.minor,
// otherwise oldFn will be called.
#define JUCE_DEFINE_IOS_VERSION_CHECKER_FOR_VERSION(major, minor) \
template <typename Trait, typename... Args> \
auto ifelse_ ## major ## _ ## minor (Args&&... args) \
{ \
constexpr auto fullVersion = major * 10'000 + minor * 100; \
if constexpr (fullVersion <= __IPHONE_OS_VERSION_MIN_REQUIRED) \
return Trait::newFn (std::forward<Args> (args)...); \
else if constexpr (__IPHONE_OS_VERSION_MAX_ALLOWED < fullVersion) \
return Trait::oldFn (std::forward<Args> (args)...); \
else if (@available (ios major ## . ## minor, *)) \
return Trait::newFn (std::forward<Args> (args)...); \
else \
return Trait::oldFn (std::forward<Args> (args)...); \
}
JUCE_DEFINE_IOS_VERSION_CHECKER_FOR_VERSION (14, 0)
#endif
} // namespace juce

View file

@ -79,6 +79,7 @@
#import <UserNotifications/UserNotifications.h>
#endif
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#import <MetalKit/MetalKit.h>
#import <UIKit/UIActivityViewController.h>

View file

@ -55,7 +55,7 @@
OSXFrameworks: Cocoa QuartzCore
WeakOSXFrameworks: Metal MetalKit
iOSFrameworks: CoreServices UIKit
WeakiOSFrameworks: Metal MetalKit
WeakiOSFrameworks: Metal MetalKit UniformTypeIdentifiers
END_JUCE_MODULE_DECLARATION

View file

@ -92,8 +92,7 @@ public:
//==============================================================================
void didPickDocumentsAtURLs (NSArray<NSURL*>* urls)
{
const auto isWriting = controller.get().documentPickerMode == UIDocumentPickerModeExportToService
|| controller.get().documentPickerMode == UIDocumentPickerModeMoveToService;
const auto isWriting = (savedFlags & FileBrowserComponent::saveMode) != 0;
const auto accessOptions = isWriting ? 0 : NSFileCoordinatorReadingWithoutChanges;
auto* fileCoordinator = [[[NSFileCoordinator alloc] initWithFilePresenter: nil] autorelease];
@ -165,24 +164,97 @@ public:
private:
UIViewController* getViewController() const override { return controller.get(); }
struct CreateSaveControllerTrait
{
API_AVAILABLE (ios (14))
static FileChooserControllerClass* newFn (NSURL* url, const File& currentFileOrDirectory)
{
return [[FileChooserControllerClass alloc] initForExportingURLs: @[url] asCopy: currentFileOrDirectory.existsAsFile()];
}
static FileChooserControllerClass* oldFn (NSURL* url, const File& currentFileOrDirectory)
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
const auto pickerMode = currentFileOrDirectory.existsAsFile()
? UIDocumentPickerModeExportToService
: UIDocumentPickerModeMoveToService;
return [[FileChooserControllerClass alloc] initWithURL: url inMode: pickerMode];
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
};
struct CreateOpenControllerTrait
{
API_AVAILABLE (ios (14))
static FileChooserControllerClass* newFn (int flags, const StringArray& validExtensions)
{
NSUniquePtr<NSMutableArray> types ([[NSMutableArray alloc] init]);
if ((flags & FileBrowserComponent::canSelectDirectories) != 0)
{
if (NSUniquePtr<UTType> ptr {[UTType typeWithIdentifier: @"public.folder"]})
[types.get() addObject: ptr.get()];
}
else
{
if (validExtensions.isEmpty())
if (NSUniquePtr<UTType> ptr {[UTType typeWithIdentifier: @"public.data"]})
[types.get() addObject: ptr.get()];
for (const auto& extension : validExtensions)
if (NSUniquePtr<UTType> ptr {[UTType typeWithFilenameExtension: juceStringToNS (extension)]})
[types.get() addObject: ptr.get()];
}
return [[FileChooserControllerClass alloc] initForOpeningContentTypes: types.get()];
}
static FileChooserControllerClass* oldFn (int flags, const StringArray& validExtensions)
{
const NSUniquePtr<NSArray> utTypeArray { std::invoke ([&]
{
if ((flags & FileBrowserComponent::canSelectDirectories) != 0)
return @[@"public.folder"];
if (validExtensions.isEmpty())
return @[@"public.data"];
StringArray result;
for (const auto& extension : validExtensions)
{
if (extension.isEmpty())
continue;
CFUniquePtr<CFStringRef> fileExtensionCF (extension.toCFString());
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
if (CFUniquePtr<CFStringRef> tag { UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, fileExtensionCF.get(), nullptr) })
result.add (String::fromCFString (tag.get()));
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
return createNSArrayFromStringArray (result);
}) };
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
return [[FileChooserControllerClass alloc] initWithDocumentTypes: utTypeArray.get() inMode: UIDocumentPickerModeOpen];
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
};
Native (FileChooser& fileChooser, int flags)
: owner (fileChooser)
: owner (fileChooser),
savedFlags (flags)
{
delegate.reset ([[FileChooserDelegateClass alloc] initWithOwner: this]);
const auto validExtensions = getValidExtensionsForWildcards (owner.filters);
const auto utTypeArray = (flags & FileBrowserComponent::canSelectDirectories) != 0
? @[@"public.folder"]
: createNSArrayFromStringArray (getUTTypesForExtensions (validExtensions));
if ((flags & FileBrowserComponent::saveMode) != 0)
{
auto currentFileOrDirectory = owner.startingFile;
UIDocumentPickerMode pickerMode = currentFileOrDirectory.existsAsFile()
? UIDocumentPickerModeExportToService
: UIDocumentPickerModeMoveToService;
if (! currentFileOrDirectory.existsAsFile())
{
const auto extension = validExtensions.isEmpty() ? String()
@ -204,15 +276,12 @@ private:
}
}
auto url = [[NSURL alloc] initFileURLWithPath: juceStringToNS (currentFileOrDirectory.getFullPathName())];
controller.reset ([[FileChooserControllerClass alloc] initWithURL: url inMode: pickerMode]);
[url release];
const NSUniquePtr<NSURL> url { [[NSURL alloc] initFileURLWithPath: juceStringToNS (currentFileOrDirectory.getFullPathName())] };
controller.reset (ifelse_14_0<CreateSaveControllerTrait> (url.get(), currentFileOrDirectory));
}
else
{
controller.reset ([[FileChooserControllerClass alloc] initWithDocumentTypes: utTypeArray inMode: UIDocumentPickerModeOpen]);
controller.reset (ifelse_14_0<CreateOpenControllerTrait> (flags, validExtensions));
[controller.get() setAllowsMultipleSelection: (flags & FileBrowserComponent::canSelectMultipleItems) != 0];
}
@ -267,27 +336,6 @@ private:
return result;
}
static StringArray getUTTypesForExtensions (const StringArray& extensions)
{
if (extensions.isEmpty())
return { "public.data" };
StringArray result;
for (const auto& extension : extensions)
{
if (extension.isEmpty())
continue;
CFUniquePtr<CFStringRef> fileExtensionCF (extension.toCFString());
if (const auto tag = CFUniquePtr<CFStringRef> (UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, fileExtensionCF.get(), nullptr)))
result.add (String::fromCFString (tag.get()));
}
return result;
}
static String getFilename (const File& path, const String& fallbackExtension)
{
auto filename = path.getFileNameWithoutExtension();
@ -309,6 +357,7 @@ private:
FileChooser& owner;
NSUniquePtr<NSObject<UIDocumentPickerDelegate, UIAdaptivePresentationControllerDelegate>> delegate;
NSUniquePtr<FileChooserControllerClass> controller;
int savedFlags = 0;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native)