Browse Source

Add release notes text field to Introjucer's auto-updater dialog. Minor auto-updater infrastructure updates and code refactoring.

tags/2021-05-28
hogliux 9 years ago
parent
commit
0e7729590f
16 changed files with 926 additions and 396 deletions
  1. +5
    -0
      extras/Introjucer/Builds/CodeBlocks/The Introjucer.cbp
  2. +8
    -2
      extras/Introjucer/Builds/Linux/Makefile
  3. +33
    -27
      extras/Introjucer/Builds/MacOSX/The Introjucer.xcodeproj/project.pbxproj
  4. +11
    -0
      extras/Introjucer/Builds/VisualStudio2005/The Introjucer.vcproj
  5. +11
    -0
      extras/Introjucer/Builds/VisualStudio2008/The Introjucer.vcproj
  6. +4
    -0
      extras/Introjucer/Builds/VisualStudio2010/The Introjucer.vcxproj
  7. +6
    -0
      extras/Introjucer/Builds/VisualStudio2010/The Introjucer.vcxproj.filters
  8. +4
    -0
      extras/Introjucer/Builds/VisualStudio2012/The Introjucer.vcxproj
  9. +6
    -0
      extras/Introjucer/Builds/VisualStudio2012/The Introjucer.vcxproj.filters
  10. +4
    -0
      extras/Introjucer/Builds/VisualStudio2013/The Introjucer.vcxproj
  11. +6
    -0
      extras/Introjucer/Builds/VisualStudio2013/The Introjucer.vcxproj.filters
  12. +3
    -1
      extras/Introjucer/Introjucer.jucer
  13. +1
    -1
      extras/Introjucer/JuceLibraryCode/AppConfig.h
  14. +789
    -0
      extras/Introjucer/Source/Application/jucer_AutoUpdater.cpp
  15. +33
    -365
      extras/Introjucer/Source/Application/jucer_AutoUpdater.h
  16. +2
    -0
      modules/juce_core/system/juce_StandardHeader.h

+ 5
- 0
extras/Introjucer/Builds/CodeBlocks/The Introjucer.cbp View File

@@ -97,6 +97,7 @@
<Option compile="0"/>
<Option link="0"/>
</Unit>
<Unit filename="../../Source/Application/jucer_AutoUpdater.cpp"/>
<Unit filename="../../Source/Application/jucer_Application.h">
<Option compile="0"/>
<Option link="0"/>
@@ -1498,6 +1499,10 @@
<Option compile="0"/>
<Option link="0"/>
</Unit>
<Unit filename="../../../../modules/juce_core/native/juce_curl_Network.cpp">
<Option compile="0"/>
<Option link="0"/>
</Unit>
<Unit filename="../../../../modules/juce_core/native/juce_linux_CommonFile.cpp">
<Option compile="0"/>
<Option link="0"/>


+ 8
- 2
extras/Introjucer/Builds/Linux/Makefile View File

@@ -21,7 +21,7 @@ ifeq ($(CONFIG),Debug)
CPPFLAGS := $(DEPFLAGS) -D "LINUX=1" -D "DEBUG=1" -D "_DEBUG=1" -D "JUCER_LINUX_MAKE_7346DA2A=1" -D "JUCE_APP_VERSION=3.1.1" -D "JUCE_APP_VERSION_HEX=0x30101" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../../modules
CFLAGS += $(CPPFLAGS) $(TARGET_ARCH) -g -ggdb -O0 -std=gnu++0x -Wreorder -Wuninitialized -Wunused-parameter -Wstrict-aliasing -Wshadow -Wsign-compare
CXXFLAGS += $(CFLAGS) -std=c++11
LDFLAGS += $(TARGET_ARCH) -L$(BINDIR) -L$(LIBDIR) -L/usr/X11R6/lib/ -lX11 -lXext -lXinerama -ldl -lfreetype -lpthread -lrt
LDFLAGS += $(TARGET_ARCH) -L$(BINDIR) -L$(LIBDIR) -L/usr/X11R6/lib/ -lX11 -lXext -lXinerama -ldl -lfreetype -lpthread -lrt -lcurl
TARGET := Introjucer
BLDCMD = $(CXX) -o $(OUTDIR)/$(TARGET) $(OBJECTS) $(LDFLAGS) $(RESOURCES) $(TARGET_ARCH)
@@ -41,7 +41,7 @@ ifeq ($(CONFIG),Release)
CPPFLAGS := $(DEPFLAGS) -D "LINUX=1" -D "NDEBUG=1" -D "JUCER_LINUX_MAKE_7346DA2A=1" -D "JUCE_APP_VERSION=3.1.1" -D "JUCE_APP_VERSION_HEX=0x30101" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../../modules
CFLAGS += $(CPPFLAGS) $(TARGET_ARCH) -O3 -std=gnu++0x -Wreorder -Wuninitialized -Wunused-parameter -Wstrict-aliasing -Wshadow -Wsign-compare
CXXFLAGS += $(CFLAGS) -std=c++11
LDFLAGS += $(TARGET_ARCH) -L$(BINDIR) -L$(LIBDIR) -fvisibility=hidden -L/usr/X11R6/lib/ -lX11 -lXext -lXinerama -ldl -lfreetype -lpthread -lrt
LDFLAGS += $(TARGET_ARCH) -L$(BINDIR) -L$(LIBDIR) -fvisibility=hidden -L/usr/X11R6/lib/ -lX11 -lXext -lXinerama -ldl -lfreetype -lpthread -lrt -lcurl
TARGET := Introjucer
BLDCMD = $(CXX) -o $(OUTDIR)/$(TARGET) $(OBJECTS) $(LDFLAGS) $(RESOURCES) $(TARGET_ARCH)
@@ -50,6 +50,7 @@ endif
OBJECTS := \
$(OBJDIR)/jucer_AppearanceSettings_788d9889.o \
$(OBJDIR)/jucer_AutoUpdater_ca658dc2.o \
$(OBJDIR)/jucer_CommandLine_f35de107.o \
$(OBJDIR)/jucer_DocumentEditorComponent_695dff1d.o \
$(OBJDIR)/jucer_Main_f8488f5b.o \
@@ -122,6 +123,11 @@ $(OBJDIR)/jucer_AppearanceSettings_788d9889.o: ../../Source/Application/jucer_Ap
@echo "Compiling jucer_AppearanceSettings.cpp"
@$(CXX) $(CXXFLAGS) -o "$@" -c "$<"
$(OBJDIR)/jucer_AutoUpdater_ca658dc2.o: ../../Source/Application/jucer_AutoUpdater.cpp
-@mkdir -p $(OBJDIR)
@echo "Compiling jucer_AutoUpdater.cpp"
@$(CXX) $(CXXFLAGS) -o "$@" -c "$<"
$(OBJDIR)/jucer_CommandLine_f35de107.o: ../../Source/Application/jucer_CommandLine.cpp
-@mkdir -p $(OBJDIR)
@echo "Compiling jucer_CommandLine.cpp"


+ 33
- 27
extras/Introjucer/Builds/MacOSX/The Introjucer.xcodeproj/project.pbxproj View File

@@ -14,6 +14,7 @@
2610F357881240ACBF612F48 = {isa = PBXBuildFile; fileRef = 6678E9B3EEACAD47F438B264; };
1321E6C1C6170B6C898AD09D = {isa = PBXBuildFile; fileRef = 951128CA33CCDEF570436B1C; };
357A6AA6960EF95D92929BEE = {isa = PBXBuildFile; fileRef = 441CFEA771BAA50E187342E9; };
6DD9DA1677A6CF789CDAB478 = {isa = PBXBuildFile; fileRef = 0D4D508C638BC74943B9976D; };
954A036F5DDB375DB23FFB3E = {isa = PBXBuildFile; fileRef = 0400CB0E056A1D840304D2DE; };
3EB3D569250C4BA4CA9AF578 = {isa = PBXBuildFile; fileRef = C7608A3967D9AB9481848F2B; };
95B44E6C74B1DED31DBE37EB = {isa = PBXBuildFile; fileRef = 8C52A3DDA62A746AA7A68535; };
@@ -109,6 +110,7 @@
0CEF14D37EC664CA49A2B04D = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ComponentPeer.cpp"; path = "../../../../modules/juce_gui_basics/windows/juce_ComponentPeer.cpp"; sourceTree = "SOURCE_ROOT"; };
0D100A1B27A72355323CB637 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_win32_Windowing.cpp"; path = "../../../../modules/juce_gui_basics/native/juce_win32_Windowing.cpp"; sourceTree = "SOURCE_ROOT"; };
0D1C432D74433308E05942AD = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_TextDiff.cpp"; path = "../../../../modules/juce_core/text/juce_TextDiff.cpp"; sourceTree = "SOURCE_ROOT"; };
0D4D508C638BC74943B9976D = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "jucer_AutoUpdater.cpp"; path = "../../Source/Application/jucer_AutoUpdater.cpp"; sourceTree = "SOURCE_ROOT"; };
0DB0A9E30EEDDEA720BC5A03 = {isa = PBXFileReference; lastKnownFileType = file.svg; name = "wizard_StaticLibrary.svg"; path = "../../Source/BinaryData/wizard_StaticLibrary.svg"; sourceTree = "SOURCE_ROOT"; };
0E80EFDB550393DB1C94F291 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Font.h"; path = "../../../../modules/juce_graphics/fonts/juce_Font.h"; sourceTree = "SOURCE_ROOT"; };
0F01067432AC314EAC213C1C = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_ProjectWizard_Blank.h"; path = "../../Source/Wizards/jucer_ProjectWizard_Blank.h"; sourceTree = "SOURCE_ROOT"; };
@@ -736,6 +738,7 @@
C2990A8D054BC230E7C637C3 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_NewProjectWizardClasses.h"; path = "../../Source/Wizards/jucer_NewProjectWizardClasses.h"; sourceTree = "SOURCE_ROOT"; };
C2D43E4F571D16F0E9B1E44E = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_FileTreeComponent.h"; path = "../../../../modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.h"; sourceTree = "SOURCE_ROOT"; };
C2ECD077AC4C784A67BC3DDE = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Socket.h"; path = "../../../../modules/juce_core/network/juce_Socket.h"; sourceTree = "SOURCE_ROOT"; };
C3447B6A7C639350E98E43AF = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_curl_Network.cpp"; path = "../../../../modules/juce_core/native/juce_curl_Network.cpp"; sourceTree = "SOURCE_ROOT"; };
C38DFB0C94BBC1CE46A3D4E4 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_TooltipWindow.h"; path = "../../../../modules/juce_gui_basics/windows/juce_TooltipWindow.h"; sourceTree = "SOURCE_ROOT"; };
C4718AEAC94D07FEB9DA0312 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_android_Misc.cpp"; path = "../../../../modules/juce_core/native/juce_android_Misc.cpp"; sourceTree = "SOURCE_ROOT"; };
C48DCD3879352EB40F468B0F = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_DirectoryContentsList.h"; path = "../../../../modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.h"; sourceTree = "SOURCE_ROOT"; };
@@ -746,7 +749,6 @@
C5D5D2FFB030BD43160167EB = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_DirectoryIterator.cpp"; path = "../../../../modules/juce_core/files/juce_DirectoryIterator.cpp"; sourceTree = "SOURCE_ROOT"; };
C5FCECA838D4D12D03929139 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_FileBrowserComponent.cpp"; path = "../../../../modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp"; sourceTree = "SOURCE_ROOT"; };
C61E82D93918A611FAFD9E9F = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_ComponentBoundsConstrainer.h"; path = "../../../../modules/juce_gui_basics/layout/juce_ComponentBoundsConstrainer.h"; sourceTree = "SOURCE_ROOT"; };
C68355AA4D62939EECAD469E = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Random.h"; path = "../../../../modules/juce_core/maths/juce_Random.h"; sourceTree = "SOURCE_ROOT"; };
C6861DB44A31CE7A3DEFA083 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_FileInputStream.h"; path = "../../../../modules/juce_core/files/juce_FileInputStream.h"; sourceTree = "SOURCE_ROOT"; };
C6E46DBD249D9426C95E2235 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ChangeBroadcaster.cpp"; path = "../../../../modules/juce_events/broadcasters/juce_ChangeBroadcaster.cpp"; sourceTree = "SOURCE_ROOT"; };
C717D2384896547D1CD993F6 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_DialogWindow.h"; path = "../../../../modules/juce_gui_basics/windows/juce_DialogWindow.h"; sourceTree = "SOURCE_ROOT"; };
@@ -755,7 +757,6 @@
C7B47372A9D5970E3D9A5400 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_GroupInformationComponent.h"; path = "../../Source/Project/jucer_GroupInformationComponent.h"; sourceTree = "SOURCE_ROOT"; };
C7E19F79947BEFC7DB7A9CE7 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_GlowEffect.cpp"; path = "../../../../modules/juce_graphics/effects/juce_GlowEffect.cpp"; sourceTree = "SOURCE_ROOT"; };
C83992DA0BE30EA3CD06EA98 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_ProgressBar.h"; path = "../../../../modules/juce_gui_basics/widgets/juce_ProgressBar.h"; sourceTree = "SOURCE_ROOT"; };
C85B4D62B96F4A44890F20E2 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_SortedSet.h"; path = "../../../../modules/juce_core/containers/juce_SortedSet.h"; sourceTree = "SOURCE_ROOT"; };
C8A229ACD244F402C57286CD = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_ProjectExport_MSVC.h"; path = "../../Source/Project Saving/jucer_ProjectExport_MSVC.h"; sourceTree = "SOURCE_ROOT"; };
C90C66C5727759D5CBD5FB07 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_AbstractFifo.cpp"; path = "../../../../modules/juce_core/containers/juce_AbstractFifo.cpp"; sourceTree = "SOURCE_ROOT"; };
C9616830BB2474066AC8C910 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_ResourceFile.h"; path = "../../Source/Project Saving/jucer_ResourceFile.h"; sourceTree = "SOURCE_ROOT"; };
@@ -770,16 +771,16 @@
CD140A1C0161176682F6CA29 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ResizableWindow.cpp"; path = "../../../../modules/juce_gui_basics/windows/juce_ResizableWindow.cpp"; sourceTree = "SOURCE_ROOT"; };
CD3216F23C7B273B010A8D12 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_CustomTypeface.h"; path = "../../../../modules/juce_graphics/fonts/juce_CustomTypeface.h"; sourceTree = "SOURCE_ROOT"; };
CDCAF0EC777DA2884AEB2B59 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_Label.cpp"; path = "../../../../modules/juce_gui_basics/widgets/juce_Label.cpp"; sourceTree = "SOURCE_ROOT"; };
CDDF5BDC75277F7B83A38885 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_PropertySet.cpp"; path = "../../../../modules/juce_core/containers/juce_PropertySet.cpp"; sourceTree = "SOURCE_ROOT"; };
CDF8F65F9079B2C14A740F0F = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_ImageButton.h"; path = "../../../../modules/juce_gui_basics/buttons/juce_ImageButton.h"; sourceTree = "SOURCE_ROOT"; };
CE1DFE4E3908943656E180AD = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_UnitTest.cpp"; path = "../../../../modules/juce_core/unit_tests/juce_UnitTest.cpp"; sourceTree = "SOURCE_ROOT"; };
CF0615A1AF1A514A60322B50 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_MouseCursor.h"; path = "../../../../modules/juce_gui_basics/mouse/juce_MouseCursor.h"; sourceTree = "SOURCE_ROOT"; };
CF21D9DB3AEC0A4DCAB36A99 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "jucer_Icons.cpp"; path = "../../Source/Utility/jucer_Icons.cpp"; sourceTree = "SOURCE_ROOT"; };
CF6C8BD0DA3D8CD4E99EBADA = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
CF8011B3C67B609032974DA5 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "jucer_NewCppFileTemplate.cpp"; path = "../../Source/BinaryData/jucer_NewCppFileTemplate.cpp"; sourceTree = "SOURCE_ROOT"; };
D00F311BFC3C2625C457CB9B = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
D041043D3B554B88F855C174 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_HighResolutionTimer.h"; path = "../../../../modules/juce_core/threads/juce_HighResolutionTimer.h"; sourceTree = "SOURCE_ROOT"; };
D0D8B580D0689FFF4B9B823B = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_StrokeType.h"; path = "../../Source/ComponentEditor/paintelements/jucer_StrokeType.h"; sourceTree = "SOURCE_ROOT"; };
D0F1614CC861E8E0B59B7A06 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_linux_Messaging.cpp"; path = "../../../../modules/juce_events/native/juce_linux_Messaging.cpp"; sourceTree = "SOURCE_ROOT"; };
D10D51A0A2D63F38B4D86A60 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "jucer_ResourceFile.cpp"; path = "../../Source/Project Saving/jucer_ResourceFile.cpp"; sourceTree = "SOURCE_ROOT"; };
D141433D3FE81F20490DE928 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Desktop.h"; path = "../../../../modules/juce_gui_basics/components/juce_Desktop.h"; sourceTree = "SOURCE_ROOT"; };
D1F9B0E9F5D54FE48BEB46EA = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
D253F74B7F5734984E568CA7 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_linux_Files.cpp"; path = "../../../../modules/juce_core/native/juce_linux_Files.cpp"; sourceTree = "SOURCE_ROOT"; };
@@ -791,66 +792,64 @@
D4697A0232AECE5DAC5E332E = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ChoicePropertyComponent.cpp"; path = "../../../../modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.cpp"; sourceTree = "SOURCE_ROOT"; };
D4E56676E2EF83404EDCBA8C = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_TextEditor.cpp"; path = "../../../../modules/juce_gui_basics/widgets/juce_TextEditor.cpp"; sourceTree = "SOURCE_ROOT"; };
D4F2D42C58F4D86E00E76F31 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_ApplicationCommandID.h"; path = "../../../../modules/juce_gui_basics/commands/juce_ApplicationCommandID.h"; sourceTree = "SOURCE_ROOT"; };
D503780AB98993E8F0BA1311 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ImageComponent.cpp"; path = "../../../../modules/juce_gui_basics/widgets/juce_ImageComponent.cpp"; sourceTree = "SOURCE_ROOT"; };
D5057D7B18ABD5E810A6F830 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_win32_DirectWriteTypeface.cpp"; path = "../../../../modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp"; sourceTree = "SOURCE_ROOT"; };
D526C38D581425949BA0E4AC = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_FilePreviewComponent.h"; path = "../../Source/Application/jucer_FilePreviewComponent.h"; sourceTree = "SOURCE_ROOT"; };
D53B54D1786A1FFC024BF064 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "juce_cryptography.mm"; path = "../../../../modules/juce_cryptography/juce_cryptography.mm"; sourceTree = "SOURCE_ROOT"; };
D678882D133090214AF681BC = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_ThreadLocalValue.h"; path = "../../../../modules/juce_core/threads/juce_ThreadLocalValue.h"; sourceTree = "SOURCE_ROOT"; };
D6C91E2BF537F75A80F5C1DB = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_ListBox.h"; path = "../../../../modules/juce_gui_basics/widgets/juce_ListBox.h"; sourceTree = "SOURCE_ROOT"; };
D75EAC16FAECCC51E3669193 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_ApplicationCommandInfo.h"; path = "../../../../modules/juce_gui_basics/commands/juce_ApplicationCommandInfo.h"; sourceTree = "SOURCE_ROOT"; };
D780ED33573AED5AD383A036 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_CaretComponent.h"; path = "../../../../modules/juce_gui_basics/keyboard/juce_CaretComponent.h"; sourceTree = "SOURCE_ROOT"; };
D782DA091AD3ECE158FC6A5F = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_ButtonPropertyComponent.h"; path = "../../../../modules/juce_gui_basics/properties/juce_ButtonPropertyComponent.h"; sourceTree = "SOURCE_ROOT"; };
D800DE818BEDBF4579D15B1D = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "juce_mac_AppleRemote.mm"; path = "../../../../modules/juce_gui_extra/native/juce_mac_AppleRemote.mm"; sourceTree = "SOURCE_ROOT"; };
D87FC8F6834E9DC9C8E88B94 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_JustificationProperty.h"; path = "../../Source/ComponentEditor/properties/jucer_JustificationProperty.h"; sourceTree = "SOURCE_ROOT"; };
D926E13AB5AD647A7A00F486 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_linux_Network.cpp"; path = "../../../../modules/juce_core/native/juce_linux_Network.cpp"; sourceTree = "SOURCE_ROOT"; };
D92A6E9404A30EED32DCE4ED = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_RelativePositionedRectangle.h"; path = "../../Source/ComponentEditor/ui/jucer_RelativePositionedRectangle.h"; sourceTree = "SOURCE_ROOT"; };
DA345D5B9DABD049F90DC96F = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_GeneratedCode.h"; path = "../../Source/ComponentEditor/jucer_GeneratedCode.h"; sourceTree = "SOURCE_ROOT"; };
DBE0CDE1B017190ABBFF557C = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_ProjectExport_CodeBlocks.h"; path = "../../Source/Project Saving/jucer_ProjectExport_CodeBlocks.h"; sourceTree = "SOURCE_ROOT"; };
DC5E7FF30B01118F6DAEC38F = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_Time.cpp"; path = "../../../../modules/juce_core/time/juce_Time.cpp"; sourceTree = "SOURCE_ROOT"; };
D9342535EA61901A1AD816C6 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_linux_WebBrowserComponent.cpp"; path = "../../../../modules/juce_gui_extra/native/juce_linux_WebBrowserComponent.cpp"; sourceTree = "SOURCE_ROOT"; };
DC922C6A65D260C18E888E49 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "jucer_ComponentTemplate.cpp"; path = "../../Source/BinaryData/jucer_ComponentTemplate.cpp"; sourceTree = "SOURCE_ROOT"; };
DCCB75165B7C73A589498E87 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_linux_Windowing.cpp"; path = "../../../../modules/juce_gui_basics/native/juce_linux_Windowing.cpp"; sourceTree = "SOURCE_ROOT"; };
DD00494140C86144306A9356 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Array.h"; path = "../../../../modules/juce_core/containers/juce_Array.h"; sourceTree = "SOURCE_ROOT"; };
DE8DF5D263F40F65581CFDE4 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ChildProcess.cpp"; path = "../../../../modules/juce_core/threads/juce_ChildProcess.cpp"; sourceTree = "SOURCE_ROOT"; };
DF725A596B7BCD7520CC0A9F = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "jucer_ResourceEditorPanel.cpp"; path = "../../Source/ComponentEditor/ui/jucer_ResourceEditorPanel.cpp"; sourceTree = "SOURCE_ROOT"; };
DFC6364D81D9C60BD4CA9D12 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_RelativeTime.cpp"; path = "../../../../modules/juce_core/time/juce_RelativeTime.cpp"; sourceTree = "SOURCE_ROOT"; };
E0F9CA57E44F7F7E7E217E47 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_ComponentUndoableAction.h"; path = "../../Source/ComponentEditor/components/jucer_ComponentUndoableAction.h"; sourceTree = "SOURCE_ROOT"; };
E19160CF7208320D128786CF = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Javascript.h"; path = "../../../../modules/juce_core/javascript/juce_Javascript.h"; sourceTree = "SOURCE_ROOT"; };
E1D8CCD9F4ACBE1EC1D5BEA0 = {isa = PBXFileReference; lastKnownFileType = file.svg; name = "wizard_AudioApp.svg"; path = "../../Source/BinaryData/wizard_AudioApp.svg"; sourceTree = "SOURCE_ROOT"; };
E2374E15D65425C4101237E2 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_NewComponentTemplate.h"; path = "../../Source/BinaryData/jucer_NewComponentTemplate.h"; sourceTree = "SOURCE_ROOT"; };
E2C1C995D554A3F0A363CE58 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Thread.h"; path = "../../../../modules/juce_core/threads/juce_Thread.h"; sourceTree = "SOURCE_ROOT"; };
E2DBA3307837B64AFCCD8F8D = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_File.cpp"; path = "../../../../modules/juce_core/files/juce_File.cpp"; sourceTree = "SOURCE_ROOT"; };
E3B56C5718D87CA0EFCB2662 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "jucer_AudioComponentTemplate.cpp"; path = "../../Source/BinaryData/jucer_AudioComponentTemplate.cpp"; sourceTree = "SOURCE_ROOT"; };
E4BB22E27C5AA4B666F265BD = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_TextButtonHandler.h"; path = "../../Source/ComponentEditor/components/jucer_TextButtonHandler.h"; sourceTree = "SOURCE_ROOT"; };
E4BD4C43370651B49F75855B = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "jucer_MainTemplate_SimpleWindow.cpp"; path = "../../Source/BinaryData/jucer_MainTemplate_SimpleWindow.cpp"; sourceTree = "SOURCE_ROOT"; };
E570E57CC1FCEF78B54A7084 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_FileOutputStream.h"; path = "../../../../modules/juce_core/files/juce_FileOutputStream.h"; sourceTree = "SOURCE_ROOT"; };
E5D6C36496F5BC84D7213BE8 = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
E62D9BA6E92FE7BB6EF65699 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_JSON.h"; path = "../../../../modules/juce_core/javascript/juce_JSON.h"; sourceTree = "SOURCE_ROOT"; };
E65A820D34BF39478B7C5925 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_DocumentEditorComponent.h"; path = "../../Source/Application/jucer_DocumentEditorComponent.h"; sourceTree = "SOURCE_ROOT"; };
E6F5CEC32EDC917B054467EF = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ReadWriteLock.cpp"; path = "../../../../modules/juce_core/threads/juce_ReadWriteLock.cpp"; sourceTree = "SOURCE_ROOT"; };
E720FBCD836FF3AA466C31B6 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_HighResolutionTimer.cpp"; path = "../../../../modules/juce_core/threads/juce_HighResolutionTimer.cpp"; sourceTree = "SOURCE_ROOT"; };
E96597BBC6A98255B51B94DC = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; };
25F52316D256B4534BED16D1 = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Introjucer.app; sourceTree = "BUILT_PRODUCTS_DIR"; };
CF21D9DB3AEC0A4DCAB36A99 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "jucer_Icons.cpp"; path = "../../Source/Utility/jucer_Icons.cpp"; sourceTree = "SOURCE_ROOT"; };
D10D51A0A2D63F38B4D86A60 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "jucer_ResourceFile.cpp"; path = "../../Source/Project Saving/jucer_ResourceFile.cpp"; sourceTree = "SOURCE_ROOT"; };
C68355AA4D62939EECAD469E = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Random.h"; path = "../../../../modules/juce_core/maths/juce_Random.h"; sourceTree = "SOURCE_ROOT"; };
C85B4D62B96F4A44890F20E2 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_SortedSet.h"; path = "../../../../modules/juce_core/containers/juce_SortedSet.h"; sourceTree = "SOURCE_ROOT"; };
CDDF5BDC75277F7B83A38885 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_PropertySet.cpp"; path = "../../../../modules/juce_core/containers/juce_PropertySet.cpp"; sourceTree = "SOURCE_ROOT"; };
D041043D3B554B88F855C174 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_HighResolutionTimer.h"; path = "../../../../modules/juce_core/threads/juce_HighResolutionTimer.h"; sourceTree = "SOURCE_ROOT"; };
D3E139185095C486DD3D61F2 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_TranslationTool.h"; path = "../../Source/Utility/jucer_TranslationTool.h"; sourceTree = "SOURCE_ROOT"; };
D503780AB98993E8F0BA1311 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ImageComponent.cpp"; path = "../../../../modules/juce_gui_basics/widgets/juce_ImageComponent.cpp"; sourceTree = "SOURCE_ROOT"; };
D5057D7B18ABD5E810A6F830 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_win32_DirectWriteTypeface.cpp"; path = "../../../../modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp"; sourceTree = "SOURCE_ROOT"; };
D6C91E2BF537F75A80F5C1DB = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_ListBox.h"; path = "../../../../modules/juce_gui_basics/widgets/juce_ListBox.h"; sourceTree = "SOURCE_ROOT"; };
D780ED33573AED5AD383A036 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_CaretComponent.h"; path = "../../../../modules/juce_gui_basics/keyboard/juce_CaretComponent.h"; sourceTree = "SOURCE_ROOT"; };
D782DA091AD3ECE158FC6A5F = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_ButtonPropertyComponent.h"; path = "../../../../modules/juce_gui_basics/properties/juce_ButtonPropertyComponent.h"; sourceTree = "SOURCE_ROOT"; };
D678882D133090214AF681BC = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_ThreadLocalValue.h"; path = "../../../../modules/juce_core/threads/juce_ThreadLocalValue.h"; sourceTree = "SOURCE_ROOT"; };
D7A7F1AA9F313B0CCAAA73A0 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_MathsFunctions.h"; path = "../../../../modules/juce_core/maths/juce_MathsFunctions.h"; sourceTree = "SOURCE_ROOT"; };
D800DE818BEDBF4579D15B1D = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "juce_mac_AppleRemote.mm"; path = "../../../../modules/juce_gui_extra/native/juce_mac_AppleRemote.mm"; sourceTree = "SOURCE_ROOT"; };
D9342535EA61901A1AD816C6 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_linux_WebBrowserComponent.cpp"; path = "../../../../modules/juce_gui_extra/native/juce_linux_WebBrowserComponent.cpp"; sourceTree = "SOURCE_ROOT"; };
D95D7B49EC6C6BDCB5A1B988 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "juce_ios_MessageManager.mm"; path = "../../../../modules/juce_events/native/juce_ios_MessageManager.mm"; sourceTree = "SOURCE_ROOT"; };
D9B077E2ECDDA94961E134D7 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_StringPairArray.h"; path = "../../../../modules/juce_core/text/juce_StringPairArray.h"; sourceTree = "SOURCE_ROOT"; };
D9E59DE07A815AB303A297D9 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "juce_data_structures.mm"; path = "../../../../modules/juce_data_structures/juce_data_structures.mm"; sourceTree = "SOURCE_ROOT"; };
DA345D5B9DABD049F90DC96F = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_GeneratedCode.h"; path = "../../Source/ComponentEditor/jucer_GeneratedCode.h"; sourceTree = "SOURCE_ROOT"; };
DAF84A553D264705FA6EB6FF = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_TreeViewHandler.h"; path = "../../Source/ComponentEditor/components/jucer_TreeViewHandler.h"; sourceTree = "SOURCE_ROOT"; };
DB20268A566DABEAE3F2CBEE = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_RectanglePlacement.cpp"; path = "../../../../modules/juce_graphics/placement/juce_RectanglePlacement.cpp"; sourceTree = "SOURCE_ROOT"; };
DB876F7873F42DC685A58CA7 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_JPEGLoader.cpp"; path = "../../../../modules/juce_graphics/image_formats/juce_JPEGLoader.cpp"; sourceTree = "SOURCE_ROOT"; };
DCCB75165B7C73A589498E87 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_linux_Windowing.cpp"; path = "../../../../modules/juce_gui_basics/native/juce_linux_Windowing.cpp"; sourceTree = "SOURCE_ROOT"; };
DBE0CDE1B017190ABBFF557C = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_ProjectExport_CodeBlocks.h"; path = "../../Source/Project Saving/jucer_ProjectExport_CodeBlocks.h"; sourceTree = "SOURCE_ROOT"; };
DC5E7FF30B01118F6DAEC38F = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_Time.cpp"; path = "../../../../modules/juce_core/time/juce_Time.cpp"; sourceTree = "SOURCE_ROOT"; };
DD2494D5F1C081898D616AF5 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_KeyListener.cpp"; path = "../../../../modules/juce_gui_basics/keyboard/juce_KeyListener.cpp"; sourceTree = "SOURCE_ROOT"; };
DD985A60FB76E976AF91852D = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_SHA256.h"; path = "../../../../modules/juce_cryptography/hashing/juce_SHA256.h"; sourceTree = "SOURCE_ROOT"; };
DE40B42B57F29C650CB7F2AD = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_RecentlyOpenedFilesList.h"; path = "../../../../modules/juce_gui_extra/misc/juce_RecentlyOpenedFilesList.h"; sourceTree = "SOURCE_ROOT"; };
DE5F3C9EF6BFFDE73AF9E7FC = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_DirectoryContentsDisplayComponent.cpp"; path = "../../../../modules/juce_gui_basics/filebrowser/juce_DirectoryContentsDisplayComponent.cpp"; sourceTree = "SOURCE_ROOT"; };
DE8DF5D263F40F65581CFDE4 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ChildProcess.cpp"; path = "../../../../modules/juce_core/threads/juce_ChildProcess.cpp"; sourceTree = "SOURCE_ROOT"; };
DEF579B1433EB8DEE7AB50F8 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_Drawable.cpp"; path = "../../../../modules/juce_gui_basics/drawables/juce_Drawable.cpp"; sourceTree = "SOURCE_ROOT"; };
DF7BB5B6B394EDEEF5F5B4B8 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_CallbackMessage.h"; path = "../../../../modules/juce_events/messages/juce_CallbackMessage.h"; sourceTree = "SOURCE_ROOT"; };
DFC6364D81D9C60BD4CA9D12 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_RelativeTime.cpp"; path = "../../../../modules/juce_core/time/juce_RelativeTime.cpp"; sourceTree = "SOURCE_ROOT"; };
E07C56267CBB46FC44EF2026 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_Image.cpp"; path = "../../../../modules/juce_graphics/images/juce_Image.cpp"; sourceTree = "SOURCE_ROOT"; };
E19160CF7208320D128786CF = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Javascript.h"; path = "../../../../modules/juce_core/javascript/juce_Javascript.h"; sourceTree = "SOURCE_ROOT"; };
E1C16C872E34BCB144B469F9 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_EdgeTable.h"; path = "../../../../modules/juce_graphics/geometry/juce_EdgeTable.h"; sourceTree = "SOURCE_ROOT"; };
E27F0860F27023BAA9798B46 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Rectangle.h"; path = "../../../../modules/juce_graphics/geometry/juce_Rectangle.h"; sourceTree = "SOURCE_ROOT"; };
E284B565DBD647DC0830D23B = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ButtonPropertyComponent.cpp"; path = "../../../../modules/juce_gui_basics/properties/juce_ButtonPropertyComponent.cpp"; sourceTree = "SOURCE_ROOT"; };
E2C1C995D554A3F0A363CE58 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Thread.h"; path = "../../../../modules/juce_core/threads/juce_Thread.h"; sourceTree = "SOURCE_ROOT"; };
E2DBA3307837B64AFCCD8F8D = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_File.cpp"; path = "../../../../modules/juce_core/files/juce_File.cpp"; sourceTree = "SOURCE_ROOT"; };
E3869AC657E984565F1718AA = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_ConnectedChildProcess.h"; path = "../../../../modules/juce_events/interprocess/juce_ConnectedChildProcess.h"; sourceTree = "SOURCE_ROOT"; };
E3FF16862AA1B2F943DC616C = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ImageCache.cpp"; path = "../../../../modules/juce_graphics/images/juce_ImageCache.cpp"; sourceTree = "SOURCE_ROOT"; };
E446FFE889CD490FDE3F0F2B = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ThreadWithProgressWindow.cpp"; path = "../../../../modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.cpp"; sourceTree = "SOURCE_ROOT"; };
@@ -858,11 +857,15 @@
E4F9D1E097CC2C1D1291823C = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_LookAndFeel_V2.cpp"; path = "../../../../modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp"; sourceTree = "SOURCE_ROOT"; };
E530742870F07704E9616358 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_ToolbarItemComponent.h"; path = "../../../../modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.h"; sourceTree = "SOURCE_ROOT"; };
E54D0994D31E20A0A05EBA2B = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_SystemTrayIconComponent.h"; path = "../../../../modules/juce_gui_extra/misc/juce_SystemTrayIconComponent.h"; sourceTree = "SOURCE_ROOT"; };
E570E57CC1FCEF78B54A7084 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_FileOutputStream.h"; path = "../../../../modules/juce_core/files/juce_FileOutputStream.h"; sourceTree = "SOURCE_ROOT"; };
E60E28D1B7491047DEA236AE = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_ProjectContentComponent.h"; path = "../../Source/Project/jucer_ProjectContentComponent.h"; sourceTree = "SOURCE_ROOT"; };
E62D9BA6E92FE7BB6EF65699 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_JSON.h"; path = "../../../../modules/juce_core/javascript/juce_JSON.h"; sourceTree = "SOURCE_ROOT"; };
E63F54CF8D5D922A319C6962 = {isa = PBXFileReference; lastKnownFileType = image.png; name = projectIconXcodeIOS.png; path = ../../Source/BinaryData/projectIconXcodeIOS.png; sourceTree = "SOURCE_ROOT"; };
E642193A9990C48CFB6479A9 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_FileListComponent.h"; path = "../../../../modules/juce_gui_basics/filebrowser/juce_FileListComponent.h"; sourceTree = "SOURCE_ROOT"; };
E654E3A3CD45A888C5F773DF = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_MenuBarModel.h"; path = "../../../../modules/juce_gui_basics/menus/juce_MenuBarModel.h"; sourceTree = "SOURCE_ROOT"; };
E6F5CEC32EDC917B054467EF = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ReadWriteLock.cpp"; path = "../../../../modules/juce_core/threads/juce_ReadWriteLock.cpp"; sourceTree = "SOURCE_ROOT"; };
E70CA21960A64CCB835725FF = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "jucer_ProjectType.cpp"; path = "../../Source/Project/jucer_ProjectType.cpp"; sourceTree = "SOURCE_ROOT"; };
E720FBCD836FF3AA466C31B6 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_HighResolutionTimer.cpp"; path = "../../../../modules/juce_core/threads/juce_HighResolutionTimer.cpp"; sourceTree = "SOURCE_ROOT"; };
E73C7E17116F6085765622E3 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Drawable.h"; path = "../../../../modules/juce_gui_basics/drawables/juce_Drawable.h"; sourceTree = "SOURCE_ROOT"; };
E7A4604F766ABC8BE26C94A1 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_RelativeParallelogram.cpp"; path = "../../../../modules/juce_gui_basics/positioning/juce_RelativeParallelogram.cpp"; sourceTree = "SOURCE_ROOT"; };
E7B6A0CBA0D27A095E83F5B7 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_win32_WebBrowserComponent.cpp"; path = "../../../../modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp"; sourceTree = "SOURCE_ROOT"; };
@@ -943,6 +946,7 @@
ACBAFA7D92DD82AD44ABE68A = {isa = PBXGroup; children = (
441CFEA771BAA50E187342E9,
223C4209F18A221EB183A056,
0D4D508C638BC74943B9976D,
EE690110171E1648FF2118B8,
4D698BF12BCD6B0896BCDF17,
23A8DE16C0CDB8EED18B008B,
@@ -1369,6 +1373,7 @@
F07EA5078D6BB60B698F5E12,
68351D69C94230D1DCDB8345,
A243C85FC2C37FD73F115E67,
C3447B6A7C639350E98E43AF,
A4422A360A9FB7BCC315BEF4,
D253F74B7F5734984E568CA7,
D926E13AB5AD647A7A00F486,
@@ -2075,6 +2080,7 @@
1321E6C1C6170B6C898AD09D, ); runOnlyForDeploymentPostprocessing = 0; };
84449D044096A03F2582904B = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = (
357A6AA6960EF95D92929BEE,
6DD9DA1677A6CF789CDAB478,
954A036F5DDB375DB23FFB3E,
3EB3D569250C4BA4CA9AF578,
95B44E6C74B1DED31DBE37EB,


+ 11
- 0
extras/Introjucer/Builds/VisualStudio2005/The Introjucer.vcproj View File

@@ -128,6 +128,7 @@
<Filter Name="Application">
<File RelativePath="..\..\Source\Application\jucer_AppearanceSettings.cpp"/>
<File RelativePath="..\..\Source\Application\jucer_AppearanceSettings.h"/>
<File RelativePath="..\..\Source\Application\jucer_AutoUpdater.cpp"/>
<File RelativePath="..\..\Source\Application\jucer_Application.h"/>
<File RelativePath="..\..\Source\Application\jucer_AutoUpdater.h"/>
<File RelativePath="..\..\Source\Application\jucer_CommandIDs.h"/>
@@ -1535,6 +1536,16 @@
</FileConfiguration>
</File>
<File RelativePath="..\..\..\..\modules\juce_core\native\juce_BasicNativeHeaders.h"/>
<File RelativePath="..\..\..\..\modules\juce_core\native\juce_curl_Network.cpp">
<FileConfiguration Name="Debug|Win32"
ExcludedFromBuild="true">
<Tool Name="VCCLCompilerTool"/>
</FileConfiguration>
<FileConfiguration Name="Release|Win32"
ExcludedFromBuild="true">
<Tool Name="VCCLCompilerTool"/>
</FileConfiguration>
</File>
<File RelativePath="..\..\..\..\modules\juce_core\native\juce_linux_CommonFile.cpp">
<FileConfiguration Name="Debug|Win32"
ExcludedFromBuild="true">


+ 11
- 0
extras/Introjucer/Builds/VisualStudio2008/The Introjucer.vcproj View File

@@ -128,6 +128,7 @@
<Filter Name="Application">
<File RelativePath="..\..\Source\Application\jucer_AppearanceSettings.cpp"/>
<File RelativePath="..\..\Source\Application\jucer_AppearanceSettings.h"/>
<File RelativePath="..\..\Source\Application\jucer_AutoUpdater.cpp"/>
<File RelativePath="..\..\Source\Application\jucer_Application.h"/>
<File RelativePath="..\..\Source\Application\jucer_AutoUpdater.h"/>
<File RelativePath="..\..\Source\Application\jucer_CommandIDs.h"/>
@@ -1535,6 +1536,16 @@
</FileConfiguration>
</File>
<File RelativePath="..\..\..\..\modules\juce_core\native\juce_BasicNativeHeaders.h"/>
<File RelativePath="..\..\..\..\modules\juce_core\native\juce_curl_Network.cpp">
<FileConfiguration Name="Debug|Win32"
ExcludedFromBuild="true">
<Tool Name="VCCLCompilerTool"/>
</FileConfiguration>
<FileConfiguration Name="Release|Win32"
ExcludedFromBuild="true">
<Tool Name="VCCLCompilerTool"/>
</FileConfiguration>
</File>
<File RelativePath="..\..\..\..\modules\juce_core\native\juce_linux_CommonFile.cpp">
<FileConfiguration Name="Debug|Win32"
ExcludedFromBuild="true">


+ 4
- 0
extras/Introjucer/Builds/VisualStudio2010/The Introjucer.vcxproj View File

@@ -124,6 +124,7 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\..\Source\Application\jucer_AppearanceSettings.cpp"/>
<ClCompile Include="..\..\Source\Application\jucer_AutoUpdater.cpp"/>
<ClCompile Include="..\..\Source\Application\jucer_CommandLine.cpp"/>
<ClCompile Include="..\..\Source\Application\jucer_DocumentEditorComponent.cpp"/>
<ClCompile Include="..\..\Source\Application\jucer_Main.cpp"/>
@@ -396,6 +397,9 @@
<ClCompile Include="..\..\..\..\modules\juce_core\native\juce_android_Threads.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_core\native\juce_curl_Network.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_core\native\juce_linux_CommonFile.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>


+ 6
- 0
extras/Introjucer/Builds/VisualStudio2010/The Introjucer.vcxproj.filters View File

@@ -253,6 +253,9 @@
<ClCompile Include="..\..\Source\Application\jucer_AppearanceSettings.cpp">
<Filter>The Introjucer\Application</Filter>
</ClCompile>
<ClCompile Include="..\..\Source\Application\jucer_AutoUpdater.cpp">
<Filter>The Introjucer\Application</Filter>
</ClCompile>
<ClCompile Include="..\..\Source\Application\jucer_CommandLine.cpp">
<Filter>The Introjucer\Application</Filter>
</ClCompile>
@@ -607,6 +610,9 @@
<ClCompile Include="..\..\..\..\modules\juce_core\native\juce_android_Threads.cpp">
<Filter>Juce Modules\juce_core\native</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_core\native\juce_curl_Network.cpp">
<Filter>Juce Modules\juce_core\native</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_core\native\juce_linux_CommonFile.cpp">
<Filter>Juce Modules\juce_core\native</Filter>
</ClCompile>


+ 4
- 0
extras/Introjucer/Builds/VisualStudio2012/The Introjucer.vcxproj View File

@@ -130,6 +130,7 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\..\Source\Application\jucer_AppearanceSettings.cpp"/>
<ClCompile Include="..\..\Source\Application\jucer_AutoUpdater.cpp"/>
<ClCompile Include="..\..\Source\Application\jucer_CommandLine.cpp"/>
<ClCompile Include="..\..\Source\Application\jucer_DocumentEditorComponent.cpp"/>
<ClCompile Include="..\..\Source\Application\jucer_Main.cpp"/>
@@ -402,6 +403,9 @@
<ClCompile Include="..\..\..\..\modules\juce_core\native\juce_android_Threads.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_core\native\juce_curl_Network.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_core\native\juce_linux_CommonFile.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>


+ 6
- 0
extras/Introjucer/Builds/VisualStudio2012/The Introjucer.vcxproj.filters View File

@@ -253,6 +253,9 @@
<ClCompile Include="..\..\Source\Application\jucer_AppearanceSettings.cpp">
<Filter>The Introjucer\Application</Filter>
</ClCompile>
<ClCompile Include="..\..\Source\Application\jucer_AutoUpdater.cpp">
<Filter>The Introjucer\Application</Filter>
</ClCompile>
<ClCompile Include="..\..\Source\Application\jucer_CommandLine.cpp">
<Filter>The Introjucer\Application</Filter>
</ClCompile>
@@ -607,6 +610,9 @@
<ClCompile Include="..\..\..\..\modules\juce_core\native\juce_android_Threads.cpp">
<Filter>Juce Modules\juce_core\native</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_core\native\juce_curl_Network.cpp">
<Filter>Juce Modules\juce_core\native</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_core\native\juce_linux_CommonFile.cpp">
<Filter>Juce Modules\juce_core\native</Filter>
</ClCompile>


+ 4
- 0
extras/Introjucer/Builds/VisualStudio2013/The Introjucer.vcxproj View File

@@ -130,6 +130,7 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\..\Source\Application\jucer_AppearanceSettings.cpp"/>
<ClCompile Include="..\..\Source\Application\jucer_AutoUpdater.cpp"/>
<ClCompile Include="..\..\Source\Application\jucer_CommandLine.cpp"/>
<ClCompile Include="..\..\Source\Application\jucer_DocumentEditorComponent.cpp"/>
<ClCompile Include="..\..\Source\Application\jucer_Main.cpp"/>
@@ -402,6 +403,9 @@
<ClCompile Include="..\..\..\..\modules\juce_core\native\juce_android_Threads.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_core\native\juce_curl_Network.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_core\native\juce_linux_CommonFile.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>


+ 6
- 0
extras/Introjucer/Builds/VisualStudio2013/The Introjucer.vcxproj.filters View File

@@ -253,6 +253,9 @@
<ClCompile Include="..\..\Source\Application\jucer_AppearanceSettings.cpp">
<Filter>The Introjucer\Application</Filter>
</ClCompile>
<ClCompile Include="..\..\Source\Application\jucer_AutoUpdater.cpp">
<Filter>The Introjucer\Application</Filter>
</ClCompile>
<ClCompile Include="..\..\Source\Application\jucer_CommandLine.cpp">
<Filter>The Introjucer\Application</Filter>
</ClCompile>
@@ -607,6 +610,9 @@
<ClCompile Include="..\..\..\..\modules\juce_core\native\juce_android_Threads.cpp">
<Filter>Juce Modules\juce_core\native</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_core\native\juce_curl_Network.cpp">
<Filter>Juce Modules\juce_core\native</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_core\native\juce_linux_CommonFile.cpp">
<Filter>Juce Modules\juce_core\native</Filter>
</ClCompile>


+ 3
- 1
extras/Introjucer/Introjucer.jucer View File

@@ -160,6 +160,8 @@
file="Source/Application/jucer_AppearanceSettings.cpp"/>
<FILE id="oCCddi" name="jucer_AppearanceSettings.h" compile="0" resource="0"
file="Source/Application/jucer_AppearanceSettings.h"/>
<FILE id="IvfYzk" name="jucer_AutoUpdater.cpp" compile="1" resource="0"
file="Source/Application/jucer_AutoUpdater.cpp"/>
<FILE id="PXX8Yi" name="jucer_Application.h" compile="0" resource="0"
file="Source/Application/jucer_Application.h"/>
<FILE id="RWf1Jy" name="jucer_AutoUpdater.h" compile="0" resource="0"
@@ -602,7 +604,7 @@
JUCE_USE_FLAC="disabled" JUCE_USE_CDBURNER="disabled" JUCE_USE_CDREADER="disabled"
JUCE_USE_CAMERA="disabled" JUCE_PLUGINHOST_VST="disabled" JUCE_PLUGINHOST_AU="disabled"
JUCE_USE_OGGVORBIS="disabled" JUCE_USE_COREIMAGE_LOADER="disabled"
JUCE_LOG_ASSERTIONS="enabled"/>
JUCE_LOG_ASSERTIONS="enabled" JUCE_USE_CURL="enabled"/>
<MODULES>
<MODULE id="juce_core" showAllCode="1"/>
<MODULE id="juce_cryptography" showAllCode="1"/>


+ 1
- 1
extras/Introjucer/JuceLibraryCode/AppConfig.h View File

@@ -59,7 +59,7 @@
#endif
#ifndef JUCE_USE_CURL
//#define JUCE_USE_CURL
#define JUCE_USE_CURL 1
#endif
//==============================================================================


+ 789
- 0
extras/Introjucer/Source/Application/jucer_AutoUpdater.cpp View File

@@ -0,0 +1,789 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#include "jucer_Application.h"
#include "jucer_AutoUpdater.h"
LatestVersionChecker::JuceVersionTriple::JuceVersionTriple()
: major (JUCE_MAJOR_VERSION),
minor (JUCE_MINOR_VERSION),
build (JUCE_BUILDNUMBER)
{}
LatestVersionChecker::JuceVersionTriple::JuceVersionTriple (int majorInt, int minorInt, int buildNumber)
: major (majorInt),
minor (minorInt),
build (buildNumber)
{}
bool LatestVersionChecker::JuceVersionTriple::fromString (const String& versionString,
LatestVersionChecker::JuceVersionTriple& result)
{
StringArray tokenizedString = StringArray::fromTokens (versionString, ".", StringRef());
if (tokenizedString.size() != 3)
return false;
result.major = tokenizedString [0].getIntValue();
result.minor = tokenizedString [1].getIntValue();
result.build = tokenizedString [2].getIntValue();
return true;
}
String LatestVersionChecker::JuceVersionTriple::toString() const
{
String retval;
retval << major << '.' << minor << '.' << build;
return retval;
}
bool LatestVersionChecker::JuceVersionTriple::operator> (const LatestVersionChecker::JuceVersionTriple& b) const noexcept
{
if (major == b.major)
{
if (minor == b.minor)
return build > b.build;
return minor > b.minor;
}
return major > b.major;
}
//==============================================================================
struct RelaunchTimer : private Timer
{
RelaunchTimer (const File& f) : parentFolder (f)
{
startTimer (1500);
}
void timerCallback() override
{
stopTimer();
File app = parentFolder.getChildFile (
#if JUCE_MAC
"Introjucer.app");
#elif JUCE_WINDOWS
"Introjucer.exe");
#elif JUCE_LINUX
"Introjucer");
#endif
JUCEApplication::quit();
if (app.exists())
{
app.setExecutePermission (true);
#if JUCE_MAC
app.getChildFile ("Contents")
.getChildFile ("MacOS")
.getChildFile ("Introjucer").setExecutePermission (true);
#endif
app.startAsProcess();
}
delete this;
}
File parentFolder;
};
//==============================================================================
class DownloadNewVersionThread : public ThreadWithProgressWindow
{
public:
DownloadNewVersionThread (URL u, const String& extraHeaders, File target)
: ThreadWithProgressWindow ("Downloading New Version", true, true),
result (Result::ok()),
url (u), headers (extraHeaders), targetFolder (target)
{
}
static void performDownload (URL u, const String& extraHeaders, File targetFolder)
{
DownloadNewVersionThread d (u, extraHeaders, targetFolder);
if (d.runThread())
{
if (d.result.failed())
{
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
"Installation Failed",
d.result.getErrorMessage());
}
else
{
new RelaunchTimer (targetFolder);
}
}
}
void run() override
{
setProgress (-1.0);
MemoryBlock zipData;
result = download (zipData);
if (result.wasOk() && ! threadShouldExit())
result = unzip (zipData);
}
Result download (MemoryBlock& dest)
{
setStatusMessage ("Downloading...");
int statusCode = 302;
const int maxRedirects = 5;
// we need to do the redirecting manually due to inconsistencies on the way headers are handled on redirects
ScopedPointer<InputStream> in;
for (int redirect = 0; redirect < maxRedirects; ++redirect)
{
StringPairArray responseHeaders;
in = url.createInputStream (false, nullptr, nullptr, headers, 10000, &responseHeaders, &statusCode, 0);
if (in == nullptr || statusCode != 302)
break;
String redirectPath = responseHeaders ["Location"];
if (redirectPath.isEmpty())
break;
url = LatestVersionChecker::getLatestVersionURL (headers, redirectPath);
}
if (in != nullptr && statusCode == 200)
{
int64 total = 0;
MemoryOutputStream mo (dest, true);
for (;;)
{
if (threadShouldExit())
return Result::fail ("cancel");
int64 written = mo.writeFromInputStream (*in, 8192);
if (written == 0)
break;
total += written;
setStatusMessage (String (TRANS ("Downloading... (123)"))
.replace ("123", File::descriptionOfSizeInBytes (total)));
}
return Result::ok();
}
return Result::fail ("Failed to download from: " + url.toString (false));
}
Result unzip (const MemoryBlock& data)
{
setStatusMessage ("Installing...");
File unzipTarget;
bool isUsingTempFolder = false;
{
MemoryInputStream input (data, false);
ZipFile zip (input);
if (zip.getNumEntries() == 0)
return Result::fail ("The downloaded file wasn't a valid JUCE file!");
unzipTarget = targetFolder;
if (unzipTarget.exists())
{
isUsingTempFolder = true;
unzipTarget = targetFolder.getNonexistentSibling();
if (! unzipTarget.createDirectory())
return Result::fail ("Couldn't create a folder to unzip the new version!");
}
Result r (zip.uncompressTo (unzipTarget));
if (r.failed())
{
if (isUsingTempFolder)
unzipTarget.deleteRecursively();
return r;
}
}
if (isUsingTempFolder)
{
File oldFolder (targetFolder.getSiblingFile (targetFolder.getFileNameWithoutExtension() + "_old")
.getNonexistentSibling());
if (! targetFolder.moveFileTo (oldFolder))
{
unzipTarget.deleteRecursively();
return Result::fail ("Could not remove the existing folder!");
}
if (! unzipTarget.moveFileTo (targetFolder))
{
unzipTarget.deleteRecursively();
return Result::fail ("Could not overwrite the existing folder!");
}
}
return Result::ok();
}
Result result;
URL url;
String headers;
File targetFolder;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DownloadNewVersionThread)
};
//==============================================================================
class UpdateUserDialog : public Component,
public ButtonListener
{
public:
UpdateUserDialog (const LatestVersionChecker::JuceVersionTriple& version,
const String& releaseNotes,
const char* overwriteFolderPath)
: hasOverwriteButton (overwriteFolderPath != nullptr)
{
addAndMakeVisible (titleLabel = new Label ("Title Label",
TRANS ("Download JUCE version 123?").
replace ("123", version.toString())));
titleLabel->setFont (Font (15.00f, Font::bold));
titleLabel->setJustificationType (Justification::centredLeft);
titleLabel->setEditable (false, false, false);
titleLabel->setColour (TextEditor::textColourId, Colours::black);
titleLabel->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
addAndMakeVisible (contentLabel = new Label ("Content Label",
TRANS("A new version of JUCE is available - would you like to download it?")));
contentLabel->setFont (Font (15.00f, Font::plain));
contentLabel->setJustificationType (Justification::topLeft);
contentLabel->setEditable (false, false, false);
contentLabel->setColour (TextEditor::textColourId, Colours::black);
contentLabel->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
addAndMakeVisible (okButton = new TextButton ("OK Button"));
okButton->setButtonText (TRANS(hasOverwriteButton ? "Choose Another Folder..." : "OK"));
okButton->addListener (this);
addAndMakeVisible (cancelButton = new TextButton ("Cancel Button"));
cancelButton->setButtonText (TRANS("Cancel"));
cancelButton->addListener (this);
addAndMakeVisible (changeLogLabel = new Label ("Change Log Label",
TRANS("Release Notes:")));
changeLogLabel->setFont (Font (15.00f, Font::plain));
changeLogLabel->setJustificationType (Justification::topLeft);
changeLogLabel->setEditable (false, false, false);
changeLogLabel->setColour (TextEditor::textColourId, Colours::black);
changeLogLabel->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
addAndMakeVisible (changeLog = new TextEditor ("Change Log"));
changeLog->setMultiLine (true);
changeLog->setReturnKeyStartsNewLine (true);
changeLog->setReadOnly (true);
changeLog->setScrollbarsShown (true);
changeLog->setCaretVisible (false);
changeLog->setPopupMenuEnabled (false);
changeLog->setText (releaseNotes);
if (hasOverwriteButton)
{
addAndMakeVisible (overwriteLabel = new Label ("Overwrite Label",
TRANS("Updating will overwrite everything in the following folder:")));
overwriteLabel->setFont (Font (15.00f, Font::plain));
overwriteLabel->setJustificationType (Justification::topLeft);
overwriteLabel->setEditable (false, false, false);
overwriteLabel->setColour (TextEditor::textColourId, Colours::black);
overwriteLabel->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
addAndMakeVisible (overwritePath = new Label ("Overwrite Path", overwriteFolderPath));
overwritePath->setFont (Font (15.00f, Font::bold));
overwritePath->setJustificationType (Justification::topLeft);
overwritePath->setEditable (false, false, false);
overwritePath->setColour (TextEditor::textColourId, Colours::black);
overwritePath->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
addAndMakeVisible (overwriteButton = new TextButton ("Overwrite Button"));
overwriteButton->setButtonText (TRANS("Overwrite"));
overwriteButton->addListener (this);
}
juceIcon = Drawable::createFromImageData (BinaryData::juce_icon_png,
BinaryData::juce_icon_pngSize);
setSize (518, overwritePath ? 345 : 269);
}
~UpdateUserDialog()
{
titleLabel = nullptr;
contentLabel = nullptr;
okButton = nullptr;
cancelButton = nullptr;
changeLogLabel = nullptr;
changeLog = nullptr;
overwriteLabel = nullptr;
overwritePath = nullptr;
overwriteButton = nullptr;
juceIcon = nullptr;
}
void paint (Graphics& g) override
{
g.fillAll (Colours::lightgrey);
g.setColour (Colours::black);
if (juceIcon != nullptr)
juceIcon->drawWithin (g, Rectangle<float> (20, 17, 64, 64),
RectanglePlacement::stretchToFit, 1.000f);
}
void resized() override
{
titleLabel->setBounds (88, 10, 397, 24);
contentLabel->setBounds (88, 40, 397, 51);
changeLogLabel->setBounds (22, 92, 341, 24);
changeLog->setBounds (24, 112, 476, 102);
if (hasOverwriteButton)
{
okButton->setBounds (getWidth() - 24 - 174, getHeight() - 37, 174, 28);
overwriteButton->setBounds ((getWidth() - 24 - 174) + -14 - 86, getHeight() - 37, 86, 28);
cancelButton->setBounds (24, getHeight() - 37, 70, 28);
overwriteLabel->setBounds (24, 238, 472, 16);
overwritePath->setBounds (24, 262, 472, 40);
}
else
{
okButton->setBounds (getWidth() - 24 - 47, getHeight() - 37, 47, 28);
cancelButton->setBounds ((getWidth() - 24 - 47) + -14 - 70, getHeight() - 37, 70, 28);
}
}
void buttonClicked (Button* clickedButton) override
{
if (DialogWindow* parentDialog = findParentComponentOfClass<DialogWindow>())
{
if (clickedButton == overwriteButton) parentDialog->exitModalState (1);
else if (clickedButton == okButton) parentDialog->exitModalState (2);
else if (clickedButton == cancelButton) parentDialog->exitModalState (-1);
}
else
jassertfalse;
}
static DialogWindow* launch (const LatestVersionChecker::JuceVersionTriple& version,
const String& releaseNotes,
const char* overwritePath = nullptr)
{
OptionalScopedPointer<Component> userDialog (new UpdateUserDialog (version, releaseNotes, overwritePath), true);
DialogWindow::LaunchOptions lo;
lo.dialogTitle = TRANS ("Download JUCE version 123?").replace ("123", version.toString());
lo.dialogBackgroundColour = Colours::lightgrey;
lo.content = userDialog;
lo.componentToCentreAround = nullptr;
lo.escapeKeyTriggersCloseButton = true;
lo.useNativeTitleBar = true;
lo.resizable = false;
lo.useBottomRightCornerResizer = false;
return lo.launchAsync();
}
private:
bool hasOverwriteButton;
ScopedPointer<Label> titleLabel, contentLabel, changeLogLabel, overwriteLabel, overwritePath;
ScopedPointer<TextButton> okButton, cancelButton;
ScopedPointer<TextEditor> changeLog;
ScopedPointer<TextButton> overwriteButton;
ScopedPointer<Drawable> juceIcon;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UpdateUserDialog)
};
//==============================================================================
class UpdaterDialogModalCallback : public ModalComponentManager::Callback
{
public:
struct DelayedCallback : private Timer
{
DelayedCallback (LatestVersionChecker& versionChecker,
URL& newVersionToDownload,
const String& extraHeaders,
const File& appParentFolder,
int returnValue)
: parent (versionChecker), download (newVersionToDownload),
headers (extraHeaders), folder (appParentFolder), result (returnValue)
{
startTimer (200);
}
private:
void timerCallback() override
{
stopTimer();
parent.modalStateFinished (result, download, headers, folder);
delete this;
}
LatestVersionChecker& parent;
URL download;
String headers;
File folder;
int result;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayedCallback)
};
UpdaterDialogModalCallback (LatestVersionChecker& versionChecker,
URL& newVersionToDownload,
const String& extraHeaders,
const File& appParentFolder)
: parent (versionChecker), download (newVersionToDownload),
headers (extraHeaders), folder (appParentFolder)
{}
void modalStateFinished (int returnValue) override
{
// the dialog window is only closed after this function exits
// so we need a deferred callback to the parent. Unfortunately
// our instance is also deleted after this function is used
// so we can't use our own instance for a timer callback
// we must allocate a new one.
new DelayedCallback (parent, download, headers, folder, returnValue);
}
private:
LatestVersionChecker& parent;
URL download;
String headers;
File folder;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UpdaterDialogModalCallback)
};
//==============================================================================
LatestVersionChecker::LatestVersionChecker() : Thread ("Updater"),
statusCode (-1),
hasAttemptedToReadWebsite (false)
{
startTimer (2000);
}
LatestVersionChecker::~LatestVersionChecker()
{
stopThread (20000);
}
String LatestVersionChecker::getOSString()
{
SystemStats::OperatingSystemType osType = SystemStats::getOperatingSystemType();
if ((osType & SystemStats::MacOSX) != 0) return "OSX";
else if ((osType & SystemStats::Windows) != 0) return "Windows";
else if ((osType & SystemStats::Linux) != 0) return "Linux";
else return SystemStats::getOperatingSystemName();
}
URL LatestVersionChecker::getLatestVersionURL (String& headers, const String& path)
{
static const char* updateSeverHostname = "https://my.roli.com";
static const char* publicAPIKey = "495fb2d-cce9a8-3c52824-2da2679";
static const int apiVersion = 1;
static const char* updatePath = "/software_versions/update_to/Introjucer/";
String updateURL;
bool isAbsolute = (path.startsWith ("http://") || path.startsWith ("https://"));
bool isRedirect = path.isNotEmpty();
if (isAbsolute)
{
updateURL = path;
}
else
{
updateURL << updateSeverHostname
<< (isRedirect ? path : String (updatePath));
if (! isRedirect)
{
updateURL << JuceVersionTriple().toString() << '/'
<< getOSString() << "?language=" << SystemStats::getUserLanguage();
}
}
headers.clear();
if (! isAbsolute)
{
headers << "X-API-Key: " << publicAPIKey;
if (! isRedirect)
{
headers << "\nContent-Type: application/json\n"
<< "Accept: application/json; version=" << apiVersion;
}
}
return URL (updateURL);
}
URL LatestVersionChecker::getLatestVersionURL (String& headers)
{
String emptyString;
return getLatestVersionURL (headers, emptyString);
}
void LatestVersionChecker::checkForNewVersion()
{
hasAttemptedToReadWebsite = true;
{
String extraHeaders;
URL updateURL (getLatestVersionURL (extraHeaders));
StringPairArray responseHeaders;
const int numRedirects = 0;
const ScopedPointer<InputStream> in (updateURL.createInputStream (false, nullptr, nullptr,
extraHeaders, 0, &responseHeaders,
&statusCode, numRedirects));
if (in == nullptr || threadShouldExit())
return; // can't connect: fail silently.
if (statusCode != 303 && statusCode != 400)
return;
// if this doesn't fail then there is a new version available.
// By leaving the scope of this function we will abort the download
// to give the user a chance to cancel an update
if (statusCode == 303)
{
newRelativeDownloadPath = responseHeaders ["Location"];
// A 303 without a redirect target? Let's quickly get out of here
if (newRelativeDownloadPath.isEmpty())
return;
}
jsonReply = JSON::parse (in->readEntireStreamAsString());
}
if (threadShouldExit())
return;
if (jsonReply.isObject())
startTimer (100);
}
void LatestVersionChecker::processResult (var reply, const String& downloadPath)
{
if (statusCode == 303)
{
String versionString = reply.getProperty ("version", var()).toString();
String releaseNotes = reply.getProperty ("notes", var()).toString();
JuceVersionTriple version;
if (versionString.isNotEmpty() && releaseNotes.isNotEmpty())
{
if (JuceVersionTriple::fromString (versionString, version))
{
String extraHeaders;
URL newVersionToDownload = getLatestVersionURL (extraHeaders, downloadPath);
askUserAboutNewVersion (version, releaseNotes, newVersionToDownload, extraHeaders);
}
}
}
else if (statusCode == 400)
{
// In the far-distant future, this may be contacting a defunct
// URL, so hopefully the website will contain a helpful message
// for the user..
var errorObj = reply.getDynamicObject()->getProperty ("error");
if (errorObj.isObject())
{
String message = errorObj.getProperty ("message", var()).toString();
if (message.isNotEmpty())
{
AlertWindow::showMessageBox (AlertWindow::WarningIcon,
TRANS("JUCE Updater"),
message);
}
}
}
}
void LatestVersionChecker::askUserAboutNewVersion (const LatestVersionChecker::JuceVersionTriple& version,
const String& releaseNotes,
URL& newVersionToDownload,
const String& extraHeaders)
{
// Currently we do not show the release notes
ignoreUnused (releaseNotes);
JuceVersionTriple currentVersion;
if (version > currentVersion)
{
File appParentFolder (File::getSpecialLocation (File::currentApplicationFile).getParentDirectory());
DialogWindow* modalDialog = nullptr;
if (isZipFolder (appParentFolder))
{
modalDialog = UpdateUserDialog::launch (version, releaseNotes,
appParentFolder.getFullPathName().toRawUTF8());
}
else
{
modalDialog = UpdateUserDialog::launch (version, releaseNotes);
}
if (modalDialog != nullptr)
{
UpdaterDialogModalCallback* callback = new UpdaterDialogModalCallback (*this,
newVersionToDownload,
extraHeaders,
appParentFolder);
// attachCallback will delete callback
if (ModalComponentManager* mm = ModalComponentManager::getInstance())
mm->attachCallback (modalDialog, callback);
}
}
}
void LatestVersionChecker::modalStateFinished (int result,
URL& newVersionToDownload,
const String& extraHeaders,
File appParentFolder)
{
if (result == 1)
DownloadNewVersionThread::performDownload (newVersionToDownload, extraHeaders, appParentFolder);
if (result == 2)
askUserForLocationToDownload (newVersionToDownload, extraHeaders);
}
void LatestVersionChecker::askUserForLocationToDownload (URL& newVersionToDownload, const String& extraHeaders)
{
File targetFolder (findDefaultModulesFolder());
if (isJuceModulesFolder (targetFolder))
targetFolder = targetFolder.getParentDirectory();
FileChooser chooser (TRANS("Please select the location into which you'd like to install the new version"),
targetFolder);
if (chooser.browseForDirectory())
{
targetFolder = chooser.getResult();
if (isJuceModulesFolder (targetFolder))
targetFolder = targetFolder.getParentDirectory();
if (targetFolder.getChildFile ("JUCE").isDirectory())
targetFolder = targetFolder.getChildFile ("JUCE");
if (targetFolder.getChildFile (".git").isDirectory())
{
AlertWindow::showMessageBox (AlertWindow::WarningIcon,
TRANS ("Downloading new JUCE version"),
TRANS ("This folder is a GIT repository!\n\n"
"You should use a \"git pull\" to update it to the latest version. "
"Or to use the Introjucer to get an update, you should select an empty "
"folder into which you'd like to download the new code."));
return;
}
if (isJuceFolder (targetFolder))
{
if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
TRANS("Overwrite existing JUCE folder?"),
TRANS("Do you want to overwrite the folder:\n\n"
"xfldrx\n\n"
" ..with the latest version from juce.com?\n\n"
"(Please note that this will overwrite everything in that folder!)")
.replace ("xfldrx", targetFolder.getFullPathName())))
{
return;
}
}
else
{
targetFolder = targetFolder.getChildFile ("JUCE").getNonexistentSibling();
}
DownloadNewVersionThread::performDownload (newVersionToDownload, extraHeaders, targetFolder);
}
}
bool LatestVersionChecker::isZipFolder (const File& f)
{
return f.getChildFile ("modules").isDirectory()
&& f.getChildFile ("extras").isDirectory()
&& f.getChildFile ("examples").isDirectory()
&& ! f.getChildFile (".git").isDirectory();
}
void LatestVersionChecker::timerCallback()
{
stopTimer();
if (hasAttemptedToReadWebsite)
processResult (jsonReply, newRelativeDownloadPath);
else
startThread (3);
}
void LatestVersionChecker::run()
{
checkForNewVersion();
}

+ 33
- 365
extras/Introjucer/Source/Application/jucer_AutoUpdater.h View File

@@ -25,395 +25,63 @@
#ifndef JUCER_AUTOUPDATER_H_INCLUDED
#define JUCER_AUTOUPDATER_H_INCLUDED
class UpdaterDialogModalCallback;
//==============================================================================
class LatestVersionChecker : private Thread,
private Timer
{
public:
LatestVersionChecker() : Thread ("Updater"),
hasAttemptedToReadWebsite (false)
{
startTimer (2000);
}
~LatestVersionChecker()
{
stopThread (20000);
}
static URL getLatestVersionURL()
{
return URL ("http://www.juce.com/juce/updates/updatelist.php");
}
void checkForNewVersion()
{
hasAttemptedToReadWebsite = true;
{
const ScopedPointer<InputStream> in (getLatestVersionURL().createInputStream (false));
if (in == nullptr || threadShouldExit())
return; // can't connect: fail silently.
jsonReply = JSON::parse (in->readEntireStreamAsString());
}
if (threadShouldExit())
return;
if (jsonReply.isArray() || jsonReply.isObject())
startTimer (100);
}
void processResult (var reply)
{
if (reply.isArray())
{
askUserAboutNewVersion (VersionInfo (reply[0]));
}
else if (reply.isObject())
{
// In the far-distant future, this may be contacting a defunct
// URL, so hopefully the website will contain a helpful message
// for the user..
String message = reply.getProperty ("message", var()).toString();
if (message.isNotEmpty())
{
AlertWindow::showMessageBox (AlertWindow::WarningIcon,
TRANS("JUCE Updater"),
message);
}
}
}
struct VersionInfo
struct JuceVersionTriple
{
VersionInfo (var v)
{
version = v.getProperty ("version", var()).toString().trim();
JuceVersionTriple();
JuceVersionTriple (int majorInt, int minorInt, int buildNumber);
url = v.getProperty (
#if JUCE_MAC
"url_osx",
#elif JUCE_WINDOWS
"url_win",
#elif JUCE_LINUX
"url_linux",
#endif
var()).toString();
}
static bool fromString (const String& versionString, JuceVersionTriple& result);
String toString() const;
bool isDifferentVersionToCurrent() const
{
return version != JUCE_STRINGIFY(JUCE_MAJOR_VERSION)
"." JUCE_STRINGIFY(JUCE_MINOR_VERSION)
"." JUCE_STRINGIFY(JUCE_BUILDNUMBER)
&& version.containsChar ('.')
&& version.length() > 2;
}
bool operator> (const JuceVersionTriple& b) const noexcept;
String version;
URL url;
int major, minor, build;
};
void askUserAboutNewVersion (const VersionInfo& info)
{
if (info.isDifferentVersionToCurrent())
{
File appParentFolder (File::getSpecialLocation (File::currentApplicationFile).getParentDirectory());
if (isZipFolder (appParentFolder))
{
int result = AlertWindow::showYesNoCancelBox (AlertWindow::InfoIcon,
TRANS("Download JUCE version 123?").replace ("123", info.version),
TRANS("A new version of JUCE is available - would you like to overwrite the folder:\n\n"
"xfldrx\n\n"
" ..with the latest version from juce.com?\n\n"
"(Please note that this will overwrite everything in that folder!)")
.replace ("xfldrx", appParentFolder.getFullPathName()),
TRANS("Overwrite"),
TRANS("Choose another folder..."),
TRANS("Cancel"));
if (result == 1)
DownloadNewVersionThread::performDownload (info.url, appParentFolder);
if (result == 2)
askUserForLocationToDownload (info);
}
else
{
if (AlertWindow::showOkCancelBox (AlertWindow::InfoIcon,
TRANS("Download JUCE version 123?").replace ("123", info.version),
TRANS("A new version of JUCE is available - would you like to download it?")))
askUserForLocationToDownload (info);
}
}
}
void askUserForLocationToDownload (const VersionInfo& info)
{
File targetFolder (findDefaultModulesFolder());
if (isJuceModulesFolder (targetFolder))
targetFolder = targetFolder.getParentDirectory();
FileChooser chooser (TRANS("Please select the location into which you'd like to install the new version"),
targetFolder);
if (chooser.browseForDirectory())
{
targetFolder = chooser.getResult();
//==============================================================================
LatestVersionChecker();
~LatestVersionChecker();
if (isJuceModulesFolder (targetFolder))
targetFolder = targetFolder.getParentDirectory();
static String getOSString();
if (targetFolder.getChildFile ("JUCE").isDirectory())
targetFolder = targetFolder.getChildFile ("JUCE");
static URL getLatestVersionURL (String& headers, const String& path);
static URL getLatestVersionURL (String& headers);
if (targetFolder.getChildFile (".git").isDirectory())
{
AlertWindow::showMessageBox (AlertWindow::WarningIcon,
TRANS ("Downloading new JUCE version"),
TRANS ("This folder is a GIT repository!\n\n"
"You should use a \"git pull\" to update it to the latest version. "
"Or to use the Introjucer to get an update, you should select an empty "
"folder into which you'd like to download the new code."));
void checkForNewVersion();
void processResult (var reply, const String& downloadPath);
return;
}
void askUserAboutNewVersion (const JuceVersionTriple& version,
const String& releaseNotes,
URL& newVersionToDownload,
const String& extraHeaders);
if (isJuceFolder (targetFolder))
{
if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
TRANS("Overwrite existing JUCE folder?"),
TRANS("Do you want to overwrite the folder:\n\n"
"xfldrx\n\n"
" ..with the latest version from juce.com?\n\n"
"(Please note that this will overwrite everything in that folder!)")
.replace ("xfldrx", targetFolder.getFullPathName())))
{
return;
}
}
else
{
targetFolder = targetFolder.getChildFile ("JUCE").getNonexistentSibling();
}
void askUserForLocationToDownload (URL& newVersionToDownload, const String& extraHeaders);
DownloadNewVersionThread::performDownload (info.url, targetFolder);
}
}
static bool isZipFolder (const File& f)
{
return f.getChildFile ("modules").isDirectory()
&& f.getChildFile ("extras").isDirectory()
&& f.getChildFile ("examples").isDirectory()
&& ! f.getChildFile (".git").isDirectory();
}
static bool isZipFolder (const File&);
private:
void timerCallback() override
{
stopTimer();
if (hasAttemptedToReadWebsite)
processResult (jsonReply);
else
startThread (3);
}
//==============================================================================
friend class UpdaterDialogModalCallback;
void run() override
{
checkForNewVersion();
}
// callbacks
void timerCallback() override;
void run() override;
void modalStateFinished (int result,
URL& newVersionToDownload,
const String& extraHeaders,
File appParentFolder);
int statusCode;
var jsonReply;
bool hasAttemptedToReadWebsite;
URL newVersionToDownload;
//==============================================================================
class DownloadNewVersionThread : public ThreadWithProgressWindow
{
public:
DownloadNewVersionThread (URL u, File target)
: ThreadWithProgressWindow ("Downloading New Version", true, true),
result (Result::ok()),
url (u), targetFolder (target)
{
}
static void performDownload (URL u, File targetFolder)
{
DownloadNewVersionThread d (u, targetFolder);
if (d.runThread())
{
if (d.result.failed())
{
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
"Installation Failed",
d.result.getErrorMessage());
}
else
{
new RelaunchTimer (targetFolder);
}
}
}
void run() override
{
setProgress (-1.0);
MemoryBlock zipData;
result = download (zipData);
if (result.wasOk() && ! threadShouldExit())
result = unzip (zipData);
}
Result download (MemoryBlock& dest)
{
setStatusMessage ("Downloading...");
const ScopedPointer<InputStream> in (url.createInputStream (false, nullptr, nullptr, String::empty, 10000));
if (in != nullptr)
{
int64 total = 0;
MemoryOutputStream mo (dest, true);
for (;;)
{
if (threadShouldExit())
return Result::fail ("cancel");
int64 written = mo.writeFromInputStream (*in, 8192);
if (written == 0)
break;
total += written;
setStatusMessage (String (TRANS ("Downloading... (123)"))
.replace ("123", File::descriptionOfSizeInBytes (total)));
}
return Result::ok();
}
return Result::fail ("Failed to download from: " + url.toString (false));
}
Result unzip (const MemoryBlock& data)
{
setStatusMessage ("Installing...");
File unzipTarget;
bool isUsingTempFolder = false;
{
MemoryInputStream input (data, false);
ZipFile zip (input);
if (zip.getNumEntries() == 0)
return Result::fail ("The downloaded file wasn't a valid JUCE file!");
unzipTarget = targetFolder;
if (unzipTarget.exists())
{
isUsingTempFolder = true;
unzipTarget = targetFolder.getNonexistentSibling();
if (! unzipTarget.createDirectory())
return Result::fail ("Couldn't create a folder to unzip the new version!");
}
Result r (zip.uncompressTo (unzipTarget));
if (r.failed())
{
if (isUsingTempFolder)
unzipTarget.deleteRecursively();
return r;
}
}
if (isUsingTempFolder)
{
File oldFolder (targetFolder.getSiblingFile (targetFolder.getFileNameWithoutExtension() + "_old").getNonexistentSibling());
if (! targetFolder.moveFileTo (oldFolder))
{
unzipTarget.deleteRecursively();
return Result::fail ("Could not remove the existing folder!");
}
if (! unzipTarget.moveFileTo (targetFolder))
{
unzipTarget.deleteRecursively();
return Result::fail ("Could not overwrite the existing folder!");
}
}
return Result::ok();
}
Result result;
URL url;
File targetFolder;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DownloadNewVersionThread)
};
struct RelaunchTimer : private Timer
{
RelaunchTimer (const File& f) : parentFolder (f)
{
startTimer (1500);
}
void timerCallback() override
{
stopTimer();
File app = parentFolder.getChildFile (
#if JUCE_MAC
"Introjucer.app");
#elif JUCE_WINDOWS
"Introjucer.exe");
#elif JUCE_LINUX
"Introjucer");
#endif
JUCEApplication::quit();
if (app.exists())
{
app.setExecutePermission (true);
#if JUCE_MAC
app.getChildFile("Contents")
.getChildFile("MacOS")
.getChildFile("Introjucer").setExecutePermission (true);
#endif
app.startAsProcess();
}
delete this;
}
File parentFolder;
};
String newRelativeDownloadPath;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LatestVersionChecker)
};


+ 2
- 0
modules/juce_core/system/juce_StandardHeader.h View File

@@ -114,6 +114,8 @@
#undef TYPE_BOOL
#undef max
#undef min
#undef major
#undef minor
//==============================================================================
// DLL building settings on Windows


Loading…
Cancel
Save