| @@ -100,8 +100,8 @@ OBJECTS_APP := \ | |||
| $(JUCE_OBJDIR)/jucer_CompileEngineClient_aee8c99c.o \ | |||
| $(JUCE_OBJDIR)/jucer_CompileEngineServer_5d8914.o \ | |||
| $(JUCE_OBJDIR)/jucer_DownloadCompileEngineThread_19bb4bb3.o \ | |||
| $(JUCE_OBJDIR)/jucer_Modules_e20cbd10.o \ | |||
| $(JUCE_OBJDIR)/jucer_HeaderComponent_1ebf72ba.o \ | |||
| $(JUCE_OBJDIR)/jucer_Module_3f7666a5.o \ | |||
| $(JUCE_OBJDIR)/jucer_Project_c131864a.o \ | |||
| $(JUCE_OBJDIR)/jucer_ProjectExporter_cf377b25.o \ | |||
| $(JUCE_OBJDIR)/jucer_ProjectSaver_4276639b.o \ | |||
| @@ -302,14 +302,14 @@ $(JUCE_OBJDIR)/jucer_DownloadCompileEngineThread_19bb4bb3.o: ../../Source/LiveBu | |||
| @echo "Compiling jucer_DownloadCompileEngineThread.cpp" | |||
| $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<" | |||
| $(JUCE_OBJDIR)/jucer_HeaderComponent_1ebf72ba.o: ../../Source/Project/UI/jucer_HeaderComponent.cpp | |||
| $(JUCE_OBJDIR)/jucer_Modules_e20cbd10.o: ../../Source/Project/Modules/jucer_Modules.cpp | |||
| -$(V_AT)mkdir -p $(JUCE_OBJDIR) | |||
| @echo "Compiling jucer_HeaderComponent.cpp" | |||
| @echo "Compiling jucer_Modules.cpp" | |||
| $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<" | |||
| $(JUCE_OBJDIR)/jucer_Module_3f7666a5.o: ../../Source/Project/jucer_Module.cpp | |||
| $(JUCE_OBJDIR)/jucer_HeaderComponent_1ebf72ba.o: ../../Source/Project/UI/jucer_HeaderComponent.cpp | |||
| -$(V_AT)mkdir -p $(JUCE_OBJDIR) | |||
| @echo "Compiling jucer_Module.cpp" | |||
| @echo "Compiling jucer_HeaderComponent.cpp" | |||
| $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<" | |||
| $(JUCE_OBJDIR)/jucer_Project_c131864a.o: ../../Source/Project/jucer_Project.cpp | |||
| @@ -205,13 +205,13 @@ | |||
| isa = PBXBuildFile; | |||
| fileRef = ADA538034910F52FDD2DC88D; | |||
| }; | |||
| 05A08E366EBF8D650974E695 = { | |||
| 0E783907C6214ADD59EC95DC = { | |||
| isa = PBXBuildFile; | |||
| fileRef = 516D6D7C564DD5DF5C15CB06; | |||
| fileRef = F58B23995765C9FDBE28F871; | |||
| }; | |||
| 3FCA61C401007B243E2E9035 = { | |||
| 05A08E366EBF8D650974E695 = { | |||
| isa = PBXBuildFile; | |||
| fileRef = F797071D88542C813CF7222A; | |||
| fileRef = 516D6D7C564DD5DF5C15CB06; | |||
| }; | |||
| 30B921C38DCEE787B294B746 = { | |||
| isa = PBXBuildFile; | |||
| @@ -548,13 +548,6 @@ | |||
| path = "../../Source/Utility/PIPs/jucer_PIPGenerator.cpp"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| 194457D806A26E793584AC0C = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = file.svg; | |||
| name = "huckleberry_icon.svg"; | |||
| path = "../../Source/BinaryData/Icons/huckleberry_icon.svg"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| 1B0F18E1D96F727C062B05FA = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = sourcecode.cpp.cpp; | |||
| @@ -660,6 +653,13 @@ | |||
| path = "../../Source/LiveBuildEngine/jucer_ActivityList.h"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| 247768B490B9D759DDA79359 = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = sourcecode.c.h; | |||
| name = "jucer_UserAvatarComponent.h"; | |||
| path = "../../Source/Project/UI/jucer_UserAvatarComponent.h"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| 24EB4C2412821B8019D6F754 = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = sourcecode.cpp.cpp; | |||
| @@ -709,6 +709,13 @@ | |||
| path = "../../Source/Application/jucer_CommandLine.h"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| 2F0A7CA808B2FCCC9ED68992 = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = sourcecode.c.h; | |||
| name = "jucer_LicenseQueryThread.h"; | |||
| path = "../../Source/Application/UserAccount/jucer_LicenseQueryThread.h"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| 2F373F97E30AC1A0BFC1FC61 = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = sourcecode.c.h; | |||
| @@ -1094,6 +1101,13 @@ | |||
| path = "../../Source/ComponentEditor/Properties/jucer_ComponentTextProperty.h"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| 582F659B801F656C2B7C51B1 = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = sourcecode.c.h; | |||
| name = "jucer_Modules.h"; | |||
| path = "../../Source/Project/Modules/jucer_Modules.h"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| 5867DC4E39DF8539B54C0D59 = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = sourcecode.cpp.objcpp; | |||
| @@ -1304,13 +1318,6 @@ | |||
| path = "../../Source/ComponentEditor/UI/jucer_RelativePositionedRectangle.h"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| 7211101FFA28400ADBB1D47A = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = sourcecode.c.h; | |||
| name = "jucer_Module.h"; | |||
| path = "../../Source/Project/jucer_Module.h"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| 728FE25157E9874D50BBECB2 = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = wrapper.framework; | |||
| @@ -1584,6 +1591,13 @@ | |||
| path = "../../Source/Project/UI/jucer_ProjectContentComponent.h"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| 94146B40B41BF0AACF4359DD = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = sourcecode.c.h; | |||
| name = "jucer_LicenseState.h"; | |||
| path = "../../Source/Application/UserAccount/jucer_LicenseState.h"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| 951128CA33CCDEF570436B1C = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = file.icns; | |||
| @@ -1899,6 +1913,13 @@ | |||
| path = "../../Source/Utility/Helpers/jucer_FileHelpers.cpp"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| B6444A4A8DFD6828FF6BD1CB = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = sourcecode.c.h; | |||
| name = "jucer_LoginFormComponent.h"; | |||
| path = "../../Source/Application/UserAccount/jucer_LoginFormComponent.h"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| B6F2905330EA5C560D527209 = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = file; | |||
| @@ -2102,6 +2123,13 @@ | |||
| path = "../../Source/LiveBuildEngine/UI/jucer_BuildTabStatusComponent.h"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| CD267A28C16C4E79EB749005 = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = file.svg; | |||
| name = "gpl_logo.svg"; | |||
| path = "../../Source/BinaryData/Icons/gpl_logo.svg"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| CF6C8BD0DA3D8CD4E99EBADA = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = wrapper.framework; | |||
| @@ -2438,6 +2466,13 @@ | |||
| path = "../../Source/Utility/Helpers/jucer_CodeHelpers.cpp"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| F30DF63DBEFA4BEEF7C369FC = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = sourcecode.c.h; | |||
| name = "jucer_LicenseController.h"; | |||
| path = "../../Source/Application/UserAccount/jucer_LicenseController.h"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| F313EE01ECE306DB2CFE011D = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = file.in; | |||
| @@ -2459,6 +2494,13 @@ | |||
| path = "../../Source/ComponentEditor/Properties/jucer_ComponentBooleanProperty.h"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| F58B23995765C9FDBE28F871 = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = sourcecode.cpp.cpp; | |||
| name = "jucer_Modules.cpp"; | |||
| path = "../../Source/Project/Modules/jucer_Modules.cpp"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| F5DD97B45B8EA60C1ED0DD80 = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = sourcecode.cpp.cpp; | |||
| @@ -2466,11 +2508,11 @@ | |||
| path = "../../Source/Settings/jucer_StoredSettings.cpp"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| F797071D88542C813CF7222A = { | |||
| F63F46CA0A51C679867855A7 = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = sourcecode.cpp.cpp; | |||
| name = "jucer_Module.cpp"; | |||
| path = "../../Source/Project/jucer_Module.cpp"; | |||
| lastKnownFileType = sourcecode.c.h; | |||
| name = "jucer_ProjectMessagesComponent.h"; | |||
| path = "../../Source/Project/UI/jucer_ProjectMessagesComponent.h"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| F7C74E934C954F6F1A3BE4F9 = { | |||
| @@ -2501,6 +2543,13 @@ | |||
| path = "../../Source/CodeEditor/jucer_OpenDocumentManager.cpp"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| F9A363BFBB6B1143C2E967C3 = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = sourcecode.c.h; | |||
| name = "jucer_ModuleDescription.h"; | |||
| path = "../../Source/Project/Modules/jucer_ModuleDescription.h"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| FA790C59A304579F660F112F = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = sourcecode.c.h; | |||
| @@ -2522,6 +2571,13 @@ | |||
| path = "../../Source/ComponentEditor/Properties/jucer_ComponentColourProperty.h"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| FDABEE6B64546586368A4729 = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = sourcecode.c.h; | |||
| name = "jucer_AvailableModulesList.h"; | |||
| path = "../../Source/Project/Modules/jucer_AvailableModulesList.h"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| FE20FE5805A02A4843048200 = { | |||
| isa = PBXFileReference; | |||
| lastKnownFileType = sourcecode.c.h; | |||
| @@ -2550,6 +2606,17 @@ | |||
| path = "../../Source/ComponentEditor/UI/jucer_PaintRoutinePanel.cpp"; | |||
| sourceTree = "SOURCE_ROOT"; | |||
| }; | |||
| 9D43579A76E23FBCE6B36333 = { | |||
| isa = PBXGroup; | |||
| children = ( | |||
| F30DF63DBEFA4BEEF7C369FC, | |||
| 2F0A7CA808B2FCCC9ED68992, | |||
| 94146B40B41BF0AACF4359DD, | |||
| B6444A4A8DFD6828FF6BD1CB, | |||
| ); | |||
| name = UserAccount; | |||
| sourceTree = "<group>"; | |||
| }; | |||
| EB1D55A76652399EB81CC1F0 = { | |||
| isa = PBXGroup; | |||
| children = ( | |||
| @@ -2568,6 +2635,7 @@ | |||
| BC67FD952A6F210A11A1ECB8 = { | |||
| isa = PBXGroup; | |||
| children = ( | |||
| 9D43579A76E23FBCE6B36333, | |||
| EB1D55A76652399EB81CC1F0, | |||
| 7CA44FF0BA319517C6E39651, | |||
| EE690110171E1648FF2118B8, | |||
| @@ -2606,7 +2674,7 @@ | |||
| 69B478C992FA0B8C885946A6, | |||
| EAC1731150A7F79D59BAA0B6, | |||
| 8F4D281E98808204E2846A7D, | |||
| 194457D806A26E793584AC0C, | |||
| CD267A28C16C4E79EB749005, | |||
| 432EC251A122071809471804, | |||
| B83C9BD89F31EA9E5E12A3C6, | |||
| 8FEF6F5EA676B824C021EB6F, | |||
| @@ -2865,6 +2933,17 @@ | |||
| name = LiveBuildEngine; | |||
| sourceTree = "<group>"; | |||
| }; | |||
| 5108FDF7F62E617332FB13B0 = { | |||
| isa = PBXGroup; | |||
| children = ( | |||
| FDABEE6B64546586368A4729, | |||
| F9A363BFBB6B1143C2E967C3, | |||
| F58B23995765C9FDBE28F871, | |||
| 582F659B801F656C2B7C51B1, | |||
| ); | |||
| name = Modules; | |||
| sourceTree = "<group>"; | |||
| }; | |||
| 236D186F5A6536C59D6E751C = { | |||
| isa = PBXGroup; | |||
| children = ( | |||
| @@ -2891,6 +2970,8 @@ | |||
| B3528C08B84CBC950252EA69, | |||
| 1B0F18E1D96F727C062B05FA, | |||
| 92A66A8BD87F98EB6B4FB6D0, | |||
| F63F46CA0A51C679867855A7, | |||
| 247768B490B9D759DDA79359, | |||
| ); | |||
| name = UI; | |||
| sourceTree = "<group>"; | |||
| @@ -2898,9 +2979,8 @@ | |||
| 89E9055A179B4C2019B4E1AE = { | |||
| isa = PBXGroup; | |||
| children = ( | |||
| 5108FDF7F62E617332FB13B0, | |||
| EBC037ECAAC8156B8B19DC69, | |||
| F797071D88542C813CF7222A, | |||
| 7211101FFA28400ADBB1D47A, | |||
| BAC43B20E14A340CCF14119C, | |||
| BF3CEF080FA013E2778DCE90, | |||
| ); | |||
| @@ -3424,8 +3504,8 @@ | |||
| D25EBE02B55DB244BE0D5635, | |||
| 85E7FCB0516EFF853FA7B380, | |||
| CC6C4D351BA9B473E5F95791, | |||
| 0E783907C6214ADD59EC95DC, | |||
| 05A08E366EBF8D650974E695, | |||
| 3FCA61C401007B243E2E9035, | |||
| 30B921C38DCEE787B294B746, | |||
| 244567D3AE2E417A8CB2B95E, | |||
| 26D6AEA321E80ABCC3CCCCD1, | |||
| @@ -214,11 +214,11 @@ | |||
| <ClCompile Include="..\..\Source\LiveBuildEngine\jucer_CompileEngineClient.cpp"/> | |||
| <ClCompile Include="..\..\Source\LiveBuildEngine\jucer_CompileEngineServer.cpp"/> | |||
| <ClCompile Include="..\..\Source\LiveBuildEngine\jucer_DownloadCompileEngineThread.cpp"/> | |||
| <ClCompile Include="..\..\Source\Project\Modules\jucer_Modules.cpp"/> | |||
| <ClCompile Include="..\..\Source\Project\UI\jucer_HeaderComponent.cpp"/> | |||
| <ClCompile Include="..\..\Source\Project\UI\jucer_ProjectContentComponent.cpp"> | |||
| <ExcludedFromBuild>true</ExcludedFromBuild> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Project\jucer_Module.cpp"/> | |||
| <ClCompile Include="..\..\Source\Project\jucer_Project.cpp"/> | |||
| <ClCompile Include="..\..\Source\ProjectSaving\jucer_ProjectExporter.cpp"/> | |||
| <ClCompile Include="..\..\Source\ProjectSaving\jucer_ProjectSaver.cpp"/> | |||
| @@ -1483,6 +1483,10 @@ | |||
| <ClCompile Include="..\..\JuceLibraryCode\include_juce_gui_extra.cpp"/> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LicenseController.h"/> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LicenseQueryThread.h"/> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LicenseState.h"/> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LoginFormComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Application\Windows\jucer_AboutWindowComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Application\Windows\jucer_EditorColourSchemeWindowComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Application\Windows\jucer_FloatingToolWindow.h"/> | |||
| @@ -1603,6 +1607,9 @@ | |||
| <ClInclude Include="..\..\Source\LiveBuildEngine\jucer_MessageIDs.h"/> | |||
| <ClInclude Include="..\..\Source\LiveBuildEngine\jucer_ProjectBuildInfo.h"/> | |||
| <ClInclude Include="..\..\Source\LiveBuildEngine\jucer_SourceCodeRange.h"/> | |||
| <ClInclude Include="..\..\Source\Project\Modules\jucer_AvailableModulesList.h"/> | |||
| <ClInclude Include="..\..\Source\Project\Modules\jucer_ModuleDescription.h"/> | |||
| <ClInclude Include="..\..\Source\Project\Modules\jucer_Modules.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\Sidebar\jucer_ExporterTreeItems.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\Sidebar\jucer_FileTreeItems.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\Sidebar\jucer_LiveBuildTab.h"/> | |||
| @@ -1616,7 +1623,8 @@ | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_HeaderComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_ModulesInformationComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_ProjectContentComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Project\jucer_Module.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_ProjectMessagesComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_UserAvatarComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Project\jucer_Project.h"/> | |||
| <ClInclude Include="..\..\Source\ProjectSaving\jucer_ProjectExport_Android.h"/> | |||
| <ClInclude Include="..\..\Source\ProjectSaving\jucer_ProjectExport_CLion.h"/> | |||
| @@ -2105,7 +2113,7 @@ | |||
| <None Include="..\..\Source\BinaryData\Icons\export_linux.svg"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\export_visualStudio.svg"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\export_xcode.svg"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\huckleberry_icon.svg"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\gpl_logo.svg"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\juce-logo-with-text.svg"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\juce_icon.png"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\wizard_AnimatedApp.svg"/> | |||
| @@ -2,6 +2,9 @@ | |||
| <Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
| <ItemGroup> | |||
| <Filter Include="Projucer\Application\UserAccount"> | |||
| <UniqueIdentifier>{DA27985D-8427-CE70-CA06-EAF7009CCC60}</UniqueIdentifier> | |||
| </Filter> | |||
| <Filter Include="Projucer\Application\Windows"> | |||
| <UniqueIdentifier>{DC7E18A5-E854-3D99-627F-AAA88246B712}</UniqueIdentifier> | |||
| </Filter> | |||
| @@ -47,6 +50,9 @@ | |||
| <Filter Include="Projucer\LiveBuildEngine"> | |||
| <UniqueIdentifier>{0A3B9446-F50B-3D4E-230F-7ED493541A07}</UniqueIdentifier> | |||
| </Filter> | |||
| <Filter Include="Projucer\Project\Modules"> | |||
| <UniqueIdentifier>{F5C79836-30DE-9DC7-9392-DAAB3F04C18E}</UniqueIdentifier> | |||
| </Filter> | |||
| <Filter Include="Projucer\Project\UI\Sidebar"> | |||
| <UniqueIdentifier>{A0A94AE6-B447-151A-D0DA-FAE9B5410EBF}</UniqueIdentifier> | |||
| </Filter> | |||
| @@ -448,15 +454,15 @@ | |||
| <ClCompile Include="..\..\Source\LiveBuildEngine\jucer_DownloadCompileEngineThread.cpp"> | |||
| <Filter>Projucer\LiveBuildEngine</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Project\Modules\jucer_Modules.cpp"> | |||
| <Filter>Projucer\Project\Modules</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Project\UI\jucer_HeaderComponent.cpp"> | |||
| <Filter>Projucer\Project\UI</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Project\UI\jucer_ProjectContentComponent.cpp"> | |||
| <Filter>Projucer\Project\UI</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Project\jucer_Module.cpp"> | |||
| <Filter>Projucer\Project</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Project\jucer_Project.cpp"> | |||
| <Filter>Projucer\Project</Filter> | |||
| </ClCompile> | |||
| @@ -1857,6 +1863,18 @@ | |||
| </ClCompile> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LicenseController.h"> | |||
| <Filter>Projucer\Application\UserAccount</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LicenseQueryThread.h"> | |||
| <Filter>Projucer\Application\UserAccount</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LicenseState.h"> | |||
| <Filter>Projucer\Application\UserAccount</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LoginFormComponent.h"> | |||
| <Filter>Projucer\Application\UserAccount</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Application\Windows\jucer_AboutWindowComponent.h"> | |||
| <Filter>Projucer\Application\Windows</Filter> | |||
| </ClInclude> | |||
| @@ -2217,6 +2235,15 @@ | |||
| <ClInclude Include="..\..\Source\LiveBuildEngine\jucer_SourceCodeRange.h"> | |||
| <Filter>Projucer\LiveBuildEngine</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\Modules\jucer_AvailableModulesList.h"> | |||
| <Filter>Projucer\Project\Modules</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\Modules\jucer_ModuleDescription.h"> | |||
| <Filter>Projucer\Project\Modules</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\Modules\jucer_Modules.h"> | |||
| <Filter>Projucer\Project\Modules</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\UI\Sidebar\jucer_ExporterTreeItems.h"> | |||
| <Filter>Projucer\Project\UI\Sidebar</Filter> | |||
| </ClInclude> | |||
| @@ -2256,8 +2283,11 @@ | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_ProjectContentComponent.h"> | |||
| <Filter>Projucer\Project\UI</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\jucer_Module.h"> | |||
| <Filter>Projucer\Project</Filter> | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_ProjectMessagesComponent.h"> | |||
| <Filter>Projucer\Project\UI</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_UserAvatarComponent.h"> | |||
| <Filter>Projucer\Project\UI</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\jucer_Project.h"> | |||
| <Filter>Projucer\Project</Filter> | |||
| @@ -3719,7 +3749,7 @@ | |||
| <None Include="..\..\Source\BinaryData\Icons\export_xcode.svg"> | |||
| <Filter>Projucer\BinaryData\Icons</Filter> | |||
| </None> | |||
| <None Include="..\..\Source\BinaryData\Icons\huckleberry_icon.svg"> | |||
| <None Include="..\..\Source\BinaryData\Icons\gpl_logo.svg"> | |||
| <Filter>Projucer\BinaryData\Icons</Filter> | |||
| </None> | |||
| <None Include="..\..\Source\BinaryData\Icons\juce-logo-with-text.svg"> | |||
| @@ -214,11 +214,11 @@ | |||
| <ClCompile Include="..\..\Source\LiveBuildEngine\jucer_CompileEngineClient.cpp"/> | |||
| <ClCompile Include="..\..\Source\LiveBuildEngine\jucer_CompileEngineServer.cpp"/> | |||
| <ClCompile Include="..\..\Source\LiveBuildEngine\jucer_DownloadCompileEngineThread.cpp"/> | |||
| <ClCompile Include="..\..\Source\Project\Modules\jucer_Modules.cpp"/> | |||
| <ClCompile Include="..\..\Source\Project\UI\jucer_HeaderComponent.cpp"/> | |||
| <ClCompile Include="..\..\Source\Project\UI\jucer_ProjectContentComponent.cpp"> | |||
| <ExcludedFromBuild>true</ExcludedFromBuild> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Project\jucer_Module.cpp"/> | |||
| <ClCompile Include="..\..\Source\Project\jucer_Project.cpp"/> | |||
| <ClCompile Include="..\..\Source\ProjectSaving\jucer_ProjectExporter.cpp"/> | |||
| <ClCompile Include="..\..\Source\ProjectSaving\jucer_ProjectSaver.cpp"/> | |||
| @@ -1483,6 +1483,10 @@ | |||
| <ClCompile Include="..\..\JuceLibraryCode\include_juce_gui_extra.cpp"/> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LicenseController.h"/> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LicenseQueryThread.h"/> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LicenseState.h"/> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LoginFormComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Application\Windows\jucer_AboutWindowComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Application\Windows\jucer_EditorColourSchemeWindowComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Application\Windows\jucer_FloatingToolWindow.h"/> | |||
| @@ -1603,6 +1607,9 @@ | |||
| <ClInclude Include="..\..\Source\LiveBuildEngine\jucer_MessageIDs.h"/> | |||
| <ClInclude Include="..\..\Source\LiveBuildEngine\jucer_ProjectBuildInfo.h"/> | |||
| <ClInclude Include="..\..\Source\LiveBuildEngine\jucer_SourceCodeRange.h"/> | |||
| <ClInclude Include="..\..\Source\Project\Modules\jucer_AvailableModulesList.h"/> | |||
| <ClInclude Include="..\..\Source\Project\Modules\jucer_ModuleDescription.h"/> | |||
| <ClInclude Include="..\..\Source\Project\Modules\jucer_Modules.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\Sidebar\jucer_ExporterTreeItems.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\Sidebar\jucer_FileTreeItems.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\Sidebar\jucer_LiveBuildTab.h"/> | |||
| @@ -1616,7 +1623,8 @@ | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_HeaderComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_ModulesInformationComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_ProjectContentComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Project\jucer_Module.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_ProjectMessagesComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_UserAvatarComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Project\jucer_Project.h"/> | |||
| <ClInclude Include="..\..\Source\ProjectSaving\jucer_ProjectExport_Android.h"/> | |||
| <ClInclude Include="..\..\Source\ProjectSaving\jucer_ProjectExport_CLion.h"/> | |||
| @@ -2105,7 +2113,7 @@ | |||
| <None Include="..\..\Source\BinaryData\Icons\export_linux.svg"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\export_visualStudio.svg"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\export_xcode.svg"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\huckleberry_icon.svg"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\gpl_logo.svg"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\juce-logo-with-text.svg"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\juce_icon.png"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\wizard_AnimatedApp.svg"/> | |||
| @@ -2,6 +2,9 @@ | |||
| <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
| <ItemGroup> | |||
| <Filter Include="Projucer\Application\UserAccount"> | |||
| <UniqueIdentifier>{DA27985D-8427-CE70-CA06-EAF7009CCC60}</UniqueIdentifier> | |||
| </Filter> | |||
| <Filter Include="Projucer\Application\Windows"> | |||
| <UniqueIdentifier>{DC7E18A5-E854-3D99-627F-AAA88246B712}</UniqueIdentifier> | |||
| </Filter> | |||
| @@ -47,6 +50,9 @@ | |||
| <Filter Include="Projucer\LiveBuildEngine"> | |||
| <UniqueIdentifier>{0A3B9446-F50B-3D4E-230F-7ED493541A07}</UniqueIdentifier> | |||
| </Filter> | |||
| <Filter Include="Projucer\Project\Modules"> | |||
| <UniqueIdentifier>{F5C79836-30DE-9DC7-9392-DAAB3F04C18E}</UniqueIdentifier> | |||
| </Filter> | |||
| <Filter Include="Projucer\Project\UI\Sidebar"> | |||
| <UniqueIdentifier>{A0A94AE6-B447-151A-D0DA-FAE9B5410EBF}</UniqueIdentifier> | |||
| </Filter> | |||
| @@ -448,15 +454,15 @@ | |||
| <ClCompile Include="..\..\Source\LiveBuildEngine\jucer_DownloadCompileEngineThread.cpp"> | |||
| <Filter>Projucer\LiveBuildEngine</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Project\Modules\jucer_Modules.cpp"> | |||
| <Filter>Projucer\Project\Modules</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Project\UI\jucer_HeaderComponent.cpp"> | |||
| <Filter>Projucer\Project\UI</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Project\UI\jucer_ProjectContentComponent.cpp"> | |||
| <Filter>Projucer\Project\UI</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Project\jucer_Module.cpp"> | |||
| <Filter>Projucer\Project</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Project\jucer_Project.cpp"> | |||
| <Filter>Projucer\Project</Filter> | |||
| </ClCompile> | |||
| @@ -1857,6 +1863,18 @@ | |||
| </ClCompile> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LicenseController.h"> | |||
| <Filter>Projucer\Application\UserAccount</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LicenseQueryThread.h"> | |||
| <Filter>Projucer\Application\UserAccount</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LicenseState.h"> | |||
| <Filter>Projucer\Application\UserAccount</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LoginFormComponent.h"> | |||
| <Filter>Projucer\Application\UserAccount</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Application\Windows\jucer_AboutWindowComponent.h"> | |||
| <Filter>Projucer\Application\Windows</Filter> | |||
| </ClInclude> | |||
| @@ -2217,6 +2235,15 @@ | |||
| <ClInclude Include="..\..\Source\LiveBuildEngine\jucer_SourceCodeRange.h"> | |||
| <Filter>Projucer\LiveBuildEngine</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\Modules\jucer_AvailableModulesList.h"> | |||
| <Filter>Projucer\Project\Modules</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\Modules\jucer_ModuleDescription.h"> | |||
| <Filter>Projucer\Project\Modules</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\Modules\jucer_Modules.h"> | |||
| <Filter>Projucer\Project\Modules</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\UI\Sidebar\jucer_ExporterTreeItems.h"> | |||
| <Filter>Projucer\Project\UI\Sidebar</Filter> | |||
| </ClInclude> | |||
| @@ -2256,8 +2283,11 @@ | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_ProjectContentComponent.h"> | |||
| <Filter>Projucer\Project\UI</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\jucer_Module.h"> | |||
| <Filter>Projucer\Project</Filter> | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_ProjectMessagesComponent.h"> | |||
| <Filter>Projucer\Project\UI</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_UserAvatarComponent.h"> | |||
| <Filter>Projucer\Project\UI</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\jucer_Project.h"> | |||
| <Filter>Projucer\Project</Filter> | |||
| @@ -3719,7 +3749,7 @@ | |||
| <None Include="..\..\Source\BinaryData\Icons\export_xcode.svg"> | |||
| <Filter>Projucer\BinaryData\Icons</Filter> | |||
| </None> | |||
| <None Include="..\..\Source\BinaryData\Icons\huckleberry_icon.svg"> | |||
| <None Include="..\..\Source\BinaryData\Icons\gpl_logo.svg"> | |||
| <Filter>Projucer\BinaryData\Icons</Filter> | |||
| </None> | |||
| <None Include="..\..\Source\BinaryData\Icons\juce-logo-with-text.svg"> | |||
| @@ -214,11 +214,11 @@ | |||
| <ClCompile Include="..\..\Source\LiveBuildEngine\jucer_CompileEngineClient.cpp"/> | |||
| <ClCompile Include="..\..\Source\LiveBuildEngine\jucer_CompileEngineServer.cpp"/> | |||
| <ClCompile Include="..\..\Source\LiveBuildEngine\jucer_DownloadCompileEngineThread.cpp"/> | |||
| <ClCompile Include="..\..\Source\Project\Modules\jucer_Modules.cpp"/> | |||
| <ClCompile Include="..\..\Source\Project\UI\jucer_HeaderComponent.cpp"/> | |||
| <ClCompile Include="..\..\Source\Project\UI\jucer_ProjectContentComponent.cpp"> | |||
| <ExcludedFromBuild>true</ExcludedFromBuild> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Project\jucer_Module.cpp"/> | |||
| <ClCompile Include="..\..\Source\Project\jucer_Project.cpp"/> | |||
| <ClCompile Include="..\..\Source\ProjectSaving\jucer_ProjectExporter.cpp"/> | |||
| <ClCompile Include="..\..\Source\ProjectSaving\jucer_ProjectSaver.cpp"/> | |||
| @@ -1483,6 +1483,10 @@ | |||
| <ClCompile Include="..\..\JuceLibraryCode\include_juce_gui_extra.cpp"/> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LicenseController.h"/> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LicenseQueryThread.h"/> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LicenseState.h"/> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LoginFormComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Application\Windows\jucer_AboutWindowComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Application\Windows\jucer_EditorColourSchemeWindowComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Application\Windows\jucer_FloatingToolWindow.h"/> | |||
| @@ -1603,6 +1607,9 @@ | |||
| <ClInclude Include="..\..\Source\LiveBuildEngine\jucer_MessageIDs.h"/> | |||
| <ClInclude Include="..\..\Source\LiveBuildEngine\jucer_ProjectBuildInfo.h"/> | |||
| <ClInclude Include="..\..\Source\LiveBuildEngine\jucer_SourceCodeRange.h"/> | |||
| <ClInclude Include="..\..\Source\Project\Modules\jucer_AvailableModulesList.h"/> | |||
| <ClInclude Include="..\..\Source\Project\Modules\jucer_ModuleDescription.h"/> | |||
| <ClInclude Include="..\..\Source\Project\Modules\jucer_Modules.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\Sidebar\jucer_ExporterTreeItems.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\Sidebar\jucer_FileTreeItems.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\Sidebar\jucer_LiveBuildTab.h"/> | |||
| @@ -1616,7 +1623,8 @@ | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_HeaderComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_ModulesInformationComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_ProjectContentComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Project\jucer_Module.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_ProjectMessagesComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_UserAvatarComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Project\jucer_Project.h"/> | |||
| <ClInclude Include="..\..\Source\ProjectSaving\jucer_ProjectExport_Android.h"/> | |||
| <ClInclude Include="..\..\Source\ProjectSaving\jucer_ProjectExport_CLion.h"/> | |||
| @@ -2105,7 +2113,7 @@ | |||
| <None Include="..\..\Source\BinaryData\Icons\export_linux.svg"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\export_visualStudio.svg"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\export_xcode.svg"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\huckleberry_icon.svg"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\gpl_logo.svg"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\juce-logo-with-text.svg"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\juce_icon.png"/> | |||
| <None Include="..\..\Source\BinaryData\Icons\wizard_AnimatedApp.svg"/> | |||
| @@ -2,6 +2,9 @@ | |||
| <Project ToolsVersion="16.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
| <ItemGroup> | |||
| <Filter Include="Projucer\Application\UserAccount"> | |||
| <UniqueIdentifier>{DA27985D-8427-CE70-CA06-EAF7009CCC60}</UniqueIdentifier> | |||
| </Filter> | |||
| <Filter Include="Projucer\Application\Windows"> | |||
| <UniqueIdentifier>{DC7E18A5-E854-3D99-627F-AAA88246B712}</UniqueIdentifier> | |||
| </Filter> | |||
| @@ -47,6 +50,9 @@ | |||
| <Filter Include="Projucer\LiveBuildEngine"> | |||
| <UniqueIdentifier>{0A3B9446-F50B-3D4E-230F-7ED493541A07}</UniqueIdentifier> | |||
| </Filter> | |||
| <Filter Include="Projucer\Project\Modules"> | |||
| <UniqueIdentifier>{F5C79836-30DE-9DC7-9392-DAAB3F04C18E}</UniqueIdentifier> | |||
| </Filter> | |||
| <Filter Include="Projucer\Project\UI\Sidebar"> | |||
| <UniqueIdentifier>{A0A94AE6-B447-151A-D0DA-FAE9B5410EBF}</UniqueIdentifier> | |||
| </Filter> | |||
| @@ -448,15 +454,15 @@ | |||
| <ClCompile Include="..\..\Source\LiveBuildEngine\jucer_DownloadCompileEngineThread.cpp"> | |||
| <Filter>Projucer\LiveBuildEngine</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Project\Modules\jucer_Modules.cpp"> | |||
| <Filter>Projucer\Project\Modules</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Project\UI\jucer_HeaderComponent.cpp"> | |||
| <Filter>Projucer\Project\UI</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Project\UI\jucer_ProjectContentComponent.cpp"> | |||
| <Filter>Projucer\Project\UI</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Project\jucer_Module.cpp"> | |||
| <Filter>Projucer\Project</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Project\jucer_Project.cpp"> | |||
| <Filter>Projucer\Project</Filter> | |||
| </ClCompile> | |||
| @@ -1857,6 +1863,18 @@ | |||
| </ClCompile> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LicenseController.h"> | |||
| <Filter>Projucer\Application\UserAccount</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LicenseQueryThread.h"> | |||
| <Filter>Projucer\Application\UserAccount</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LicenseState.h"> | |||
| <Filter>Projucer\Application\UserAccount</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Application\UserAccount\jucer_LoginFormComponent.h"> | |||
| <Filter>Projucer\Application\UserAccount</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Application\Windows\jucer_AboutWindowComponent.h"> | |||
| <Filter>Projucer\Application\Windows</Filter> | |||
| </ClInclude> | |||
| @@ -2217,6 +2235,15 @@ | |||
| <ClInclude Include="..\..\Source\LiveBuildEngine\jucer_SourceCodeRange.h"> | |||
| <Filter>Projucer\LiveBuildEngine</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\Modules\jucer_AvailableModulesList.h"> | |||
| <Filter>Projucer\Project\Modules</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\Modules\jucer_ModuleDescription.h"> | |||
| <Filter>Projucer\Project\Modules</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\Modules\jucer_Modules.h"> | |||
| <Filter>Projucer\Project\Modules</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\UI\Sidebar\jucer_ExporterTreeItems.h"> | |||
| <Filter>Projucer\Project\UI\Sidebar</Filter> | |||
| </ClInclude> | |||
| @@ -2256,8 +2283,11 @@ | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_ProjectContentComponent.h"> | |||
| <Filter>Projucer\Project\UI</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\jucer_Module.h"> | |||
| <Filter>Projucer\Project</Filter> | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_ProjectMessagesComponent.h"> | |||
| <Filter>Projucer\Project\UI</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\UI\jucer_UserAvatarComponent.h"> | |||
| <Filter>Projucer\Project\UI</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Project\jucer_Project.h"> | |||
| <Filter>Projucer\Project</Filter> | |||
| @@ -3719,7 +3749,7 @@ | |||
| <None Include="..\..\Source\BinaryData\Icons\export_xcode.svg"> | |||
| <Filter>Projucer\BinaryData\Icons</Filter> | |||
| </None> | |||
| <None Include="..\..\Source\BinaryData\Icons\huckleberry_icon.svg"> | |||
| <None Include="..\..\Source\BinaryData\Icons\gpl_logo.svg"> | |||
| <Filter>Projucer\BinaryData\Icons</Filter> | |||
| </None> | |||
| <None Include="..\..\Source\BinaryData\Icons\juce-logo-with-text.svg"> | |||
| @@ -57,12 +57,11 @@ target_sources(Projucer PRIVATE | |||
| Source/ComponentEditor/jucer_JucerDocument.cpp | |||
| Source/ComponentEditor/jucer_ObjectTypes.cpp | |||
| Source/ComponentEditor/jucer_PaintRoutine.cpp | |||
| Source/Licenses/jucer_LicenseController.cpp | |||
| Source/LiveBuildEngine/jucer_CompileEngineClient.cpp | |||
| Source/LiveBuildEngine/jucer_CompileEngineServer.cpp | |||
| Source/LiveBuildEngine/jucer_DownloadCompileEngineThread.cpp | |||
| Source/Project/Modules/jucer_Modules.cpp | |||
| Source/Project/UI/jucer_HeaderComponent.cpp | |||
| Source/Project/jucer_Module.cpp | |||
| Source/Project/jucer_Project.cpp | |||
| Source/ProjectSaving/jucer_ProjectExporter.cpp | |||
| Source/ProjectSaving/jucer_ProjectSaver.cpp | |||
| @@ -97,7 +96,7 @@ juce_add_binary_data(ProjucerData SOURCES | |||
| Source/BinaryData/Icons/export_linux.svg | |||
| Source/BinaryData/Icons/export_visualStudio.svg | |||
| Source/BinaryData/Icons/export_xcode.svg | |||
| Source/BinaryData/Icons/huckleberry_icon.svg | |||
| Source/BinaryData/Icons/gpl_logo.svg | |||
| Source/BinaryData/Icons/juce-logo-with-text.svg | |||
| Source/BinaryData/Icons/juce_icon.png | |||
| Source/BinaryData/Icons/wizard_AnimatedApp.svg | |||
| @@ -1981,60 +1981,131 @@ static const unsigned char temp_binary_data_16[] = | |||
| const char* export_xcode_svg = (const char*) temp_binary_data_16; | |||
| //================== huckleberry_icon.svg ================== | |||
| //================== gpl_logo.svg ================== | |||
| static const unsigned char temp_binary_data_17[] = | |||
| "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" | |||
| "<!-- Generator: Adobe Illustrator 21.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->\n" | |||
| "<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n" | |||
| "\t viewBox=\"0 0 169.7 205.2\" style=\"enable-background:new 0 0 169.7 205.2;\" xml:space=\"preserve\">\n" | |||
| "<style type=\"text/css\">\n" | |||
| "\t.st0{fill:#A65A95;}\n" | |||
| "\t.st1{fill:#001946;}\n" | |||
| "</style>\n" | |||
| "<g>\n" | |||
| "\t<g>\n" | |||
| "\t\t<path class=\"st0\" d=\"M45.2,167.8c-3,0-5.4-1.1-7.8-4.3l3.6-3.1c1.6,2.1,2.7,2.7,4.2,2.7c2.6,0,4.4-2,4.4-5v-17.2h5V158\n" | |||
| "\t\t\tC54.6,163.9,50.7,167.8,45.2,167.8z\"/>\n" | |||
| "\t\t<path class=\"st0\" d=\"M70.7,167.8c-5.8,0-10.8-4.2-10.8-11.2v-15.8h5v15.6c0,4.1,2.4,6.7,5.8,6.7c3.4,0,5.8-2.6,5.8-6.7v-15.6h5\n" | |||
| "\t\t\tv15.8C81.5,163.5,76.5,167.8,70.7,167.8z\"/>\n" | |||
| "\t\t<path class=\"st0\" d=\"M99,167.8c-7.6,0-13.9-6.1-13.9-13.6c0-7.6,6.3-13.6,13.9-13.6c3.4,0,6.3,1.2,9.1,3.4l-2.9,3.6\n" | |||
| "\t\t\tc-2.6-1.8-4.1-2.4-6.2-2.4c-4.8,0-8.8,3.9-8.8,9c0,5,3.9,9,8.8,9c2,0,3.6-0.6,6.1-2.4l3,3.7C104.6,167,102,167.8,99,167.8z\"/>\n" | |||
| "\t\t<path class=\"st0\" d=\"M111.3,167.4v-26.6h16.6v4.5h-11.6v6.4h11.2v4.5h-11.2v6.7h11.6v4.5H111.3z\"/>\n" | |||
| "\t</g>\n" | |||
| "\t<g>\n" | |||
| "\t\t<circle class=\"st1\" cx=\"84.9\" cy=\"74.9\" r=\"37.4\"/>\n" | |||
| "\t\t<circle class=\"st0\" cx=\"84.9\" cy=\"74.9\" r=\"28\"/>\n" | |||
| "\t\t<circle class=\"st1\" cx=\"84.9\" cy=\"67.9\" r=\"2.1\"/>\n" | |||
| "\t\t<circle class=\"st1\" cx=\"91.4\" cy=\"72.6\" r=\"2.1\"/>\n" | |||
| "\t\t<circle class=\"st1\" cx=\"88.9\" cy=\"80.3\" r=\"2.1\"/>\n" | |||
| "\t\t<circle class=\"st1\" cx=\"80.8\" cy=\"80.3\" r=\"2.1\"/>\n" | |||
| "\t\t<circle class=\"st1\" cx=\"78.3\" cy=\"72.6\" r=\"2.1\"/>\n" | |||
| "\t</g>\n" | |||
| "\t<g>\n" | |||
| "\t\t<path class=\"st1\" d=\"M48.2,131.7v-4.6h-4.3v4.6h-2V121h2v4.3h4.3V121h2v10.8H48.2z\"/>\n" | |||
| "\t\t<path class=\"st1\" d=\"M56.7,131.7v-0.5c-0.5,0.4-1.3,0.7-2.1,0.7c-1.9,0-3.2-1.3-3.2-3.4v-5h2v4.8c0,1.2,0.7,1.8,1.7,1.8\n" | |||
| "\t\t\tc1,0,1.7-0.7,1.7-1.8v-4.8h2v8.3H56.7z\"/>\n" | |||
| "\t\t<path class=\"st1\" d=\"M63.8,131.9c-2.4,0-4.3-1.9-4.3-4.3c0-2.4,1.9-4.3,4.3-4.3c1.1,0,2.1,0.4,3.3,1.5l-1.3,1.2\n" | |||
| "\t\t\tc-0.7-0.7-1.3-1-2-1c-1.3,0-2.3,1.1-2.3,2.5c0,1.3,1,2.5,2.3,2.5c0.6,0,1.3-0.2,2-0.9l1.3,1.2C66,131.5,65,131.9,63.8,131.9z\"/>\n" | |||
| "\t\t<path class=\"st1\" d=\"M73.8,131.7l-3.9-3.9v3.9h-2v-11.3h2v6.8l3.6-3.7h2.5l-3.9,4l4.3,4.3H73.8z\"/>\n" | |||
| "\t\t<path class=\"st1\" d=\"M77.1,131.7v-11.3h2v11.3H77.1z\"/>\n" | |||
| "\t\t<path class=\"st1\" d=\"M88.6,128.4h-6.2c0.1,1,1.1,1.7,2.1,1.7c0.7,0,1.5-0.3,2.3-1.1l1.3,1.2c-1.1,1.2-2.3,1.7-3.6,1.7\n" | |||
| "\t\t\tc-2.4,0-4.3-1.9-4.3-4.3c0-2.4,1.9-4.3,4.3-4.3c2.4,0,4.1,1.9,4.1,4.2C88.7,127.9,88.6,128.4,88.6,128.4z M84.5,125.1\n" | |||
| "\t\t\tc-1,0-1.9,0.6-2.1,1.5h4.1C86.3,125.8,85.5,125.1,84.5,125.1z\"/>\n" | |||
| "\t\t<path class=\"st1\" d=\"M93.9,131.9c-0.9,0-1.7-0.3-2.3-0.8v0.6h-2v-11.3h2v3.7c0.6-0.5,1.4-0.8,2.3-0.8c2.3,0,4.2,1.9,4.2,4.3\n" | |||
| "\t\t\tC98.1,129.9,96.2,131.9,93.9,131.9z M93.9,125.1c-1.3,0-2.2,1.1-2.2,2.5c0,1.4,1,2.5,2.2,2.5c1.3,0,2.2-1.1,2.2-2.5\n" | |||
| "\t\t\tC96.2,126.2,95.2,125.1,93.9,125.1z\"/>\n" | |||
| "\t\t<path class=\"st1\" d=\"M107.4,128.4h-6.2c0.1,1,1.1,1.7,2.1,1.7c0.7,0,1.5-0.3,2.3-1.1l1.3,1.2c-1.1,1.2-2.3,1.7-3.6,1.7\n" | |||
| "\t\t\tc-2.4,0-4.3-1.9-4.3-4.3c0-2.4,1.9-4.3,4.3-4.3c2.4,0,4.1,1.9,4.1,4.2C107.5,127.9,107.4,128.4,107.4,128.4z M103.3,125.1\n" | |||
| "\t\t\tc-1,0-1.9,0.6-2.1,1.5h4.1C105.1,125.8,104.3,125.1,103.3,125.1z\"/>\n" | |||
| "\t\t<path class=\"st1\" d=\"M110.4,127.8v3.9h-2v-8.3h2v0.8c0.7-0.6,1.4-0.9,2.6-1v1.8C111,125.4,110.4,126.6,110.4,127.8z\"/>\n" | |||
| "\t\t<path class=\"st1\" d=\"M116,127.8v3.9h-2v-8.3h2v0.8c0.7-0.6,1.4-0.9,2.6-1v1.8C116.6,125.4,116,126.6,116,127.8z\"/>\n" | |||
| "\t\t<path class=\"st1\" d=\"M122.2,134.6h-2.1l2.2-4.2l-3.4-6.9h2.1l2.4,4.8l2.3-4.8h2.2L122.2,134.6z\"/>\n" | |||
| "\t</g>\n" | |||
| "</g>\n" | |||
| "</svg>\n"; | |||
| "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"358\" version=\"1.1\">\n" | |||
| " <g id=\"g1\" transform=\"translate(-26.149211,-6.9701601)\">\n" | |||
| " <path id=\"path1\" style=\"fill:#bd0000;\" d=\"m 107.04146,18.25331 c 2e-5,0 -59.309734,258.76742 -59.309734,258.76742 0.01705,0 0.01919,0 0.05329,0 0,0 274.380814,0 274.380814,0 10e-6,0 142.70573,0 142.70573,0 -17.00107,-1.48353 -30.42327,-7.451" | |||
| "82 -38.42076,-18.33112 -1.48353,-2.01219 -2.70918,-4.17993 -3.78347,-6.44788 -0.68207,-1.6711 -1.32368,-3.35715 -1.97167,-5.06237 -1.70524,-5.42261 -2.38517,-11.43991 -2.07824,-17.85154 0.85262,-17.87074 9.23805,-39.12802 23.76654,-61.38798 15.43225," | |||
| "-23.65656 37.79837,-48.45089 65.49116,-71.45944 6.20699,-5.15319 12.62503,-10.24199 19.34361,-15.18714 8.30441,-6.10979 16.67281,-11.73661 25.04545,-16.99893 30.84745,-19.42077 61.61817,-33.01012 88.88467,-39.91282 -26.05577,8.50051 -55.32805,22.9313" | |||
| "8 -84.24861,42.57724 -1.04016,0.70255 -2.06758,1.42215 -3.09072,2.13152 -16.04613,11.11805 -30.58314,22.92586 -43.26998,34.85046 -40.82303,38.38457 -62.40046,78.15884 -52.59544,101.46069 0.69916,1.6029 1.53256,3.13335 2.50455,4.58279 10.53825,15.5004" | |||
| "7 36.65587,18.17979 69.75422,9.85831 2.25089,-0.56275 4.50178,-1.20004 6.82088,-1.86508 10.57238,-3.05237 21.797,-7.17473 33.3584,-12.25628 2.77949,-1.22777 5.58887,-2.48963 8.41953,-3.83676 0.28988,-0.13641 0.56273,-0.27283 0.85261,-0.4263 36.49173," | |||
| "-18.13162 64.53625,-38.99458 70.60684,-51.52967 1.33008,-2.72662 1.6498,-5.0513 0.74603,-6.87417 -4.33124,-8.78188 -34.63945,-2.849 -71.35287,13.0556 -2.95003,1.2789 -5.93417,2.59492 -8.95241,3.99661 2.45552,-2.27986 5.01123,-4.56828 7.6202,-6.82089 " | |||
| "4.16075,-3.58438 8.46856,-7.08945 13.0556,-10.55104 7.179,-5.43627 14.43685,-10.35071 21.58173,-14.70754 33.45643,-25.24067 50.3701,-49.11722 46.8403,-57.97753 -0.66505,-1.66943 -2.08037,-2.81789 -4.26306,-3.51703 -6.97438,-2.21847 -19.70383,0.42929 " | |||
| "-35.17019,6.7676 -12.75504,5.23164 -27.40504,12.9908 -42.25752,22.64748 0,0 -2.02495,1.3322 -2.02495,1.3322 2e-5,0 -0.37302,0.21314 -0.37302,0.21314 2e-5,2e-5 -7.14061,4.74265 -7.14061,4.74265 -2e-5,-1e-5 4.10319,-7.51363 4.10319,-7.51363 6.08765,-11" | |||
| ".14364 16.0035,-22.77067 28.3493,-33.57154 9.05473,-7.89689 19.39903,-15.33762 30.53413,-21.84816 4.21191,-2.46573 8.4174,-4.75586 12.62929,-6.82088 4.31422,-2.10769 8.63055,-3.98089 12.84246,-5.64859 12.23682,-4.8393 23.91781,-7.7798 34.10442,-8.579" | |||
| "4 -0.92767,0 -28.61574,0 -28.61574,0 10e-6,0 -96.45159,0 -96.45159,0 10e-6,0 -425.29295,0 -425.29295,0 0,0 -33.73141,0 -33.73141,0 z m 305.98071,0.3731 c -1.67109,25.50496 3.27403,62.86211 14.49439,104.3382 1.99509,7.38021 4.16712,14.89085 6.55444,22" | |||
| ".48762 1.48356,4.69104 3.01399,9.30881 4.58278,13.85492 -0.9208,1.35396 -1.84804,2.69937 -2.7177,4.0499 -13.99985,21.45508 -22.1828,41.73104 -24.24611,59.36303 -5.91714,-19.81468 -10.37201,-41.5183 -12.89574,-64.53198 -0.71619,-6.5941 -1.27679,-13.11" | |||
| "442 -1.65194,-19.55675 -2.76247,-46.79639 3.34649,-89.2172 15.87988,-120.00494 z m -279.3899,33.1985 c 0.31934,-0.0075 0.63414,0 0.95918,0 0,-2e-5 63.03991,0 63.03991,0 5.38851,-2e-5 9.55564,1.00991 12.52272,3.03742 2.95002,2.03946 4.05631,4.67743 3." | |||
| "35715,7.88665 2e-5,2e-5 -7.51363,33.94456 -7.51363,33.94456 0,3e-5 -22.22116,0 -22.22116,0 2e-5,3e-5 7.30048,-33.03866 7.30048,-33.03866 -2e-5,0 -55.63285,0 -55.63285,0 -2e-5,0 -20.08965,90.80305 -20.08965,90.80305 2e-5,0 -7.24719,32.87881 -7.24719,3" | |||
| "2.87881 0,3e-5 55.57957,0 55.57957,0 0,3e-5 0.0533,-0.21316 0.0533,-0.21316 0,2e-5 11.13722,-50.30404 11.13722,-50.30404 0,-2e-5 -26.1112,0 -26.1112,0 0,-2e-5 2.50454,-11.35038 2.50454,-11.35038 -1e-5,-2e-5 48.33238,0 48.33238,0 0,-2e-5 -13.85493,62." | |||
| "77348 -13.85493,62.77348 -0.18757,0.89352 -0.51582,1.70308 -0.95919,2.50453 -1.14249,2.08037 -3.13547,3.86234 -5.91498,5.32883 -3.85378,2.02923 -8.46643,3.03742 -13.85493,3.03742 0,2e-5 -62.98662,0 -62.98662,0 -5.388524,2e-5 -9.555644,-1.00821 -12.52" | |||
| "2724,-3.03742 -2.95001,-2.0292 -4.07335,-4.62073 -3.35715,-7.83336 10e-6,-2e-5 27.763134,-125.49366 27.763134,-125.49366 0.71618,-3.20921 2.95005,-5.84721 6.82089,-7.88665 0.13642,-0.07331 0.28989,-0.08995 0.4263,-0.15986 3.53302,-1.7697 7.67937,-2.7" | |||
| "6443 12.46944,-2.87756 z m 98.79628,0 c -2e-5,-2e-5 79.34608,0 79.34608,0 5.32031,-2e-5 9.50235,1.00991 12.46943,3.03742 2.98417,2.03946 4.12666,4.67743 3.41045,7.88665 2e-5,2e-5 -14.81411,66.92995 -14.81411,66.92995 -0.69912,3.18874 -2.98627,5.7905 " | |||
| "-6.87417,7.83336 -3.8879,2.04966 -8.4643,3.09071 -13.80164,3.09071 -2e-5,0 -57.6578,0 -57.6578,0 0,0 -11.13723,50.25075 -11.13723,50.25075 0,2e-5 -1.8118,8.25967 -1.8118,8.25967 1e-5,2e-5 -21.74158,0 -21.74158,0 0,2e-5 1.65193,-7.46034 1.65193,-7.460" | |||
| "34 0,2e-5 30.96044,-139.82817 30.96044,-139.82817 z m 109.61377,0 c -1e-5,-2e-5 21.74158,0 21.74158,0 2e-5,-2e-5 -24.61914,111.21241 -24.61914,111.21241 2e-5,2e-5 -5.3821,24.29942 -5.3821,24.29942 0,3e-5 52.00926,0 52.00926,0 0.54568,3.98854 1.18299," | |||
| "7.90582 1.86508,11.77668 -2e-5,2e-5 -78.22705,0 -78.22705,0 2e-5,2e-5 6.18143,-27.92301 6.18143,-27.92301 2e-5,0 26.43094,-119.3655 26.43094,-119.3655 z M 251.559,63.65488 c 2e-5,0 -14.4411,65.11814 -14.4411,65.11814 0,-2e-5 54.19408,0 54.19408,0 -1e" | |||
| "-5,-2e-5 14.3878,-65.11814 14.3878,-65.11814 2e-5,0 -54.14078,0 -54.14078,0 z m -45.5614,145.68987 c 0.21267,-0.007 0.42524,0 0.63947,0 2.52373,2e-5 4.72772,0.44336 6.50115,1.27891 1.8928,0.88671 3.24632,2.204 3.99662,3.94333 0.73325,1.68819 0.92508," | |||
| "3.60441 0.53287,5.70184 0,1e-5 -0.15985,0.79933 -0.15985,0.79933 1e-5,-3e-5 -6.23472,0 -6.23472,0 -2e-5,-3e-5 0.0533,-0.74605 0.0533,-0.74605 0.16118,-1.46543 -0.0205,-2.61779 -0.58618,-3.41043 -0.0737,-0.0994 -0.18053,-0.23063 -0.26644,-0.31973 -0.1" | |||
| "4068,-0.14363 -0.35198,-0.3078 -0.53287,-0.42631 -0.87238,-0.5495 -2.23703,-0.85262 -3.99662,-0.85262 -2.33615,2e-5 -4.08187,0.40712 -5.27552,1.22564 -1.15956,0.80146 -1.8928,1.6967 -2.13153,2.77098 -0.23875,1.09134 0.15346,1.66472 0.42631,1.97166 0." | |||
| "008,0.008 0.0429,0.0438 0.0533,0.0533 0.34552,0.29445 1.54536,0.98476 5.27553,1.86508 3.37632,0.81851 5.61232,1.55389 6.87418,2.18482 1.90987,0.97196 3.24419,2.2317 3.94334,3.78346 0.69911,1.53472 0.8526,3.33157 0.4263,5.27553 -0.4263,1.89279 -1.3918" | |||
| "9,3.63852 -2.82428,5.27554 -1.41532,1.637 -3.21647,2.93297 -5.38211,3.83673 -2.1486,0.88672 -4.50604,1.38549 -6.92746,1.38549 -3.0694,-1e-5 -5.53343,-0.46467 -7.46034,-1.38549 -2.01217,-0.95491 -3.46374,-2.4619 -4.31634,-4.42291 -0.81851,-1.90986 -0." | |||
| "9933,-4.07762 -0.53289,-6.44786 0,-3e-5 0.15987,-0.74605 0.15987,-0.74605 0,0 6.12814,0 6.12814,0 0,0 -0.0533,0.74605 -0.0533,0.74605 -0.15348,1.41532 -0.002,2.55782 0.37302,3.41043 0.35809,0.81853 1.04019,1.49633 2.13152,2.02496 1.15954,0.5627 2.608" | |||
| "98,0.8526 4.26305,0.8526 1.48355,-1e-5 2.83921,-0.24939 4.04991,-0.69273 1.19365,-0.4263 2.14218,-0.98904 2.82427,-1.70523 0.66502,-0.71622 1.05511,-1.47288 1.22563,-2.2914 0.17052,-0.73324 0.13002,-1.3535 -0.15987,-1.86508 -0.32399,-0.54564 -0.96558" | |||
| ",-1.02953 -1.97165,-1.43878 0,-1e-5 -5.22226,-1.54536 -5.22226,-1.54536 -2.91592,-0.73323 -4.87905,-1.43238 -6.02155,-2.13152 -1.55177,-0.92081 -2.67081,-2.10169 -3.25059,-3.51702 -0.57977,-1.39831 -0.64158,-2.94791 -0.26643,-4.63608 0.3922,-1.80753 " | |||
| "1.26399,-3.52767 2.61111,-5.06237 1.3642,-1.55177 3.12482,-2.76885 5.22225,-3.57031 1.85443,-0.6954 3.80596,-1.10321 5.86169,-1.17234 z m 55.9526,0.10658 c 0.33725,-0.0263 0.65436,0 1.01247,0 10e-6,0 3.83675,0.4263 3.83675,0.4263 -10e-6,0 1.27891,0.1" | |||
| "0658 1.27891,0.10658 2e-5,1e-5 -1.86508,4.74264 -1.86508,4.74264 -1e-5,2e-5 -0.37302,0.74604 -0.37302,0.74604 0,-2e-5 -2.93084,-0.26644 -2.93084,-0.26644 -0.86541,0 -1.50113,0.13352 -1.91838,0.4263 -0.0277,0.0209 -0.081,0.0842 -0.10658,0.10658 -0.055" | |||
| "9,0.049 -0.14991,0.13458 -0.21315,0.21316 -0.25659,0.33651 -0.56912,0.93786 -0.79932,2.02495 -2e-5,0 -0.11084,0.4604 -0.21315,0.85261 1.34713,1e-5 4.58277,0 4.58277,0 0,1e-5 -1.17233,5.27552 -1.17233,5.27552 0,-1e-5 -3.35076,0 -4.4762,0 -0.34106,1.53" | |||
| "469 -4.5295,20.3028 -4.5295,20.3028 -2e-5,-2e-5 -6.18143,0 -6.18143,0 -2e-5,-2e-5 4.01793,-18.03485 4.5295,-20.3028 -1.04021,-1e-5 -3.57031,0 -3.57031,0 0,-1e-5 1.17234,-5.27552 1.17234,-5.27552 10e-6,1e-5 2.54504,0 3.51701,0 0.17052,-0.73324 0.4263," | |||
| "-1.65194 0.4263,-1.65194 0.37516,-1.70522 0.76735,-2.93085 1.27891,-3.78346 0.69917,-1.17659 1.68606,-2.16137 2.93087,-2.87756 1.02527,-0.59575 2.32198,-0.95194 3.78346,-1.06576 z m 14.76082,0.26644 c -2e-5,-1e-5 -1.6157,7.27064 -2.02495,9.11228 1.19" | |||
| "364,1e-5 3.99661,0 3.99661,0 0,1e-5 -1.17233,5.27552 -1.17233,5.27552 -2e-5,-1e-5 -2.93939,0 -3.99662,0 -0.32401,1.46647 -2.87756,12.84246 -2.87756,12.84246 2e-5,-2e-5 -0.21316,1.36631 -0.21316,1.75851 2.8e-4,0.0131 -10e-4,0.0437 0,0.0533 8.4e-4,0.00" | |||
| "4 -10e-4,0.05 0,0.0533 0.002,0.003 0.0512,-0.002 0.0533,0 0,-2e-5 0.63947,0.0533 0.63947,0.0533 0,0 2.93085,-0.21315 2.93085,-0.21315 0,0 -0.31973,4.74264 -0.31973,4.74264 2e-5,0 0.0533,0.85261 0.0533,0.85261 -2e-5,-2e-5 -4.20978,0.4796 -4.20978,0.47" | |||
| "96 -1.6711,-2e-5 -2.89675,-0.27283 -3.78346,-0.8526 -0.93785,-0.61392 -1.48354,-1.42814 -1.70522,-2.45126 -0.0341,-0.18759 -0.10658,-0.46042 -0.10658,-0.85261 0,-0.76736 0.14495,-2.06545 0.63946,-4.31634 2e-5,2e-5 2.24023,-10.03524 2.7177,-12.14972 -" | |||
| "0.80143,-1e-5 -2.93085,0 -2.93085,0 2e-5,-1e-5 1.17233,-5.27552 1.17233,-5.27552 -2e-5,10e-6 2.07824,0 2.93086,0 0.27281,-1.22775 1.22562,-5.4354 1.22562,-5.4354 0,2e-5 4.95581,-2.61112 4.95581,-2.61112 -10e-6,2e-5 2.02495,-1.06576 2.02495,-1.06576 z" | |||
| " m -196.366934,0.21315 c 2e-5,0 23.979684,0 23.979684,0 0,0 -1.3322,5.96828 -1.3322,5.96828 0,2e-5 -15.997114,0 -17.531814,0 -0.27282,1.21073 -1.36631,6.11322 -1.75851,7.88665 2.2168,0 15.18713,0 15.18713,0 0,0 -1.33221,5.96826 -1.33221,5.96826 -1e-5" | |||
| ",0 -13.65242,0 -15.18713,0 -0.32399,1.48355 -3.25058,14.65426 -3.25058,14.65426 2e-5,-2e-5 -6.44786,0 -6.44786,0 1e-5,-2e-5 7.67349,-34.47745 7.67349,-34.47745 z m 32.132764,8.41954 c 0.24067,-0.0296 0.50358,0 0.74604,0 1.5347,0 2.95217,0.4668 4.3163" | |||
| "5,1.43877 0,2e-5 0.8526,0.58617 0.8526,0.58617 0,0 -3.30386,5.4354 -3.30386,5.4354 0,-2e-5 -0.90589,-0.63946 -0.90589,-0.63946 -0.66504,-0.42629 -1.38123,-0.63946 -2.13153,-0.63946 -0.64799,2e-5 -1.25121,0.2302 -1.86509,0.63946 -0.64797,0.4263 -1.189" | |||
| "4,0.99115 -1.59864,1.75851 -0.7162,1.31304 -1.24056,2.78377 -1.59865,4.36963 0,1e-5 -2.87756,13.10889 -2.87756,13.10889 0,-2e-5 -6.181434,0 -6.181434,0 0,-2e-5 5.701844,-25.57832 5.701844,-25.57832 2e-5,1e-5 5.75512,0 5.75512,0 0,1e-5 -0.19824,0.7758" | |||
| "8 -0.26645,1.06576 0.27284,-0.20461 0.56059,-0.48599 0.79933,-0.63946 0.82597,-0.4929 1.69837,-0.80035 2.55782,-0.90589 z m 18.33114,0 c 0.37219,-0.0282 0.73859,0 1.11906,0 3.47866,0 6.11108,1.21922 7.83335,3.62359 1.07428,1.55174 1.65195,3.47012 1.6" | |||
| "5195,5.75512 0,1.26185 -0.15561,2.61965 -0.47959,4.10319 0,0 -0.47961,1.86509 -0.47961,1.86509 0,-2e-5 -15.63261,0 -17.37194,0 -0.0341,0.35808 -0.0533,0.74177 -0.0533,1.06576 0,1.33009 0.27283,2.37665 0.85261,3.14401 0.2755,0.37513 0.5949,0.71299 0.9" | |||
| "5919,0.95918 0.68678,0.44789 1.57334,0.69275 2.61112,0.69275 1.26188,-2e-5 2.37025,-0.34745 3.41044,-1.01248 0.98905,-0.63093 1.93544,-1.63914 2.77099,-3.03742 0,-3e-5 6.60773,0 6.60773,0 0,-3e-5 -0.58616,1.27891 -0.58616,1.27891 -1.26187,2.55785 -3." | |||
| "02251,4.55294 -5.22225,5.96828 -2.19973,1.41533 -4.82791,2.1848 -7.72679,2.1848 -3.76854,-1e-5 -6.57363,-1.21922 -8.31296,-3.62358 -1.72226,-2.35323 -2.09315,-5.60593 -1.17233,-9.69846 0.92083,-4.16073 2.7582,-7.48591 5.43539,-9.80502 2.39958,-2.0806" | |||
| "2 5.12245,-3.23468 8.15309,-3.46372 z m 27.76315,0 c 0.37218,-0.0282 0.73857,0 1.11904,0 3.47867,0 6.1111,1.21922 7.83337,3.62359 1.07429,1.55174 1.65193,3.47012 1.65193,5.75512 0,1.2448 -0.1556,2.58341 -0.4796,4.0499 0,2e-5 -0.47958,1.91838 -0.47958" | |||
| ",1.91838 0,-2e-5 -15.63263,0 -17.37195,0 -0.0171,0.2387 -0.0362,0.45402 -0.0533,0.69275 -0.0128,0.0895 0.006,0.22541 0,0.31972 -5e-4,0.0154 0,0.0384 0,0.0533 0,0.2558 0.0362,0.5073 0.0533,0.74604 0.0852,0.97196 0.33892,1.78409 0.79932,2.39797 0.0877," | |||
| "0.11936 0.17008,0.2667 0.26644,0.37302 0.78101,0.83554 1.8864,1.27891 3.30387,1.27891 1.26188,-2e-5 2.38731,-0.34745 3.41045,-1.01248 0.98904,-0.63093 1.93542,-1.63914 2.77098,-3.03742 0,-3e-5 6.66102,0 6.66102,0 -1e-5,-3e-5 -0.63946,1.27891 -0.63946" | |||
| ",1.27891 -1.26186,2.55785 -3.0225,4.55294 -5.22224,5.96828 -2.19974,1.41533 -4.82791,2.1848 -7.72678,2.1848 -3.76853,-1e-5 -6.57363,-1.21922 -8.31296,-3.62358 -1.12545,-1.53472 -1.65194,-3.48931 -1.65194,-5.80841 0,-0.35812 0.0192,-0.74391 0.0533,-1." | |||
| "11905 0.0682,-0.88671 0.20463,-1.78196 0.42631,-2.771 0.92084,-4.14368 2.70491,-7.48591 5.3821,-9.80502 2.39957,-2.08062 5.17575,-3.23468 8.20639,-3.46372 z m 75.61591,0 c 0.38084,-0.0293 0.73112,0 1.11905,0 3.54687,0 6.2731,1.20004 8.04652,3.5703 1." | |||
| "7564,2.37023 2.18269,5.56754 1.27891,9.59186 -0.69913,3.13763 -1.70947,5.672 -3.0907,7.51365 -1.38126,1.84165 -3.14188,3.34649 -5.22226,4.36962 -2.06328,1.02314 -4.24813,1.54535 -6.44786,1.54535 -3.61507,-1e-5 -6.30719,-1.23628 -8.04652,-3.62358 -1.1" | |||
| "2546,-1.51764 -1.70522,-3.47226 -1.70522,-5.80841 2e-5,-1.26187 0.13856,-2.6026 0.4796,-4.10319 1.02315,-4.57001 3.11202,-8.05078 6.18143,-10.28462 2.25301,-1.62634 4.74114,-2.56606 7.40705,-2.77098 z m 93.62733,0 c 0.4496,-0.0277 0.86593,0 1.3322,0 " | |||
| "2.14859,0 3.83675,0.2174 5.11567,0.74602 1.36418,0.54569 2.33189,1.31729 2.87756,2.23811 0.52864,0.86963 0.79933,1.93756 0.79933,3.25057 10e-6,2e-5 -0.63948,3.89004 -0.63948,3.89004 3e-5,0 -1.17232,5.27554 -1.17232,5.27554 -1.00611,4.50176 -1.22776,6" | |||
| ".18995 -1.27893,6.82089 -0.0512,0.85262 0.0277,1.6839 0.26645,2.45125 2e-5,-2e-5 0.4263,1.38549 0.4263,1.38549 2e-5,-2e-5 -6.288,0 -6.288,0 -2e-5,-2e-5 -0.26645,-0.79933 -0.26645,-0.79933 -0.10231,-0.37512 -0.0725,-0.8526 -0.10657,-1.27891 -1.19365,0" | |||
| ".73325 -2.38944,1.38336 -3.46373,1.75851 -1.58584,0.54569 -3.23139,0.8526 -4.90251,0.8526 -2.89886,-1e-5 -5.04532,-0.75454 -6.3413,-2.2381 -0.98902,-1.09133 -1.43877,-2.42567 -1.43877,-3.94332 -2e-5,-0.57977 0.0767,-1.18087 0.21314,-1.81179 0.28988,-" | |||
| "1.29597 0.81638,-2.49603 1.65194,-3.57032 0.81851,-1.05726 1.78623,-1.92689 2.87756,-2.55782 1.05726,-0.63092 2.23598,-1.07855 3.46374,-1.3855 -2e-5,0 3.78346,-0.63946 3.78346,-0.63946 2.91594,-0.34106 5.09221,-0.76521 6.66102,-1.22562 0.0341,-0.1705" | |||
| "2 0.10657,-0.37302 0.10657,-0.37302 0.24726,-1.09718 0.21632,-1.92188 -0.0533,-2.34468 -0.0198,-0.0273 -0.0842,-0.0831 -0.10657,-0.10658 -0.0576,-0.0614 -0.14603,-0.15779 -0.21316,-0.21315 -0.64135,-0.50753 -1.73506,-0.74603 -3.19728,-0.74603 -1.6540" | |||
| "7,0 -2.87756,0.27071 -3.73018,0.79931 -0.83555,0.52863 -1.60931,1.49634 -2.29139,2.87756 -1e-5,-1e-5 -6.44787,0 -6.44787,0 2e-5,-1e-5 0.58617,-1.27891 0.58617,-1.27891 0.76733,-1.77345 1.68178,-3.24418 2.82428,-4.36963 1.1425,-1.12547 2.68572,-1.9972" | |||
| "4 4.4762,-2.61111 1.37216,-0.45297 2.8705,-0.75354 4.47621,-0.85261 z m 27.01711,0 c 0.24046,-0.0296 0.50356,0 0.74603,0 1.55173,0 3.00545,0.4668 4.36963,1.43877 2e-5,2e-5 0.79932,0.58617 0.79932,0.58617 1e-5,0 -3.30386,5.4354 -3.30386,5.4354 10e-6,-" | |||
| "2e-5 -0.9059,-0.63946 -0.9059,-0.63946 -0.66503,-0.42629 -1.34501,-0.63946 -2.07824,-0.63946 -0.64801,2e-5 -1.28744,0.2302 -1.91839,0.63946 -0.64797,0.4263 -1.11904,0.99115 -1.54534,1.75851 -0.69916,1.31304 -1.24056,2.78377 -1.59865,4.36963 -2e-5,1e-" | |||
| "5 -2.93085,13.10889 -2.93085,13.10889 2e-5,-2e-5 -6.18142,0 -6.18142,0 0,-2e-5 5.70182,-25.57832 5.70182,-25.57832 -10e-6,1e-5 5.75512,0 5.75512,0 0,1e-5 -0.14494,0.77588 -0.21314,1.06576 0.27282,-0.20461 0.54353,-0.48599 0.79932,-0.63946 0.81263,-0." | |||
| "4929 1.64571,-0.80035 2.50455,-0.90589 z m 18.27783,0 c 0.37185,-0.0282 0.73859,0 1.11906,0 3.47866,0 6.12813,1.21922 7.83337,3.62359 1.09135,1.55174 1.65193,3.47012 1.65193,5.75512 -2e-5,1.26185 -0.2089,2.61965 -0.53289,4.10319 0,0 -0.4263,1.86509 -" | |||
| "0.4263,1.86509 -10e-6,-2e-5 -15.64968,0 -17.37194,0 -0.0341,0.35808 -0.10659,0.74177 -0.10659,1.06576 0,1.33009 0.28989,2.37665 0.85262,3.14401 0.0877,0.11936 0.223,0.2667 0.31972,0.37302 0.056,0.0597 0.15412,0.15773 0.21316,0.21314 0.76235,0.69302 1" | |||
| ".79981,1.06577 3.0907,1.06577 1.26189,-2e-5 2.37027,-0.34745 3.41045,-1.01248 0.98905,-0.63093 1.91839,-1.63914 2.77099,-3.03742 -2e-5,-3e-5 6.66102,0 6.66102,0 0,-3e-5 -0.63946,1.27891 -0.63946,1.27891 -1.26187,2.55785 -3.03955,4.55294 -5.22224,5.96" | |||
| "828 -2.21677,1.41533 -4.81085,2.1848 -7.72679,2.1848 -3.76851,-1e-5 -6.52034,-1.21922 -8.25966,-3.62358 -1.7223,-2.35323 -2.1294,-5.60593 -1.22563,-9.69846 0.92081,-4.16073 2.70491,-7.48591 5.38211,-9.80502 2.41475,-2.08062 5.17852,-3.23468 8.20637,-" | |||
| "3.46372 z m -93.3076,0.47959 c 2e-5,1e-5 6.34131,0 6.34131,0 2e-5,1e-5 0.78226,15.17861 0.79931,15.40027 0.25578,-0.54356 0.45424,-1.00087 0.47961,-1.06575 1e-5,1e-5 6.98074,-14.33452 6.98074,-14.33452 2e-5,1e-5 5.80841,0 5.80841,0 -10e-6,1e-5 0.5328" | |||
| "8,14.99528 0.53288,15.08054 0.17055,-0.30691 7.93994,-15.08054 7.93994,-15.08054 -2e-5,1e-5 6.28801,0 6.28801,0 -10e-6,1e-5 -13.85493,25.57832 -13.85493,25.57832 2e-5,-2e-5 -5.70183,0 -5.70183,0 0,-2e-5 -0.55206,-13.86984 -0.58617,-14.65426 -2.35323," | |||
| "4.8258 -7.14063,14.65426 -7.14063,14.65426 0,-2e-5 -5.86168,0 -5.86168,0 0,-2e-5 -2.02497,-25.57832 -2.02497,-25.57832 z m -148.88715,4.84922 c -1.46893,0.062 -2.80295,0.59149 -4.04989,1.59864 -0.98902,0.78441 -1.65621,1.86084 -2.18483,3.03743 0,2e-5" | |||
| " 10.49777,0 10.49777,0 0.0171,-0.18756 0.0533,-0.41564 0.0533,-0.58617 -2e-5,-0.97195 -0.17266,-1.72653 -0.47959,-2.2381 -0.75028,-1.22777 -1.86295,-1.8118 -3.51702,-1.8118 -0.0991,0 -0.2218,-0.004 -0.31973,0 z m 27.76313,0 c -1.46892,0.062 -2.80295," | |||
| "0.59149 -4.0499,1.59864 -0.98904,0.78441 -1.63913,1.86084 -2.1848,3.03743 0,2e-5 10.49776,0 10.49776,0 0.017,-0.18756 0.0533,-0.41564 0.0533,-0.58617 -1e-5,-0.97195 -0.1556,-1.72653 -0.4796,-2.2381 -0.14894,-0.24939 -0.35043,-0.49739 -0.53288,-0.6927" | |||
| "4 -0.71199,-0.73572 -1.6919,-1.11906 -2.98413,-1.11906 -0.0991,0 -0.2218,-0.004 -0.31974,0 z m 214.75136,0 c -1.51056,0.0465 -2.92125,0.55794 -4.20977,1.59864 -0.97198,0.78441 -1.63915,1.86084 -2.18482,3.03743 2e-5,2e-5 10.44449,0 10.44449,0 0.017,-0" | |||
| ".18756 0.0533,-0.41564 0.0533,-0.58617 -1e-5,-0.97195 -0.1556,-1.72653 -0.4796,-2.2381 -0.73324,-1.21072 -1.86296,-1.79474 -3.51702,-1.8118 -0.049,5.3e-4 -0.0578,-0.001 -0.10657,0 z m -139.61502,0.10658 c -1.40963,0.16739 -2.72036,0.81051 -3.94333,1." | |||
| "91837 -1.46648,1.33009 -2.53012,3.41897 -3.14399,6.18143 -0.25579,1.15955 -0.37303,2.15071 -0.37303,3.03742 2e-5,1.2107 0.23233,2.16137 0.69275,2.87756 0.12257,0.18384 0.28516,0.37811 0.42631,0.53289 0.75804,0.80488 1.81659,1.22563 3.144,1.22563 1.72" | |||
| "226,-2e-5 3.23992,-0.64159 4.68935,-1.97167 1.46652,-1.34714 2.51307,-3.45734 3.144,-6.288 0.5798,-2.59195 0.49878,-4.52736 -0.31972,-5.75512 -0.7844,-1.19366 -1.93116,-1.75851 -3.51701,-1.75851 -0.26911,0 -0.5383,-0.031 -0.79933,0 z m 96.29173,9.325" | |||
| "43 c -1.44941,0.39218 -3.07367,0.72258 -5.22224,1.01248 -1.60289,0.23874 -2.74541,0.49024 -3.41045,0.74603 -0.57976,0.23872 -1.04658,0.56911 -1.43878,1.01248 -0.37516,0.42627 -0.62666,0.87392 -0.74602,1.38549 -0.0341,0.20464 -0.0533,0.41564 -0.0533,0" | |||
| ".58617 0,0.0138 -3.1e-4,0.0396 0,0.0533 0.002,0.0406 -0.004,0.1207 0,0.15985 0.0107,0.0774 0.0322,0.1944 0.0533,0.26645 0.008,0.0237 0.0445,0.0834 0.0533,0.10657 0.0187,0.0458 0.0301,0.11611 0.0533,0.15987 0.0243,0.0433 0.0779,0.11849 0.10658,0.15986" | |||
| " 0.0446,0.0614 0.1055,0.1556 0.15987,0.21315 0.42631,0.47746 1.27678,0.69275 2.50454,0.69275 1.34715,1e-5 2.6431,-0.25578 3.83675,-0.85261 1.17662,-0.57977 2.12513,-1.44518 2.82427,-2.45126 0.51159,-0.7162 0.92083,-1.8182 1.27892,-3.25057 z\"/>\n" | |||
| " <path id=\"path2\" style=\"fill:none;stroke:#bd0000;stroke-width:8.52610779;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;\" d=\"m 417.07944,250.20635 c 0.69221,1.77962 1.38445,3.55633 2.1055,5.2941 1.11046,2.36077 2.39396,4.6206 " | |||
| "3.93704,6.73045 8.33557,11.33086 22.29546,17.53495 40.01931,19.07947 0,0 19.06506,0 19.06506,0 6.02811,-0.47014 12.34467,-1.34696 18.89198,-2.62759 11.29193,-2.2079 23.26167,-5.5998 35.69288,-10.13244 9.41715,-3.43375 19.0939,-7.49624 28.91485,-12.21" | |||
| "778 21.38688,-10.25215 43.48041,-23.47509 65.15575,-39.43235 7.13857,-5.25082 13.98872,-10.63863 20.56487,-16.1173 6.02813,-5.01576 11.8111,-10.10942 17.33446,-15.25927 15.41645,-14.85689 27.0545,-29.94597 33.64507,-43.1949 6.86452,-13.7969 8.23461,-" | |||
| "25.59358 2.61028,-33.02493 -3.44673,-4.56146 -9.21527,-7.00589 -16.64228,-7.5813 33.45753,-25.10471 52.95528,-55.55833 45.96096,-74.7589 -2.22096,-6.1277 -7.10981,-10.6992 -14.11858,-13.2273 -3.11497,-1.1205 -6.61943,-1.8301 -10.4411,-2.1531 0,0 -10." | |||
| "29683,0 -10.29683,0 -14.37808,1.0369 -35.28664,6.1853 -53.0249,14.8785\"/>\n" | |||
| " <g id=\"g2\" style=\"fill:#bd0000;fill-opacity:1\" transform=\"scale(1.705222,1.705222)\">\n" | |||
| " <path id=\"path1\" transform=\"translate(15.5783,177.825)\" d=\"m 0,36 c 0,0 7.46,0 7.46,0 0,0 3.35,-14 3.35,-14 0,0 12.12,0 12.12,0 0,0 1.56,-6 1.56,-6 0,0 -12.17,0 -12.17,0 0,0 2.02,-9 2.02,-9 0,0 12.95,0 12.95,0 0,0 1.56,-7 1.56,-7 C 28.85,0" | |||
| " 8.4,0 8.4,0 8.4,0 0,36 0,36 z\"/>\n" | |||
| " <path id=\"path2\" transform=\"translate(41.5283,187.825)\" d=\"m 7.51,26 c 0,0 2.24,-9.4 2.24,-9.4 1.18,-5.09 3.67,-9.6 8.38,-9.6 0.42,0 0.83,0.21 1.14,0.26 0,0 1.82,-7.21 1.82,-7.21 -0.42,0 -0.88,-0.05 -1.4,-0.05 -3.47,0 -6.38,2.44 -8.32,5.94" | |||
| " 0,0 -0.2,0 -0.2,0 C 11.46,4.23 11.71,2.61 11.9,1 11.9,1 5.47,1 5.47,1 5.1,3.08 4.53,7.15 3.66,10.82 3.66,10.82 0,26 0,26 c 0,0 7.51,0 7.51,0 z\"/>\n" | |||
| " <path id=\"path3\" transform=\"translate(62.1583,187.825)\" d=\"M 20.06,19.25 C 17.99,20.26 15.63,20 12.88,20 10.71,20 9.03,19.62 8.11,18.88 7.63,18.07 7.43,16.81 7.47,16 17.6,16.27 23.99,13.93 24.31,7.53 24.55,2.7 21.02,0 15.89,0 6.73,0 0.73,8" | |||
| ".08 0.34,15.86 0,22.65 3.52,26 10.78,26 c 2.79,0 6.49,-0.32 9.52,-1.23 0,0 -0.24,-5.52 -0.24,-5.52 z M 17.32,7.53 C 17.2,9.91 14.26,10.05 8.47,10 9.1,7.91 11.22,6 14.69,6 c 1.71,0 2.69,0.65 2.63,1.53 z\"/>\n" | |||
| " <path id=\"path4\" transform=\"translate(88.6783,187.825)\" d=\"M 20.05,19.25 C 17.98,20.26 15.62,20 12.88,20 10.7,20 9.03,19.62 8.11,18.88 7.63,18.07 7.42,16.81 7.46,16 17.6,16.27 23.98,13.93 24.31,7.53 24.55,2.7 21.02,0 15.89,0 6.72,0 0.72,8." | |||
| "08 0.33,15.86 0,22.65 3.52,26 10.77,26 c 2.8,0 6.5,-0.32 9.53,-1.23 0,0 -0.25,-5.52 -0.25,-5.52 z M 17.32,7.53 C 17.2,9.91 14.26,10.05 8.46,10 9.1,7.91 11.21,6 14.68,6 c 1.71,0 2.7,0.65 2.64,1.53 z\"/>\n" | |||
| " <path id=\"path5\" transform=\"translate(124.5883,187.825)\" d=\"M 22.74,26 C 22.8,22.83 23.56,17.85 24.35,14.58 24.35,14.58 27.56,1 27.56,1 25.52,0.31 22.34,0 19.28,0 6.86,0 0.7,9.2 0.27,17.82 0,23.23 2.93,26 7.49,26 c 2.95,0 6.28,-1.43 8.87,-" | |||
| "5.73 0,0 0.11,0 0.11,0 -0.2,2.07 -0.44,4.08 -0.57,5.73 0,0 6.84,0 6.84,0 z M 17.61,11.72 C 16.15,18.08 13.17,20 10.95,20 8.88,20 7.98,18.53 8.1,16.39 8.34,11.56 12.16,6 16.98,6 c 0.78,0 1.39,-0.14 1.96,-0.28 0,0 -1.33,6 -1.33,6 z\"/>\n" | |||
| " <path id=\"path6\" transform=\"translate(152.2183,187.825)\" d=\"m 0,24.82 c 1.44,1.12 4.46,1.13 7.61,1.18 6.73,0.05 11.81,-2.89 12.11,-8.29 0.17,-3.6 -2.62,-5.73 -5.35,-7.16 C 12.4,9.6 11.41,8.69 11.46,7.63 11.53,6.2 12.87,6 14.84,6 c 2.22,0 4" | |||
| ",0.27 5.02,0.47 0,0 2.02,-5.42 2.02,-5.42 C 20.73,0.36 18.43,0 15.48,0 8.95,0 4.17,3.45 3.9,8.69 c -0.16,3.24 2.17,5.42 4.99,6.9 2.28,1.17 3.06,2.07 3,3.34 C 11.82,20.21 10.68,20 8.61,20 6.18,20 3.49,19.68 2.07,19.46 2.07,19.46 0,24.82 0,24.82 z\"/>\n" | |||
| " <path id=\"path7\" transform=\"translate(184.6683,176.825)\" d=\"m 7.45,37 c 0,0 6.05,-25 6.05,-25 0,0 -7.41,0 -7.41,0 0,0 -6.09,25 -6.09,25 0,0 7.45,0 7.45,0 z M 10.92,9 C 13.46,9 15.72,6.9 15.86,3.3 15.98,0.86 14.4,0 12.07,0 9.64,0 7.42,1.58 " | |||
| "7.29,3.94 7.17,6.32 8.75,9 10.92,9 z\"/>\n" | |||
| " <path id=\"path8\" transform=\"translate(198.9583,188.515)\" d=\"m 7.46,25.31 c 0,0 2.64,-11.26 2.64,-11.26 1.37,-5.73 4.32,-7.74 6.75,-7.74 1.92,0 2.48,0.82 2.39,2.01 -0.05,0.96 -0.2,1.97 -0.4,2.87 0,0 -3.36,14.12 -3.36,14.12 0,0 7.46,0 7.46,0" | |||
| " 0,0 3.54,-14.81 3.54,-14.81 C 26.75,9.22 27.05,7.31 27.11,6.15 27.33,1.64 25.08,0 20.94,0 c -3.32,0 -6.55,1.54 -9.1,4.88 0,0 -0.1,0 -0.1,0 0,0 0.68,-4.57 0.68,-4.57 0,0 -6.58,0 -6.58,0 C 5.41,2.45 4.86,5.04 4.08,8.06 4.08,8.06 0,25.31 0,25.31 c 0,0 " | |||
| "7.46,0 7.46,0 z\"/>\n" | |||
| " <path id=\"path9\" transform=\"translate(238.5183,177.825)\" d=\"m 0,36 c 0,0 7.46,0 7.46,0 0,0 3.36,-14 3.36,-14 0,0 12.11,0 12.11,0 0,0 1.56,-6 1.56,-6 0,0 -12.17,0 -12.17,0 0,0 2.02,-9 2.02,-9 0,0 12.95,0 12.95,0 0,0 1.56,-7 1.56,-7 C 28.85," | |||
| "0 8.4,0 8.4,0 8.4,0 0,36 0,36 z\"/>\n" | |||
| " <path id=\"path10\" transform=\"translate(264.4683,187.825)\" d=\"m 7.51,26 c 0,0 2.24,-9.4 2.24,-9.4 1.18,-5.09 3.67,-9.6 8.38,-9.6 0.42,0 0.83,0.21 1.14,0.26 0,0 1.82,-7.21 1.82,-7.21 -0.42,0 -0.88,-0.05 -1.4,-0.05 -3.47,0 -6.38,2.44 -8.32,5." | |||
| "94 0,0 -0.2,0 -0.2,0 C 11.46,4.23 11.71,2.61 11.9,1 11.9,1 5.47,1 5.47,1 5.11,3.08 4.53,7.15 3.66,10.82 3.66,10.82 0,26 0,26 c 0,0 7.51,0 7.51,0 z\"/>\n" | |||
| " <path id=\"path11\" transform=\"translate(285.0983,187.825)\" d=\"M 20.06,19.25 C 17.99,20.26 15.63,20 12.88,20 10.71,20 9.03,19.62 8.11,18.88 7.63,18.07 7.43,16.81 7.47,16 17.6,16.27 23.99,13.93 24.31,7.53 24.55,2.7 21.02,0 15.89,0 6.73,0 0.73" | |||
| ",8.08 0.34,15.86 0,22.65 3.53,26 10.78,26 c 2.79,0 6.5,-0.32 9.52,-1.23 0,0 -0.24,-5.52 -0.24,-5.52 z M 17.32,7.53 C 17.2,9.91 14.26,10.05 8.47,10 9.1,7.91 11.22,6 14.69,6 c 1.71,0 2.69,0.65 2.63,1.53 z\"/>\n" | |||
| " <path id=\"path12\" transform=\"translate(311.6183,187.825)\" d=\"M 20.05,19.25 C 17.98,20.26 15.62,20 12.88,20 10.7,20 9.03,19.62 8.11,18.88 7.63,18.07 7.42,16.81 7.46,16 17.6,16.27 23.98,13.93 24.31,7.53 24.55,2.7 21.02,0 15.89,0 6.72,0 0.72," | |||
| "8.08 0.33,15.86 0,22.65 3.52,26 10.77,26 c 2.8,0 6.5,-0.32 9.53,-1.23 0,0 -0.25,-5.52 -0.25,-5.52 z M 17.32,7.53 C 17.2,9.91 14.26,10.05 8.46,10 9.1,7.91 11.21,6 14.68,6 c 1.71,0 2.7,0.65 2.64,1.53 z\"/>\n" | |||
| " <path id=\"path13\" transform=\"translate(337.9983,176.825)\" d=\"M 23.31,0 C 23.31,0 20.5,11.36 20.5,11.36 19.48,10.94 18.15,11 17.11,11 7.53,11 0.75,19.2 0.3,28.08 0,34.34 3.34,37 7.64,37 c 3.01,0 6.18,-1.33 8.58,-4.77 0,0 0.1,0 0.1,0 0,0 -0." | |||
| "57,4.77 -0.57,4.77 0,0 6.78,0 6.78,0 0.31,-3 0.96,-6.57 1.69,-9.83 0,0 6.49,-27.17 6.49,-27.17 0,0 -7.4,0 -7.4,0 z M 17.47,24.42 C 16.3,29.35 13.54,31 11.36,31 9.19,31 7.98,29.49 8.13,26.8 8.38,21.82 11.8,17 16.25,17 c 1.25,0 2.32,0.19 2.91,0.47 0,0 " | |||
| "-1.69,6.95 -1.69,6.95 z\"/>\n" | |||
| " <path id=\"path14\" transform=\"translate(367.7083,187.825)\" d=\"M 10.61,26 C 19.31,26 26.02,19.41 26.49,10.44 26.78,4.5 23.08,0 16.25,0 7.23,0 0.76,7.3 0.31,16.12 0,22.54 4.14,26 10.61,26 z m 1.17,-6 C 9.24,20 7.84,18.38 7.99,15.96 8.19,11.93" | |||
| " 10.68,6 14.97,6 c 2.96,0 3.87,2.27 3.75,4.5 -0.22,4.4 -2.9,9.5 -6.94,9.5 z\"/>\n" | |||
| " <path id=\"path15\" transform=\"translate(396.2683,187.825)\" d=\"M 7.2,26 C 7.2,26 9.9,14.58 9.9,14.58 11.03,9.49 13.85,6 16.38,6 c 1.82,0 2.32,1.33 2.23,3.07 -0.05,0.9 -0.25,1.91 -0.46,2.91 0,0 -3.34,14.02 -3.34,14.02 0,0 7.2,0 7.2,0 0,0 2.7," | |||
| "-11.47 2.7,-11.47 C 25.95,9.28 28.61,6 31.09,6 c 1.71,0 2.42,1.22 2.34,2.96 -0.05,1.01 -0.26,2.12 -0.52,3.13 0,0 -3.24,13.91 -3.24,13.91 0,0 7.25,0 7.25,0 0,0 3.49,-14.81 3.49,-14.81 0.27,-1.33 0.58,-3.4 0.63,-4.46 C 41.26,2.33 39.11,0 35.23,0 31.91," | |||
| "0 28.68,1.5 26.23,4.66 26.14,2.38 24.51,0 20.47,0 17.2,0 14.08,1.48 11.58,4.83 c 0,0 -0.1,0 -0.1,0 0,0 0.67,-3.83 0.67,-3.83 0,0 -6.42,0 -6.42,0 C 5.31,3.14 4.8,5.73 4.02,8.75 4.02,8.75 0,26 0,26 c 0,0 7.2,0 7.2,0 z\"/>\n" | |||
| " </g>\n" | |||
| " </g>\n" | |||
| "</svg>"; | |||
| const char* huckleberry_icon_svg = (const char*) temp_binary_data_17; | |||
| const char* gpl_logo_svg = (const char*) temp_binary_data_17; | |||
| //================== juce-logo-with-text.svg ================== | |||
| static const unsigned char temp_binary_data_18[] = | |||
| @@ -7720,7 +7791,7 @@ const char* getNamedResource (const char* resourceNameUTF8, int& numBytes) | |||
| case 0x96d2a1ce: numBytes = 28184; return export_linux_svg; | |||
| case 0x2505bd06: numBytes = 1706; return export_visualStudio_svg; | |||
| case 0x3198e2bf: numBytes = 12295; return export_xcode_svg; | |||
| case 0x0cd37295: numBytes = 3375; return huckleberry_icon_svg; | |||
| case 0xc9c78dec: numBytes = 27030; return gpl_logo_svg; | |||
| case 0x80b17530: numBytes = 5312; return jucelogowithtext_svg; | |||
| case 0x154a7275: numBytes = 45854; return juce_icon_png; | |||
| case 0x1f3b6d2f: numBytes = 5978; return wizard_AnimatedApp_svg; | |||
| @@ -7793,7 +7864,7 @@ const char* namedResourceList[] = | |||
| "export_linux_svg", | |||
| "export_visualStudio_svg", | |||
| "export_xcode_svg", | |||
| "huckleberry_icon_svg", | |||
| "gpl_logo_svg", | |||
| "jucelogowithtext_svg", | |||
| "juce_icon_png", | |||
| "wizard_AnimatedApp_svg", | |||
| @@ -7861,7 +7932,7 @@ const char* originalFilenames[] = | |||
| "export_linux.svg", | |||
| "export_visualStudio.svg", | |||
| "export_xcode.svg", | |||
| "huckleberry_icon.svg", | |||
| "gpl_logo.svg", | |||
| "juce-logo-with-text.svg", | |||
| "juce_icon.png", | |||
| "wizard_AnimatedApp.svg", | |||
| @@ -59,8 +59,8 @@ namespace BinaryData | |||
| extern const char* export_xcode_svg; | |||
| const int export_xcode_svgSize = 12295; | |||
| extern const char* huckleberry_icon_svg; | |||
| const int huckleberry_icon_svgSize = 3375; | |||
| extern const char* gpl_logo_svg; | |||
| const int gpl_logo_svgSize = 27030; | |||
| extern const char* jucelogowithtext_svg; | |||
| const int jucelogowithtext_svgSize = 5312; | |||
| @@ -101,6 +101,16 @@ | |||
| </EXPORTFORMATS> | |||
| <MAINGROUP name="Projucer" id="NhrJq66R"> | |||
| <GROUP id="{9E4C4E0D-7BAB-EB6F-87DA-FB264EC2AE68}" name="Application"> | |||
| <GROUP id="{70333C48-4F84-A180-24E1-0EC9EF223F3B}" name="UserAccount"> | |||
| <FILE id="Rw7w0l" name="jucer_LicenseController.h" compile="0" resource="0" | |||
| file="Source/Application/UserAccount/jucer_LicenseController.h"/> | |||
| <FILE id="rUtPud" name="jucer_LicenseQueryThread.h" compile="0" resource="0" | |||
| file="Source/Application/UserAccount/jucer_LicenseQueryThread.h"/> | |||
| <FILE id="Dwndl3" name="jucer_LicenseState.h" compile="0" resource="0" | |||
| file="Source/Application/UserAccount/jucer_LicenseState.h"/> | |||
| <FILE id="d5kWEN" name="jucer_LoginFormComponent.h" compile="0" resource="0" | |||
| file="Source/Application/UserAccount/jucer_LoginFormComponent.h"/> | |||
| </GROUP> | |||
| <GROUP id="{2F08ABDF-C7BB-5F54-55F5-0C2E27983930}" name="Windows"> | |||
| <FILE id="w1XB4w" name="jucer_AboutWindowComponent.h" compile="0" resource="0" | |||
| file="Source/Application/Windows/jucer_AboutWindowComponent.h"/> | |||
| @@ -177,8 +187,7 @@ | |||
| file="Source/BinaryData/Icons/export_visualStudio.svg"/> | |||
| <FILE id="G0oYd6" name="export_xcode.svg" compile="0" resource="1" | |||
| file="Source/BinaryData/Icons/export_xcode.svg"/> | |||
| <FILE id="k4zzKu" name="huckleberry_icon.svg" compile="0" resource="1" | |||
| file="Source/BinaryData/Icons/huckleberry_icon.svg"/> | |||
| <FILE id="CfDTJ0" name="gpl_logo.svg" compile="0" resource="1" file="Source/BinaryData/Icons/gpl_logo.svg"/> | |||
| <FILE id="Pk2LIn" name="juce-logo-with-text.svg" compile="0" resource="1" | |||
| file="Source/BinaryData/Icons/juce-logo-with-text.svg"/> | |||
| <FILE id="Zrx1Gl" name="juce_icon.png" compile="0" resource="1" file="Source/BinaryData/Icons/juce_icon.png"/> | |||
| @@ -523,6 +532,15 @@ | |||
| file="Source/LiveBuildEngine/jucer_SourceCodeRange.h"/> | |||
| </GROUP> | |||
| <GROUP id="{6653587F-C475-46AA-E7CF-1D0DFA0FEAA9}" name="Project"> | |||
| <GROUP id="{F2E7D5CA-F002-2635-DA2C-898FA5EA2936}" name="Modules"> | |||
| <FILE id="w7QIJd" name="jucer_AvailableModulesList.h" compile="0" resource="0" | |||
| file="Source/Project/Modules/jucer_AvailableModulesList.h"/> | |||
| <FILE id="fQrJvA" name="jucer_ModuleDescription.h" compile="0" resource="0" | |||
| file="Source/Project/Modules/jucer_ModuleDescription.h"/> | |||
| <FILE id="mOC0iL" name="jucer_Modules.cpp" compile="1" resource="0" | |||
| file="Source/Project/Modules/jucer_Modules.cpp"/> | |||
| <FILE id="TnngL4" name="jucer_Modules.h" compile="0" resource="0" file="Source/Project/Modules/jucer_Modules.h"/> | |||
| </GROUP> | |||
| <GROUP id="{C37B7D1A-F059-9C82-9436-A2A94552BF90}" name="UI"> | |||
| <GROUP id="{19B83596-13BE-A80E-2722-BB5CCDA111FA}" name="Sidebar"> | |||
| <FILE id="bItg9o" name="jucer_ExporterTreeItems.h" compile="0" resource="0" | |||
| @@ -556,10 +574,11 @@ | |||
| resource="0" file="Source/Project/UI/jucer_ProjectContentComponent.cpp"/> | |||
| <FILE id="z576fZ" name="jucer_ProjectContentComponent.h" compile="0" | |||
| resource="0" file="Source/Project/UI/jucer_ProjectContentComponent.h"/> | |||
| <FILE id="itkVli" name="jucer_ProjectMessagesComponent.h" compile="0" | |||
| resource="0" file="Source/Project/UI/jucer_ProjectMessagesComponent.h"/> | |||
| <FILE id="UUlUDF" name="jucer_UserAvatarComponent.h" compile="0" resource="0" | |||
| file="Source/Project/UI/jucer_UserAvatarComponent.h"/> | |||
| </GROUP> | |||
| <FILE id="kPwhZB" name="jucer_Module.cpp" compile="1" resource="0" | |||
| file="Source/Project/jucer_Module.cpp"/> | |||
| <FILE id="YlGT8P" name="jucer_Module.h" compile="0" resource="0" file="Source/Project/jucer_Module.h"/> | |||
| <FILE id="JT1rMJ" name="jucer_Project.cpp" compile="1" resource="0" | |||
| file="Source/Project/jucer_Project.cpp"/> | |||
| <FILE id="bUjtVS" name="jucer_Project.h" compile="0" resource="0" file="Source/Project/jucer_Project.h"/> | |||
| @@ -0,0 +1,194 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE 6 technical preview. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| You may use this code under the terms of the GPL v3 | |||
| (see www.gnu.org/licenses). | |||
| For this technical preview, this file is not subject to commercial licensing. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| #pragma once | |||
| #include "jucer_LicenseState.h" | |||
| //============================================================================== | |||
| class LicenseController | |||
| { | |||
| public: | |||
| LicenseController() = default; | |||
| //============================================================================== | |||
| LicenseState getCurrentState() const noexcept | |||
| { | |||
| return state; | |||
| } | |||
| void setState (const LicenseState& newState) | |||
| { | |||
| state = newState; | |||
| licenseStateToSettings (state, getGlobalProperties()); | |||
| stateListeners.call ([] (LicenseStateListener& l) { l.licenseStateChanged(); }); | |||
| } | |||
| void resetState() | |||
| { | |||
| setState ({}); | |||
| } | |||
| static LicenseState getGPLState() | |||
| { | |||
| static auto logoImage = []() -> Image | |||
| { | |||
| if (auto logo = Drawable::createFromImageData (BinaryData::gpl_logo_svg, BinaryData::gpl_logo_svgSize)) | |||
| { | |||
| auto bounds = logo->getDrawableBounds(); | |||
| Image image (Image::ARGB, roundToInt (bounds.getWidth()), roundToInt (bounds.getHeight()), true); | |||
| Graphics g (image); | |||
| logo->draw (g, 1.0f); | |||
| return image; | |||
| } | |||
| jassertfalse; | |||
| return {}; | |||
| }(); | |||
| return { LicenseState::Type::gpl, {}, {}, logoImage }; | |||
| } | |||
| //============================================================================== | |||
| struct LicenseStateListener | |||
| { | |||
| virtual ~LicenseStateListener() = default; | |||
| virtual void licenseStateChanged() = 0; | |||
| }; | |||
| void addListener (LicenseStateListener* listenerToAdd) | |||
| { | |||
| stateListeners.add (listenerToAdd); | |||
| } | |||
| void removeListener (LicenseStateListener* listenerToRemove) | |||
| { | |||
| stateListeners.remove (listenerToRemove); | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| static const char* getLicenseStateValue (LicenseState::Type type) | |||
| { | |||
| switch (type) | |||
| { | |||
| case LicenseState::Type::gpl: return "GPL"; | |||
| case LicenseState::Type::personal: return "personal"; | |||
| case LicenseState::Type::educational: return "edu"; | |||
| case LicenseState::Type::indie: return "indie"; | |||
| case LicenseState::Type::pro: return "pro"; | |||
| case LicenseState::Type::none: | |||
| default: break; | |||
| } | |||
| return nullptr; | |||
| } | |||
| static LicenseState::Type getLicenseTypeFromValue (const String& d) | |||
| { | |||
| if (d == getLicenseStateValue (LicenseState::Type::gpl)) return LicenseState::Type::gpl; | |||
| if (d == getLicenseStateValue (LicenseState::Type::personal)) return LicenseState::Type::personal; | |||
| if (d == getLicenseStateValue (LicenseState::Type::educational)) return LicenseState::Type::educational; | |||
| if (d == getLicenseStateValue (LicenseState::Type::indie)) return LicenseState::Type::indie; | |||
| if (d == getLicenseStateValue (LicenseState::Type::pro)) return LicenseState::Type::pro; | |||
| return LicenseState::Type::none; | |||
| } | |||
| static Image avatarFromLicenseState (const String& licenseState) | |||
| { | |||
| MemoryOutputStream imageData; | |||
| Base64::convertFromBase64 (imageData, licenseState); | |||
| return ImageFileFormat::loadFrom (imageData.getData(), imageData.getDataSize()); | |||
| } | |||
| static String avatarToLicenseState (Image avatarImage) | |||
| { | |||
| MemoryOutputStream imageData; | |||
| if (avatarImage.isValid() && PNGImageFormat().writeImageToStream (avatarImage, imageData)) | |||
| return Base64::toBase64 (imageData.getData(), imageData.getDataSize()); | |||
| return {}; | |||
| } | |||
| static LicenseState licenseStateFromSettings (PropertiesFile& props) | |||
| { | |||
| if (auto licenseXml = props.getXmlValue ("license")) | |||
| { | |||
| // this is here for backwards compatibility with old-style settings files using XML text elements | |||
| if (licenseXml->getChildElementAllSubText ("type", {}).isNotEmpty()) | |||
| { | |||
| auto stateFromOldSettings = [&licenseXml]() -> LicenseState | |||
| { | |||
| return { getLicenseTypeFromValue (licenseXml->getChildElementAllSubText ("type", {})), | |||
| licenseXml->getChildElementAllSubText ("authToken", {}), | |||
| licenseXml->getChildElementAllSubText ("username", {}), | |||
| avatarFromLicenseState (licenseXml->getStringAttribute ("avatar", {})) }; | |||
| }(); | |||
| licenseStateToSettings (stateFromOldSettings, props); | |||
| return stateFromOldSettings; | |||
| } | |||
| return { getLicenseTypeFromValue (licenseXml->getStringAttribute ("type", {})), | |||
| licenseXml->getStringAttribute ("authToken", {}), | |||
| licenseXml->getStringAttribute ("username", {}), | |||
| avatarFromLicenseState (licenseXml->getStringAttribute ("avatar", {})) }; | |||
| } | |||
| return {}; | |||
| } | |||
| static void licenseStateToSettings (const LicenseState& state, PropertiesFile& props) | |||
| { | |||
| props.removeValue ("license"); | |||
| if (state.isValid()) | |||
| { | |||
| XmlElement licenseXml ("license"); | |||
| if (auto* typeString = getLicenseStateValue (state.type)) | |||
| licenseXml.setAttribute ("type", typeString); | |||
| licenseXml.setAttribute ("authToken", state.authToken); | |||
| licenseXml.setAttribute ("username", state.username); | |||
| licenseXml.setAttribute ("avatar", avatarToLicenseState (state.avatar)); | |||
| props.setValue ("license", &licenseXml); | |||
| } | |||
| props.saveIfNeeded(); | |||
| } | |||
| //============================================================================== | |||
| #if JUCER_ENABLE_GPL_MODE | |||
| LicenseState state = getGPLState(); | |||
| #else | |||
| LicenseState state = licenseStateFromSettings (getGlobalProperties()); | |||
| #endif | |||
| ListenerList<LicenseStateListener> stateListeners; | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LicenseController) | |||
| }; | |||
| @@ -0,0 +1,274 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE 6 technical preview. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| You may use this code under the terms of the GPL v3 | |||
| (see www.gnu.org/licenses). | |||
| For this technical preview, this file is not subject to commercial licensing. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| #pragma once | |||
| //============================================================================== | |||
| class LicenseQueryThread : public Thread | |||
| { | |||
| public: | |||
| LicenseQueryThread (const String& userEmail, const String& userPassword, | |||
| std::function<void(LicenseState, String)>&& cb) | |||
| : Thread ("LicenseQueryThread"), | |||
| email (userEmail), | |||
| password (userPassword), | |||
| completionCallback (std::move (cb)) | |||
| { | |||
| startThread(); | |||
| } | |||
| ~LicenseQueryThread() override | |||
| { | |||
| signalThreadShouldExit(); | |||
| waitForThreadToExit (-1); | |||
| } | |||
| void run() override | |||
| { | |||
| LicenseState state; | |||
| auto errorMessage = runJob (std::make_unique<UserLogin> (email, password), state); | |||
| if (errorMessage.isEmpty()) | |||
| errorMessage = runJob (std::make_unique<UserLicenseQuery> (state.authToken), state); | |||
| if (errorMessage.isNotEmpty()) | |||
| state = {}; | |||
| WeakReference<LicenseQueryThread> weakThis (this); | |||
| MessageManager::callAsync ([this, weakThis, state, errorMessage] | |||
| { | |||
| if (weakThis != nullptr) | |||
| completionCallback (state, errorMessage); | |||
| }); | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| struct AccountEnquiryBase | |||
| { | |||
| virtual ~AccountEnquiryBase() = default; | |||
| virtual bool isPOSTLikeRequest() const = 0; | |||
| virtual String getEndpointURLSuffix() const = 0; | |||
| virtual StringPairArray getParameterNamesAndValues() const = 0; | |||
| virtual int getSuccessCode() const = 0; | |||
| virtual String errorCodeToString (int) const = 0; | |||
| virtual bool parseServerResponse (const String&, LicenseState&) = 0; | |||
| }; | |||
| struct UserLogin : public AccountEnquiryBase | |||
| { | |||
| UserLogin (const String& e, const String& p) | |||
| : userEmail (e), userPassword (p) | |||
| { | |||
| } | |||
| bool isPOSTLikeRequest() const override { return true; } | |||
| String getEndpointURLSuffix() const override { return "/authenticate"; } | |||
| int getSuccessCode() const override { return 200; } | |||
| StringPairArray getParameterNamesAndValues() const override | |||
| { | |||
| StringPairArray namesAndValues; | |||
| namesAndValues.set ("email", userEmail); | |||
| namesAndValues.set ("password", userPassword); | |||
| return namesAndValues; | |||
| } | |||
| String errorCodeToString (int errorCode) const override | |||
| { | |||
| switch (errorCode) | |||
| { | |||
| case 400: return "Please enter your email and password to log in."; | |||
| case 401: return "Your email and password are incorrect."; | |||
| case 451: return "Access denied."; | |||
| default: return "Something went wrong, please try again."; | |||
| } | |||
| } | |||
| bool parseServerResponse (const String& serverResponse, LicenseState& licenseState) override | |||
| { | |||
| auto json = JSON::parse (serverResponse); | |||
| licenseState.authToken = json.getProperty ("token", {}).toString(); | |||
| licenseState.username = json.getProperty ("user", {}).getProperty ("username", {}).toString(); | |||
| auto avatarURL = json.getProperty ("user", {}).getProperty ("avatar_url", {}).toString(); | |||
| if (avatarURL.isNotEmpty()) | |||
| { | |||
| URL url (avatarURL); | |||
| if (auto stream = url.createInputStream (false)) | |||
| { | |||
| MemoryBlock mb; | |||
| stream->readIntoMemoryBlock (mb); | |||
| licenseState.avatar = ImageFileFormat::loadFrom (mb.getData(), mb.getSize()); | |||
| } | |||
| } | |||
| return (licenseState.authToken.isNotEmpty() && licenseState.username.isNotEmpty()); | |||
| } | |||
| String userEmail, userPassword; | |||
| }; | |||
| struct UserLicenseQuery : public AccountEnquiryBase | |||
| { | |||
| UserLicenseQuery (const String& authToken) | |||
| : userAuthToken (authToken) | |||
| { | |||
| } | |||
| bool isPOSTLikeRequest() const override { return false; } | |||
| String getEndpointURLSuffix() const override { return "/user/licences"; } | |||
| int getSuccessCode() const override { return 200; } | |||
| StringPairArray getParameterNamesAndValues() const override | |||
| { | |||
| StringPairArray namesAndValues; | |||
| namesAndValues.set ("token", userAuthToken); | |||
| return namesAndValues; | |||
| } | |||
| String errorCodeToString (int errorCode) const override | |||
| { | |||
| switch (errorCode) | |||
| { | |||
| case 401: return "User not found or could not be verified."; | |||
| default: return "User licenses info fetch failed (unknown error)."; | |||
| } | |||
| } | |||
| bool parseServerResponse (const String& serverResponse, LicenseState& licenseState) override | |||
| { | |||
| auto json = JSON::parse (serverResponse); | |||
| if (auto* licensesJson = json.getArray()) | |||
| { | |||
| StringArray licenseTypes; | |||
| for (auto& license : *licensesJson) | |||
| { | |||
| auto name = license.getProperty ("product_name", {}).toString(); | |||
| auto status = license.getProperty ("status", {}).toString(); | |||
| if (name == "Projucer" && status == "active") | |||
| licenseTypes.add (license.getProperty ("licence_type", {}).toString()); | |||
| } | |||
| licenseTypes.removeEmptyStrings(); | |||
| licenseTypes.removeDuplicates (false); | |||
| licenseState.type = [licenseTypes] () | |||
| { | |||
| if (licenseTypes.contains ("juce-pro")) return LicenseState::Type::pro; | |||
| else if (licenseTypes.contains ("juce-indie")) return LicenseState::Type::indie; | |||
| else if (licenseTypes.contains ("juce-personal")) return LicenseState::Type::personal; | |||
| else if (licenseTypes.contains ("juce-edu")) return LicenseState::Type::educational; | |||
| return LicenseState::Type::none; | |||
| }(); | |||
| return (licenseState.type != LicenseState::Type::none); | |||
| } | |||
| return false; | |||
| } | |||
| String userAuthToken; | |||
| }; | |||
| //============================================================================== | |||
| static String postDataStringAsJSON (const StringPairArray& parameters) | |||
| { | |||
| DynamicObject::Ptr d (new DynamicObject()); | |||
| for (auto& key : parameters.getAllKeys()) | |||
| d->setProperty (key, parameters[key]); | |||
| return JSON::toString (var (d.get())); | |||
| } | |||
| String runJob (std::unique_ptr<AccountEnquiryBase> accountEnquiryJob, LicenseState& state) | |||
| { | |||
| const String endpointURL = "https://api.roli.com/api/v1"; | |||
| const String extraHeaders = "Content-Type: application/json"; | |||
| auto url = URL (endpointURL + accountEnquiryJob->getEndpointURLSuffix()); | |||
| auto isPOST = accountEnquiryJob->isPOSTLikeRequest(); | |||
| if (isPOST) | |||
| url = url.withPOSTData (postDataStringAsJSON (accountEnquiryJob->getParameterNamesAndValues())); | |||
| else | |||
| url = url.withParameters (accountEnquiryJob->getParameterNamesAndValues()); | |||
| if (threadShouldExit()) | |||
| return "Cancelled."; | |||
| int statusCode = 0; | |||
| auto urlStream = url.createInputStream (isPOST, nullptr, nullptr, extraHeaders, 0, nullptr, &statusCode); | |||
| if (urlStream == nullptr) | |||
| return "Failed to connect to the web server."; | |||
| if (statusCode != accountEnquiryJob->getSuccessCode()) | |||
| return accountEnquiryJob->errorCodeToString (statusCode); | |||
| if (threadShouldExit()) | |||
| return "Cancelled."; | |||
| String response; | |||
| for (;;) | |||
| { | |||
| char buffer [8192]; | |||
| auto num = urlStream->read (buffer, sizeof (buffer)); | |||
| if (threadShouldExit()) | |||
| return "Cancelled."; | |||
| if (num <= 0) | |||
| break; | |||
| response += buffer; | |||
| } | |||
| if (threadShouldExit()) | |||
| return "Cancelled."; | |||
| if (! accountEnquiryJob->parseServerResponse (response, state)) | |||
| return "Failed to parse server response."; | |||
| return {}; | |||
| } | |||
| //============================================================================== | |||
| const String email, password; | |||
| const std::function<void(LicenseState, String)> completionCallback; | |||
| //============================================================================== | |||
| JUCE_DECLARE_WEAK_REFERENCEABLE (LicenseQueryThread) | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LicenseQueryThread) | |||
| }; | |||
| @@ -0,0 +1,68 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE 6 technical preview. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| You may use this code under the terms of the GPL v3 | |||
| (see www.gnu.org/licenses). | |||
| For this technical preview, this file is not subject to commercial licensing. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| #pragma once | |||
| //============================================================================== | |||
| struct LicenseState | |||
| { | |||
| enum class Type | |||
| { | |||
| none, | |||
| gpl, | |||
| personal, | |||
| educational, | |||
| indie, | |||
| pro | |||
| }; | |||
| LicenseState() = default; | |||
| LicenseState (Type t, String token, String user, Image avatarImage) | |||
| : type (t), authToken (token), username (user), avatar (avatarImage) | |||
| { | |||
| } | |||
| bool isValid() const noexcept { return isGPL() || (type != Type::none && authToken.isNotEmpty() && username.isNotEmpty()); } | |||
| bool isPaid() const noexcept { return type == Type::indie || type == Type::pro; } | |||
| bool isGPL() const noexcept { return type == Type::gpl; } | |||
| bool isPaidOrGPL() const noexcept { return isPaid() || isGPL(); } | |||
| String getLicenseTypeString() const | |||
| { | |||
| switch (type) | |||
| { | |||
| case Type::none: return "No license"; | |||
| case Type::gpl: return "GPL"; | |||
| case Type::personal: return "Personal"; | |||
| case Type::educational: return "Educational"; | |||
| case Type::indie: return "Indie"; | |||
| case Type::pro: return "Pro"; | |||
| default: break; | |||
| }; | |||
| jassertfalse; | |||
| return {}; | |||
| } | |||
| Type type = Type::none; | |||
| String authToken, username; | |||
| Image avatar; | |||
| }; | |||
| @@ -0,0 +1,273 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE 6 technical preview. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| You may use this code under the terms of the GPL v3 | |||
| (see www.gnu.org/licenses). | |||
| For this technical preview, this file is not subject to commercial licensing. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| #pragma once | |||
| #include "jucer_LicenseQueryThread.h" | |||
| #include "../../Project/UI/jucer_UserAvatarComponent.h" | |||
| //============================================================================== | |||
| class LoginFormComponent : public Component | |||
| { | |||
| public: | |||
| LoginFormComponent (MainWindow& window) | |||
| : mainWindow (window) | |||
| { | |||
| addAndMakeVisible (emailBox); | |||
| emailBox.setTextToShowWhenEmpty ("Email", Colours::black.withAlpha (0.2f)); | |||
| emailBox.setJustification (Justification::centredLeft); | |||
| emailBox.onReturnKey = [this] { submitDetails(); }; | |||
| addAndMakeVisible (passwordBox); | |||
| passwordBox.setTextToShowWhenEmpty ("Password", Colours::black.withAlpha (0.2f)); | |||
| passwordBox.setPasswordCharacter ((juce_wchar) 0x2022); | |||
| passwordBox.setJustification (Justification::centredLeft); | |||
| passwordBox.onReturnKey = [this] { submitDetails(); }; | |||
| addAndMakeVisible (logInButton); | |||
| logInButton.onClick = [this] { submitDetails(); }; | |||
| addAndMakeVisible (enableGPLButton); | |||
| enableGPLButton.onClick = [this] | |||
| { | |||
| ProjucerApplication::getApp().getLicenseController().setState (LicenseController::getGPLState()); | |||
| mainWindow.hideLoginFormOverlay(); | |||
| }; | |||
| addAndMakeVisible (userAvatar); | |||
| addAndMakeVisible (createAccountLabel); | |||
| createAccountLabel.setFont (Font (14.0f, Font::underlined)); | |||
| createAccountLabel.addMouseListener (this, false); | |||
| createAccountLabel.setMouseCursor (MouseCursor::PointingHandCursor); | |||
| addAndMakeVisible (errorMessageLabel); | |||
| errorMessageLabel.setMinimumHorizontalScale (1.0f); | |||
| errorMessageLabel.setFont (12.0f); | |||
| errorMessageLabel.setColour (Label::textColourId, Colours::red); | |||
| errorMessageLabel.setVisible (false); | |||
| dismissButton.setShape (getLookAndFeel().getCrossShape (1.0f), false, true, false); | |||
| addAndMakeVisible (dismissButton); | |||
| dismissButton.onClick = [this] { mainWindow.hideLoginFormOverlay(); }; | |||
| setWantsKeyboardFocus (true); | |||
| setOpaque (true); | |||
| lookAndFeelChanged(); | |||
| setSize (300, 350); | |||
| } | |||
| void resized() override | |||
| { | |||
| auto bounds = getLocalBounds().reduced (20); | |||
| auto spacing = bounds.getHeight() / 20; | |||
| userAvatar.setBounds (bounds.removeFromTop (iconHeight).reduced ((bounds.getWidth() / 2) - (iconHeight / 2), 0)); | |||
| errorMessageLabel.setBounds (bounds.removeFromTop (spacing)); | |||
| bounds.removeFromTop (spacing / 2); | |||
| auto textEditorHeight = bounds.getHeight() / 5; | |||
| emailBox.setBounds (bounds.removeFromTop (textEditorHeight)); | |||
| bounds.removeFromTop (spacing); | |||
| passwordBox.setBounds (bounds.removeFromTop (textEditorHeight)); | |||
| bounds.removeFromTop (spacing * 2); | |||
| emailBox.setFont (Font (textEditorHeight / 2.5f)); | |||
| passwordBox.setFont (Font (textEditorHeight / 2.5f)); | |||
| logInButton.setBounds (bounds.removeFromTop (textEditorHeight)); | |||
| auto slice = bounds.removeFromTop (textEditorHeight); | |||
| createAccountLabel.setBounds (slice.removeFromLeft (createAccountLabel.getFont().getStringWidth (createAccountLabel.getText()) + 5)); | |||
| slice.removeFromLeft (15); | |||
| enableGPLButton.setBounds (slice.reduced (0, 5)); | |||
| dismissButton.setBounds (getLocalBounds().reduced (10).removeFromTop (20).removeFromRight (20)); | |||
| } | |||
| void paint (Graphics& g) override | |||
| { | |||
| g.fillAll (findColour (secondaryBackgroundColourId).contrasting (0.1f)); | |||
| } | |||
| void mouseUp (const MouseEvent& event) override | |||
| { | |||
| if (event.eventComponent == &createAccountLabel) | |||
| URL ("https://auth.roli.com/register").launchInDefaultBrowser(); | |||
| } | |||
| void lookAndFeelChanged() override | |||
| { | |||
| enableGPLButton.setColour (TextButton::buttonColourId, findColour (secondaryButtonBackgroundColourId)); | |||
| } | |||
| private: | |||
| class ProgressButton : public TextButton, | |||
| private Timer | |||
| { | |||
| public: | |||
| ProgressButton (const String& buttonName) | |||
| : TextButton (buttonName), text (buttonName) | |||
| { | |||
| } | |||
| void setBusy (bool shouldBeBusy) | |||
| { | |||
| isInProgress = shouldBeBusy; | |||
| if (isInProgress) | |||
| { | |||
| setEnabled (false); | |||
| setButtonText ({}); | |||
| startTimerHz (30); | |||
| } | |||
| else | |||
| { | |||
| setEnabled (true); | |||
| setButtonText (text); | |||
| stopTimer(); | |||
| } | |||
| } | |||
| void paint (Graphics& g) override | |||
| { | |||
| TextButton::paint (g); | |||
| if (isInProgress) | |||
| { | |||
| auto size = getHeight() - 10; | |||
| auto halfSize = size / 2; | |||
| getLookAndFeel().drawSpinningWaitAnimation (g, Colours::white, | |||
| (getWidth() / 2) - halfSize, (getHeight() / 2) - halfSize, | |||
| size, size); | |||
| } | |||
| } | |||
| private: | |||
| void timerCallback() override | |||
| { | |||
| repaint(); | |||
| } | |||
| String text; | |||
| bool isInProgress = false; | |||
| }; | |||
| //============================================================================== | |||
| void updateLoginButtonStates (bool isLoggingIn) | |||
| { | |||
| logInButton.setBusy (isLoggingIn); | |||
| emailBox.setEnabled (! isLoggingIn); | |||
| passwordBox.setEnabled (! isLoggingIn); | |||
| } | |||
| void submitDetails() | |||
| { | |||
| if ((licenseQueryThread != nullptr && licenseQueryThread->isThreadRunning())) | |||
| return; | |||
| auto loginFormError = checkLoginFormsAreValid(); | |||
| if (loginFormError.isNotEmpty()) | |||
| { | |||
| showErrorMessage (loginFormError); | |||
| return; | |||
| } | |||
| updateLoginButtonStates (true); | |||
| WeakReference<Component> weakThis (this); | |||
| licenseQueryThread.reset (new LicenseQueryThread (emailBox.getText(), passwordBox.getText(), | |||
| [this, weakThis] (LicenseState newState, String errorMessage) | |||
| { | |||
| if (weakThis == nullptr) | |||
| return; | |||
| updateLoginButtonStates (false); | |||
| if (errorMessage.isNotEmpty()) | |||
| { | |||
| showErrorMessage (errorMessage); | |||
| } | |||
| else | |||
| { | |||
| hideErrorMessage(); | |||
| auto& licenseController = ProjucerApplication::getApp().getLicenseController(); | |||
| licenseController.setState (newState); | |||
| mainWindow.hideLoginFormOverlay(); | |||
| ProjucerApplication::getApp().getCommandManager().commandStatusChanged(); | |||
| } | |||
| })); | |||
| } | |||
| String checkLoginFormsAreValid() const | |||
| { | |||
| auto email = emailBox.getText(); | |||
| if (email.isEmpty() || email.indexOfChar ('@') < 0) | |||
| return "Please enter a valid email."; | |||
| auto password = passwordBox.getText(); | |||
| if (password.isEmpty() || password.length() < 8) | |||
| return "Please enter a valid password."; | |||
| return {}; | |||
| } | |||
| void showErrorMessage (const String& errorMessage) | |||
| { | |||
| errorMessageLabel.setText (errorMessage, dontSendNotification); | |||
| errorMessageLabel.setVisible (true); | |||
| } | |||
| void hideErrorMessage() | |||
| { | |||
| errorMessageLabel.setText ({}, dontSendNotification); | |||
| errorMessageLabel.setVisible (false); | |||
| } | |||
| //============================================================================== | |||
| static constexpr int iconHeight = 50; | |||
| MainWindow& mainWindow; | |||
| TextEditor emailBox, passwordBox; | |||
| ProgressButton logInButton { "Log In" }; | |||
| TextButton enableGPLButton { "Enable GPL Mode" }; | |||
| ShapeButton dismissButton { {}, | |||
| findColour (treeIconColourId), | |||
| findColour (treeIconColourId).overlaidWith (findColour (defaultHighlightedTextColourId).withAlpha (0.2f)), | |||
| findColour (treeIconColourId).overlaidWith (findColour (defaultHighlightedTextColourId).withAlpha (0.4f)) }; | |||
| UserAvatarComponent userAvatar { false, false }; | |||
| Label createAccountLabel { {}, "Create an account" }, | |||
| errorMessageLabel { {}, {} }; | |||
| std::unique_ptr<LicenseQueryThread> licenseQueryThread; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LoginFormComponent) | |||
| }; | |||
| @@ -50,23 +50,15 @@ public: | |||
| auto bounds = getLocalBounds(); | |||
| bounds.removeFromBottom (20); | |||
| auto rightSlice = bounds.removeFromRight (150); | |||
| auto leftSlice = bounds.removeFromLeft (150); | |||
| auto centreSlice = bounds; | |||
| auto centreSlice = bounds.withTrimmedRight (150); | |||
| //============================================================================== | |||
| rightSlice.removeFromRight (20); | |||
| auto iconSlice = rightSlice.removeFromRight (100); | |||
| huckleberryLogoBounds = iconSlice.removeFromBottom (100).toFloat(); | |||
| //============================================================================== | |||
| juceLogoBounds = leftSlice.removeFromTop (150).toFloat(); | |||
| juceLogoBounds.setWidth (juceLogoBounds.getWidth() + 100); | |||
| juceLogoBounds.setHeight (juceLogoBounds.getHeight() + 100); | |||
| copyrightLabel.setBounds (leftSlice.removeFromBottom (20)); | |||
| //============================================================================== | |||
| auto titleHeight = 40; | |||
| centreSlice.removeFromTop ((centreSlice.getHeight() / 2) - (titleHeight / 2)); | |||
| @@ -86,9 +78,6 @@ public: | |||
| if (juceLogo != nullptr) | |||
| juceLogo->drawWithin (g, juceLogoBounds.translated (-75, -75), RectanglePlacement::centred, 1.0); | |||
| if (huckleberryLogo != nullptr) | |||
| huckleberryLogo->drawWithin (g, huckleberryLogoBounds, RectanglePlacement::centred, 1.0); | |||
| } | |||
| private: | |||
| @@ -98,13 +87,10 @@ private: | |||
| HyperlinkButton aboutButton { "About Us", URL ("https://juce.com") }; | |||
| Rectangle<float> huckleberryLogoBounds, juceLogoBounds; | |||
| Rectangle<float> juceLogoBounds; | |||
| std::unique_ptr<Drawable> juceLogo { Drawable::createFromImageData (BinaryData::juce_icon_png, | |||
| BinaryData::juce_icon_pngSize) }; | |||
| std::unique_ptr<Drawable> huckleberryLogo { Drawable::createFromImageData (BinaryData::huckleberry_icon_svg, | |||
| BinaryData::huckleberry_icon_svgSize) }; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AboutWindowComponent) | |||
| }; | |||
| @@ -23,7 +23,8 @@ | |||
| //============================================================================== | |||
| class GlobalPathsWindowComponent : public Component, | |||
| private Timer, | |||
| private Value::Listener | |||
| private Value::Listener, | |||
| private ChangeListener | |||
| { | |||
| public: | |||
| GlobalPathsWindowComponent() | |||
| @@ -42,6 +43,16 @@ public: | |||
| lastUserModulePath = getAppSettings().getStoredPath (Ids::defaultUserModulePath, TargetOS::getThisOS()).get(); | |||
| }; | |||
| addChildComponent (warnAboutJUCEPathButton); | |||
| warnAboutJUCEPathButton.setToggleState (ProjucerApplication::getApp().shouldPromptUserAboutIncorrectJUCEPath(), | |||
| dontSendNotification); | |||
| warnAboutJUCEPathButton.onClick = [this] | |||
| { | |||
| ProjucerApplication::getApp().setShouldPromptUserAboutIncorrectJUCEPath (warnAboutJUCEPathButton.getToggleState()); | |||
| }; | |||
| getGlobalProperties().addChangeListener (this); | |||
| addAndMakeVisible (resetToDefaultsButton); | |||
| resetToDefaultsButton.onClick = [this] { resetCurrentOSPathsToDefaults(); }; | |||
| @@ -64,6 +75,8 @@ public: | |||
| ~GlobalPathsWindowComponent() override | |||
| { | |||
| getGlobalProperties().removeChangeListener (this); | |||
| auto juceValue = getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()); | |||
| auto userValue = getAppSettings().getStoredPath (Ids::defaultUserModulePath, TargetOS::getThisOS()); | |||
| @@ -86,12 +99,15 @@ public: | |||
| { | |||
| auto b = getLocalBounds().reduced (10); | |||
| auto buttonBounds = b.removeFromBottom (50); | |||
| auto bottomBounds = b.removeFromBottom (80); | |||
| auto buttonBounds = bottomBounds.removeFromBottom (50); | |||
| rescanJUCEPathButton.setBounds (buttonBounds.removeFromLeft (150).reduced (5, 10)); | |||
| rescanUserPathButton.setBounds (buttonBounds.removeFromLeft (150).reduced (5, 10)); | |||
| resetToDefaultsButton.setBounds (buttonBounds.removeFromRight (150).reduced (5, 10)); | |||
| warnAboutJUCEPathButton.setBounds (bottomBounds.reduced (0, 5)); | |||
| warnAboutJUCEPathButton.changeWidthToFitText(); | |||
| propertyGroup.updateSize (0, 0, getWidth() - 20 - propertyViewport.getScrollBarThickness()); | |||
| propertyViewport.setBounds (b); | |||
| @@ -145,6 +161,12 @@ private: | |||
| resized(); | |||
| } | |||
| void changeListenerCallback (ChangeBroadcaster*) override | |||
| { | |||
| warnAboutJUCEPathButton.setToggleState (ProjucerApplication::getApp().shouldPromptUserAboutIncorrectJUCEPath(), | |||
| dontSendNotification); | |||
| } | |||
| //============================================================================== | |||
| bool isSelectedOSThisOS() { return TargetOS::getThisOS() == getSelectedOS(); } | |||
| @@ -223,16 +245,12 @@ private: | |||
| "This path will be used for the \"Save Project and Open in IDE...\" option of the CLion exporter."); | |||
| builder.add (new FilePathPropertyComponent (androidStudioExePathValue, "Android Studio " + exeLabel, false, isThisOS), | |||
| "This path will be used for the \"Save Project and Open in IDE...\" option of the Android Studio exporter."); | |||
| rescanJUCEPathButton.setVisible (true); | |||
| rescanUserPathButton.setVisible (true); | |||
| } | |||
| else | |||
| { | |||
| rescanJUCEPathButton.setVisible (false); | |||
| rescanUserPathButton.setVisible (false); | |||
| } | |||
| rescanJUCEPathButton.setVisible (isThisOS); | |||
| rescanUserPathButton.setVisible (isThisOS); | |||
| warnAboutJUCEPathButton.setVisible (isThisOS); | |||
| propertyGroup.setProperties (builder); | |||
| } | |||
| @@ -279,6 +297,7 @@ private: | |||
| Viewport propertyViewport; | |||
| PropertyGroupComponent propertyGroup { "Global Paths", { getIcons().openFolder, Colours::transparentBlack } }; | |||
| ToggleButton warnAboutJUCEPathButton { "Warn about incorrect JUCE path" }; | |||
| TextButton rescanJUCEPathButton { "Re-scan JUCE Modules" }, | |||
| rescanUserPathButton { "Re-scan User Modules" }, | |||
| resetToDefaultsButton { "Reset to Defaults" }; | |||
| @@ -18,6 +18,7 @@ | |||
| #pragma once | |||
| //============================================================================== | |||
| static String getWidthLimitedStringFromVarArray (const var& varArray) noexcept | |||
| { | |||
| @@ -16,7 +16,7 @@ | |||
| ============================================================================== | |||
| */ | |||
| void createGUIEditorMenu (PopupMenu&); | |||
| PopupMenu createGUIEditorMenu(); | |||
| void handleGUIEditorMenuCommand (int); | |||
| void registerGUIEditorCommands(); | |||
| @@ -36,9 +36,7 @@ struct ProjucerApplication::MainMenuModel : public MenuBarModel | |||
| PopupMenu getMenuForIndex (int /*topLevelMenuIndex*/, const String& menuName) override | |||
| { | |||
| PopupMenu menu; | |||
| getApp().createMenu (menu, menuName); | |||
| return menu; | |||
| return getApp().createMenu (menuName); | |||
| } | |||
| void menuItemSelected (int menuItemID, int /*topLevelMenuIndex*/) override | |||
| @@ -48,10 +46,6 @@ struct ProjucerApplication::MainMenuModel : public MenuBarModel | |||
| }; | |||
| //============================================================================== | |||
| ProjucerApplication::ProjucerApplication() : isRunningCommandLine (false) | |||
| { | |||
| } | |||
| void ProjucerApplication::initialise (const String& commandLine) | |||
| { | |||
| if (commandLine.trimStart().startsWith ("--server")) | |||
| @@ -99,22 +93,6 @@ void ProjucerApplication::initialise (const String& commandLine) | |||
| return; | |||
| } | |||
| rescanJUCEPathModules(); | |||
| rescanUserPathModules(); | |||
| openDocumentManager.registerType (new ProjucerAppClasses::LiveBuildCodeEditorDocument::Type(), 2); | |||
| childProcessCache.reset (new ChildProcessCache()); | |||
| initCommandManager(); | |||
| menuModel.reset (new MainMenuModel()); | |||
| settings->appearance.refreshPresetSchemeList(); | |||
| setColourScheme (settings->getGlobalProperties().getIntValue ("COLOUR SCHEME"), false); | |||
| setEditorColourScheme (settings->getGlobalProperties().getIntValue ("EDITOR COLOUR SCHEME"), false); | |||
| updateEditorColourSchemeIfNeeded(); | |||
| // do further initialisation in a moment when the message loop has started | |||
| triggerAsyncUpdate(); | |||
| } | |||
| @@ -153,29 +131,37 @@ void ProjucerApplication::initialiseWindows (const String& commandLine) | |||
| void ProjucerApplication::handleAsyncUpdate() | |||
| { | |||
| licenseController = std::make_unique<LicenseController>(); | |||
| LookAndFeel::setDefaultLookAndFeel (&lookAndFeel); | |||
| ImageCache::setCacheTimeout (30 * 1000); | |||
| icons = std::make_unique<Icons>(); | |||
| rescanJUCEPathModules(); | |||
| rescanUserPathModules(); | |||
| tooltipWindow = std::make_unique<TooltipWindow> (nullptr, 1200); | |||
| openDocumentManager.registerType (new ProjucerAppClasses::LiveBuildCodeEditorDocument::Type(), 2); | |||
| childProcessCache.reset (new ChildProcessCache()); | |||
| #if JUCE_MAC | |||
| PopupMenu extraAppleMenuItems; | |||
| createExtraAppleMenuItems (extraAppleMenuItems); | |||
| initCommandManager(); | |||
| menuModel.reset (new MainMenuModel()); | |||
| // workaround broken "Open Recent" submenu: not passing the | |||
| // submenu's title here avoids the defect in JuceMainMenuHandler::addMenuItem | |||
| MenuBarModel::setMacMainMenu (menuModel.get(), &extraAppleMenuItems); //, "Open Recent"); | |||
| #if JUCE_MAC | |||
| rebuildAppleMenu(); | |||
| appleMenuRebuildListener = std::make_unique<AppleMenuRebuildListener>(); | |||
| #endif | |||
| if (getGlobalProperties().getValue (Ids::dontQueryForUpdate, {}).isEmpty()) | |||
| LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (false); | |||
| settings->appearance.refreshPresetSchemeList(); | |||
| setColourScheme (getGlobalProperties().getIntValue ("COLOUR SCHEME"), false); | |||
| setEditorColourScheme (getGlobalProperties().getIntValue ("EDITOR COLOUR SCHEME"), false); | |||
| updateEditorColourSchemeIfNeeded(); | |||
| initialiseWindows (getCommandLineParameters()); | |||
| ImageCache::setCacheTimeout (30 * 1000); | |||
| icons = std::make_unique<Icons>(); | |||
| tooltipWindow = std::make_unique<TooltipWindow> (nullptr, 1200); | |||
| if (isAutomaticVersionCheckingEnabled()) | |||
| LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (true); | |||
| if (! isRunningCommandLine && settings->shouldAskUserToSetJUCEPath()) | |||
| showSetJUCEPathAlert(); | |||
| initialiseWindows (getCommandLineParameters()); | |||
| } | |||
| static void deleteTemporaryFiles() | |||
| @@ -314,25 +300,52 @@ MenuBarModel* ProjucerApplication::getMenuModel() | |||
| StringArray ProjucerApplication::getMenuNames() | |||
| { | |||
| return { "File", "Edit", "View", "Build", "Window", "Document", "GUI Editor", "Tools", "Help" }; | |||
| StringArray currentMenuNames { "File", "Edit", "View", "Build", "Window", "Document", "GUI Editor", "Tools", "Help" }; | |||
| if (! isLiveBuildEnabled()) currentMenuNames.removeString ("Build"); | |||
| if (! isGUIEditorEnabled()) currentMenuNames.removeString ("GUI Editor"); | |||
| return currentMenuNames; | |||
| } | |||
| void ProjucerApplication::createMenu (PopupMenu& menu, const String& menuName) | |||
| PopupMenu ProjucerApplication::createMenu (const String& menuName) | |||
| { | |||
| if (menuName == "File") createFileMenu (menu); | |||
| else if (menuName == "Edit") createEditMenu (menu); | |||
| else if (menuName == "View") createViewMenu (menu); | |||
| else if (menuName == "Build") createBuildMenu (menu); | |||
| else if (menuName == "Window") createWindowMenu (menu); | |||
| else if (menuName == "Document") createDocumentMenu (menu); | |||
| else if (menuName == "Tools") createToolsMenu (menu); | |||
| else if (menuName == "Help") createHelpMenu (menu); | |||
| else if (menuName == "GUI Editor") createGUIEditorMenu (menu); | |||
| else jassertfalse; // names have changed? | |||
| if (menuName == "File") | |||
| return createFileMenu(); | |||
| if (menuName == "Edit") | |||
| return createEditMenu(); | |||
| if (menuName == "View") | |||
| return createViewMenu(); | |||
| if (menuName == "Build") | |||
| if (isLiveBuildEnabled()) | |||
| return createBuildMenu(); | |||
| if (menuName == "Window") | |||
| return createWindowMenu(); | |||
| if (menuName == "Document") | |||
| return createDocumentMenu(); | |||
| if (menuName == "Tools") | |||
| return createToolsMenu(); | |||
| if (menuName == "Help") | |||
| return createHelpMenu(); | |||
| if (menuName == "GUI Editor") | |||
| if (isGUIEditorEnabled()) | |||
| return createGUIEditorMenu(); | |||
| jassertfalse; // names have changed? | |||
| return {}; | |||
| } | |||
| void ProjucerApplication::createFileMenu (PopupMenu& menu) | |||
| PopupMenu ProjucerApplication::createFileMenu() | |||
| { | |||
| PopupMenu menu; | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::newProject); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::newProjectFromClipboard); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::newPIP); | |||
| @@ -353,12 +366,7 @@ void ProjucerApplication::createFileMenu (PopupMenu& menu) | |||
| menu.addSubMenu ("Open Recent", recentFiles); | |||
| } | |||
| { | |||
| PopupMenu examples; | |||
| createExamplesPopupMenu (examples); | |||
| menu.addSubMenu ("Open Example", examples); | |||
| } | |||
| menu.addSubMenu ("Open Example", createExamplesPopupMenu()); | |||
| menu.addSeparator(); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::closeDocument); | |||
| @@ -373,17 +381,25 @@ void ProjucerApplication::createFileMenu (PopupMenu& menu) | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::saveAndOpenInIDE); | |||
| menu.addSeparator(); | |||
| #if ! JUCE_MAC | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::showAboutWindow); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::checkForNewVersion); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::showGlobalPathsWindow); | |||
| menu.addSeparator(); | |||
| menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::quit); | |||
| #endif | |||
| #if ! JUCER_ENABLE_GPL_MODE | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::loginLogout); | |||
| #endif | |||
| #if ! JUCE_MAC | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::showAboutWindow); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::checkForNewVersion); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::enableNewVersionCheck); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::showGlobalPathsWindow); | |||
| menu.addSeparator(); | |||
| menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::quit); | |||
| #endif | |||
| return menu; | |||
| } | |||
| void ProjucerApplication::createEditMenu (PopupMenu& menu) | |||
| PopupMenu ProjucerApplication::createEditMenu() | |||
| { | |||
| PopupMenu menu; | |||
| menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::undo); | |||
| menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::redo); | |||
| menu.addSeparator(); | |||
| @@ -398,10 +414,12 @@ void ProjucerApplication::createEditMenu (PopupMenu& menu) | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::findSelection); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::findNext); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::findPrevious); | |||
| return menu; | |||
| } | |||
| void ProjucerApplication::createViewMenu (PopupMenu& menu) | |||
| PopupMenu ProjucerApplication::createViewMenu() | |||
| { | |||
| PopupMenu menu; | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::showProjectSettings); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::showProjectTab); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::showBuildTab); | |||
| @@ -412,10 +430,13 @@ void ProjucerApplication::createViewMenu (PopupMenu& menu) | |||
| menu.addSeparator(); | |||
| createColourSchemeItems (menu); | |||
| return menu; | |||
| } | |||
| void ProjucerApplication::createBuildMenu (PopupMenu& menu) | |||
| PopupMenu ProjucerApplication::createBuildMenu() | |||
| { | |||
| PopupMenu menu; | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::toggleBuildEnabled); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::buildNow); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::toggleContinuousBuild); | |||
| @@ -429,55 +450,60 @@ void ProjucerApplication::createBuildMenu (PopupMenu& menu) | |||
| menu.addSeparator(); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::nextError); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::prevError); | |||
| return menu; | |||
| } | |||
| void ProjucerApplication::createColourSchemeItems (PopupMenu& menu) | |||
| { | |||
| PopupMenu colourSchemeMenu; | |||
| { | |||
| PopupMenu colourSchemeMenu; | |||
| colourSchemeMenu.addItem (PopupMenu::Item ("Dark") | |||
| .setTicked (selectedColourSchemeIndex == 0) | |||
| .setAction ([this] { setColourScheme (0, true); updateEditorColourSchemeIfNeeded(); })); | |||
| colourSchemeMenu.addItem (PopupMenu::Item ("Dark") | |||
| .setTicked (selectedColourSchemeIndex == 0) | |||
| .setAction ([this] { setColourScheme (0, true); updateEditorColourSchemeIfNeeded(); })); | |||
| colourSchemeMenu.addItem (PopupMenu::Item ("Grey") | |||
| .setTicked (selectedColourSchemeIndex == 1) | |||
| .setAction ([this] { setColourScheme (1, true); updateEditorColourSchemeIfNeeded(); })); | |||
| colourSchemeMenu.addItem (PopupMenu::Item ("Grey") | |||
| .setTicked (selectedColourSchemeIndex == 1) | |||
| .setAction ([this] { setColourScheme (1, true); updateEditorColourSchemeIfNeeded(); })); | |||
| colourSchemeMenu.addItem (PopupMenu::Item ("Light") | |||
| .setTicked (selectedColourSchemeIndex == 2) | |||
| .setAction ([this] { setColourScheme (2, true); updateEditorColourSchemeIfNeeded(); })); | |||
| colourSchemeMenu.addItem (PopupMenu::Item ("Light") | |||
| .setTicked (selectedColourSchemeIndex == 2) | |||
| .setAction ([this] { setColourScheme (2, true); updateEditorColourSchemeIfNeeded(); })); | |||
| menu.addSubMenu ("Colour Scheme", colourSchemeMenu); | |||
| menu.addSubMenu ("Colour Scheme", colourSchemeMenu); | |||
| } | |||
| //============================================================================== | |||
| PopupMenu editorColourSchemeMenu; | |||
| { | |||
| PopupMenu editorColourSchemeMenu; | |||
| auto& appearanceSettings = getAppSettings().appearance; | |||
| auto& appearanceSettings = getAppSettings().appearance; | |||
| appearanceSettings.refreshPresetSchemeList(); | |||
| auto schemes = appearanceSettings.getPresetSchemes(); | |||
| appearanceSettings.refreshPresetSchemeList(); | |||
| auto schemes = appearanceSettings.getPresetSchemes(); | |||
| auto i = 0; | |||
| auto i = 0; | |||
| for (auto& s : schemes) | |||
| { | |||
| editorColourSchemeMenu.addItem (PopupMenu::Item (s) | |||
| .setEnabled (editorColourSchemeWindow == nullptr) | |||
| .setTicked (selectedEditorColourSchemeIndex == i) | |||
| .setAction ([this, i] { setEditorColourScheme (i, true); })); | |||
| ++i; | |||
| } | |||
| for (auto& s : schemes) | |||
| { | |||
| editorColourSchemeMenu.addItem (PopupMenu::Item (s) | |||
| .setEnabled (editorColourSchemeWindow == nullptr) | |||
| .setTicked (selectedEditorColourSchemeIndex == i) | |||
| .setAction ([this, i] { setEditorColourScheme (i, true); })); | |||
| ++i; | |||
| } | |||
| editorColourSchemeMenu.addSeparator(); | |||
| editorColourSchemeMenu.addItem (PopupMenu::Item ("Create...") | |||
| .setEnabled (editorColourSchemeWindow == nullptr) | |||
| .setAction ([this] { showEditorColourSchemeWindow(); })); | |||
| editorColourSchemeMenu.addSeparator(); | |||
| editorColourSchemeMenu.addItem (PopupMenu::Item ("Create...") | |||
| .setEnabled (editorColourSchemeWindow == nullptr) | |||
| .setAction ([this] { showEditorColourSchemeWindow(); })); | |||
| menu.addSubMenu ("Editor Colour Scheme", editorColourSchemeMenu); | |||
| menu.addSubMenu ("Editor Colour Scheme", editorColourSchemeMenu); | |||
| } | |||
| } | |||
| void ProjucerApplication::createWindowMenu (PopupMenu& menu) | |||
| PopupMenu ProjucerApplication::createWindowMenu() | |||
| { | |||
| PopupMenu menu; | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::goToPreviousWindow); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::goToNextWindow); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::closeWindow); | |||
| @@ -486,16 +512,22 @@ void ProjucerApplication::createWindowMenu (PopupMenu& menu) | |||
| int counter = 0; | |||
| for (auto* window : mainWindowList.windows) | |||
| { | |||
| if (window != nullptr) | |||
| { | |||
| if (auto* project = window->getProject()) | |||
| menu.addItem (openWindowsBaseID + counter++, project->getProjectNameString()); | |||
| } | |||
| } | |||
| menu.addSeparator(); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::closeAllWindows); | |||
| return menu; | |||
| } | |||
| void ProjucerApplication::createDocumentMenu (PopupMenu& menu) | |||
| PopupMenu ProjucerApplication::createDocumentMenu() | |||
| { | |||
| PopupMenu menu; | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::goToPreviousDoc); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::goToNextDoc); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::goToCounterpart); | |||
| @@ -511,34 +543,46 @@ void ProjucerApplication::createDocumentMenu (PopupMenu& menu) | |||
| menu.addSeparator(); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::closeAllDocuments); | |||
| return menu; | |||
| } | |||
| void ProjucerApplication::createToolsMenu (PopupMenu& menu) | |||
| PopupMenu ProjucerApplication::createToolsMenu() | |||
| { | |||
| PopupMenu menu; | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::showUTF8Tool); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::showSVGPathTool); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::showTranslationTool); | |||
| menu.addSeparator(); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::enableLiveBuild); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::enableGUIEditor); | |||
| return menu; | |||
| } | |||
| void ProjucerApplication::createHelpMenu (PopupMenu& menu) | |||
| PopupMenu ProjucerApplication::createHelpMenu() | |||
| { | |||
| PopupMenu menu; | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::showForum); | |||
| menu.addSeparator(); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::showAPIModules); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::showAPIClasses); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::showTutorials); | |||
| return menu; | |||
| } | |||
| void ProjucerApplication::createExtraAppleMenuItems (PopupMenu& menu) | |||
| PopupMenu ProjucerApplication::createExtraAppleMenuItems() | |||
| { | |||
| PopupMenu menu; | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::showAboutWindow); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::checkForNewVersion); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::enableNewVersionCheck); | |||
| menu.addSeparator(); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::showGlobalPathsWindow); | |||
| return menu; | |||
| } | |||
| void ProjucerApplication::createExamplesPopupMenu (PopupMenu& menu) noexcept | |||
| PopupMenu ProjucerApplication::createExamplesPopupMenu() noexcept | |||
| { | |||
| PopupMenu menu; | |||
| numExamples = 0; | |||
| for (auto& dir : getSortedExampleDirectories()) | |||
| { | |||
| @@ -561,8 +605,21 @@ void ProjucerApplication::createExamplesPopupMenu (PopupMenu& menu) noexcept | |||
| menu.addSeparator(); | |||
| menu.addCommandItem (commandManager.get(), CommandIDs::launchDemoRunner); | |||
| } | |||
| return menu; | |||
| } | |||
| #if JUCE_MAC | |||
| void ProjucerApplication::rebuildAppleMenu() | |||
| { | |||
| auto extraAppleMenuItems = createExtraAppleMenuItems(); | |||
| // workaround broken "Open Recent" submenu: not passing the | |||
| // submenu's title here avoids the defect in JuceMainMenuHandler::addMenuItem | |||
| MenuBarModel::setMacMainMenu (menuModel.get(), &extraAppleMenuItems); //, "Open Recent"); | |||
| } | |||
| #endif | |||
| //============================================================================== | |||
| static File getJUCEExamplesDirectoryPathFromGlobal() | |||
| { | |||
| @@ -907,12 +964,16 @@ void ProjucerApplication::getAllCommands (Array <CommandID>& commands) | |||
| CommandIDs::showGlobalPathsWindow, | |||
| CommandIDs::showUTF8Tool, | |||
| CommandIDs::showSVGPathTool, | |||
| CommandIDs::enableLiveBuild, | |||
| CommandIDs::enableGUIEditor, | |||
| CommandIDs::showAboutWindow, | |||
| CommandIDs::checkForNewVersion, | |||
| CommandIDs::enableNewVersionCheck, | |||
| CommandIDs::showForum, | |||
| CommandIDs::showAPIModules, | |||
| CommandIDs::showAPIClasses, | |||
| CommandIDs::showTutorials }; | |||
| CommandIDs::showTutorials, | |||
| CommandIDs::loginLogout }; | |||
| commands.addArray (ids, numElementsInArray (ids)); | |||
| } | |||
| @@ -990,6 +1051,20 @@ void ProjucerApplication::getCommandInfo (CommandID commandID, ApplicationComman | |||
| result.setInfo ("SVG Path Converter", "Shows the SVG->Path data conversion utility", CommandCategories::general, 0); | |||
| break; | |||
| case CommandIDs::enableLiveBuild: | |||
| result.setInfo ("Live-Build Enabled", | |||
| "Enables or disables the live-build functionality", | |||
| CommandCategories::general, | |||
| (isLiveBuildEnabled() ? ApplicationCommandInfo::isTicked : 0)); | |||
| break; | |||
| case CommandIDs::enableGUIEditor: | |||
| result.setInfo ("GUI Editor Enabled", | |||
| "Enables or disables the GUI editor functionality", | |||
| CommandCategories::general, | |||
| (isGUIEditorEnabled() ? ApplicationCommandInfo::isTicked : 0)); | |||
| break; | |||
| case CommandIDs::showAboutWindow: | |||
| result.setInfo ("About Projucer", "Shows the Projucer's 'About' page.", CommandCategories::general, 0); | |||
| break; | |||
| @@ -998,6 +1073,13 @@ void ProjucerApplication::getCommandInfo (CommandID commandID, ApplicationComman | |||
| result.setInfo ("Check for New Version...", "Checks the web server for a new version of JUCE", CommandCategories::general, 0); | |||
| break; | |||
| case CommandIDs::enableNewVersionCheck: | |||
| result.setInfo ("Automatically Check for New Versions", | |||
| "Enables automatic background checking for new versions of JUCE.", | |||
| CommandCategories::general, | |||
| (isAutomaticVersionCheckingEnabled() ? ApplicationCommandInfo::isTicked : 0)); | |||
| break; | |||
| case CommandIDs::showForum: | |||
| result.setInfo ("JUCE Community Forum", "Shows the JUCE community forum in a browser", CommandCategories::general, 0); | |||
| break; | |||
| @@ -1014,6 +1096,19 @@ void ProjucerApplication::getCommandInfo (CommandID commandID, ApplicationComman | |||
| result.setInfo ("JUCE Tutorials", "Shows the JUCE tutorials in a browser", CommandCategories::general, 0); | |||
| break; | |||
| case CommandIDs::loginLogout: | |||
| { | |||
| auto licenseState = licenseController->getCurrentState(); | |||
| if (licenseState.isGPL()) | |||
| result.setInfo ("Disable GPL mode", "Disables GPL mode", CommandCategories::general, 0); | |||
| else | |||
| result.setInfo (licenseState.isValid() ? String ("Sign out ") + licenseState.username + "..." : String ("Sign in..."), | |||
| "Log out of your JUCE account", | |||
| CommandCategories::general, 0); | |||
| break; | |||
| } | |||
| default: | |||
| JUCEApplication::getCommandInfo (commandID, result); | |||
| break; | |||
| @@ -1031,17 +1126,21 @@ bool ProjucerApplication::perform (const InvocationInfo& info) | |||
| case CommandIDs::launchDemoRunner: launchDemoRunner(); break; | |||
| case CommandIDs::saveAll: saveAllDocuments(); break; | |||
| case CommandIDs::closeAllWindows: closeAllMainWindowsAndQuitIfNeeded(); break; | |||
| case CommandIDs::closeAllDocuments: closeAllDocuments (true); break; | |||
| case CommandIDs::closeAllDocuments: closeAllDocuments (OpenDocumentManager::SaveIfNeeded::yes); break; | |||
| case CommandIDs::clearRecentFiles: clearRecentFiles(); break; | |||
| case CommandIDs::showUTF8Tool: showUTF8ToolWindow(); break; | |||
| case CommandIDs::showSVGPathTool: showSVGPathDataToolWindow(); break; | |||
| case CommandIDs::enableLiveBuild: enableOrDisableLiveBuild(); break; | |||
| case CommandIDs::enableGUIEditor: enableOrDisableGUIEditor(); break; | |||
| case CommandIDs::showGlobalPathsWindow: showPathsWindow (false); break; | |||
| case CommandIDs::showAboutWindow: showAboutWindow(); break; | |||
| case CommandIDs::checkForNewVersion: LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (true); break; | |||
| case CommandIDs::checkForNewVersion: LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (false); break; | |||
| case CommandIDs::enableNewVersionCheck: setAutomaticVersionCheckingEnabled (! isAutomaticVersionCheckingEnabled()); break; | |||
| case CommandIDs::showForum: launchForumBrowser(); break; | |||
| case CommandIDs::showAPIModules: launchModulesBrowser(); break; | |||
| case CommandIDs::showAPIClasses: launchClassesBrowser(); break; | |||
| case CommandIDs::showTutorials: launchTutorialsBrowser(); break; | |||
| case CommandIDs::loginLogout: doLoginOrLogout(); break; | |||
| default: return JUCEApplication::perform (info); | |||
| } | |||
| @@ -1104,7 +1203,7 @@ void ProjucerApplication::saveAllDocuments() | |||
| pcc->refreshProjectTreeFileStatuses(); | |||
| } | |||
| bool ProjucerApplication::closeAllDocuments (bool askUserToSave) | |||
| bool ProjucerApplication::closeAllDocuments (OpenDocumentManager::SaveIfNeeded askUserToSave) | |||
| { | |||
| return openDocumentManager.closeAll (askUserToSave); | |||
| } | |||
| @@ -1154,6 +1253,26 @@ void ProjucerApplication::showSVGPathDataToolWindow() | |||
| 500, 500, 300, 300, 1000, 1000); | |||
| } | |||
| bool ProjucerApplication::isLiveBuildEnabled() const | |||
| { | |||
| return getGlobalProperties().getBoolValue (Ids::liveBuildEnabled); | |||
| } | |||
| void ProjucerApplication::enableOrDisableLiveBuild() | |||
| { | |||
| getGlobalProperties().setValue (Ids::liveBuildEnabled, ! isLiveBuildEnabled()); | |||
| } | |||
| bool ProjucerApplication::isGUIEditorEnabled() const | |||
| { | |||
| return getGlobalProperties().getBoolValue (Ids::guiEditorEnabled); | |||
| } | |||
| void ProjucerApplication::enableOrDisableGUIEditor() | |||
| { | |||
| getGlobalProperties().setValue (Ids::guiEditorEnabled, ! isGUIEditorEnabled()); | |||
| } | |||
| void ProjucerApplication::showAboutWindow() | |||
| { | |||
| if (aboutWindow != nullptr) | |||
| @@ -1230,6 +1349,26 @@ void ProjucerApplication::launchTutorialsBrowser() | |||
| tutorialsLink.launchInDefaultBrowser(); | |||
| } | |||
| void ProjucerApplication::doLoginOrLogout() | |||
| { | |||
| if (licenseController->getCurrentState().type == LicenseState::Type::none) | |||
| { | |||
| if (auto* window = mainWindowList.getMainWindowWithLoginFormOpen()) | |||
| { | |||
| window->toFront (true); | |||
| } | |||
| else | |||
| { | |||
| mainWindowList.createWindowIfNoneAreOpen(); | |||
| mainWindowList.getFrontmostWindow()->showLoginFormOverlay(); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| licenseController->resetState(); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| struct FileWithTime | |||
| { | |||
| @@ -1286,13 +1425,6 @@ PropertiesFile::Options ProjucerApplication::getPropertyFileOptionsFor (const St | |||
| return options; | |||
| } | |||
| void ProjucerApplication::updateAllBuildTabs() | |||
| { | |||
| for (int i = 0; i < mainWindowList.windows.size(); ++i) | |||
| if (ProjectContentComponent* p = mainWindowList.windows.getUnchecked(i)->getProjectContentComponent()) | |||
| p->rebuildProjectTabs(); | |||
| } | |||
| void ProjucerApplication::initCommandManager() | |||
| { | |||
| commandManager.reset (new ApplicationCommandManager()); | |||
| @@ -1307,28 +1439,7 @@ void ProjucerApplication::initCommandManager() | |||
| registerGUIEditorCommands(); | |||
| } | |||
| void ProjucerApplication::showSetJUCEPathAlert() | |||
| { | |||
| auto& lf = Desktop::getInstance().getDefaultLookAndFeel(); | |||
| pathAlert.reset (lf.createAlertWindow ("Set JUCE Path", "Your global JUCE path is invalid. This path is used to access the JUCE examples and demo project - " | |||
| "would you like to set it now?", | |||
| "Set path", "Cancel", "Don't ask again", | |||
| AlertWindow::WarningIcon, 3, | |||
| mainWindowList.getFrontmostWindow (false))); | |||
| pathAlert->enterModalState (true, ModalCallbackFunction::create ([this] (int retVal) | |||
| { | |||
| pathAlert.reset (nullptr); | |||
| if (retVal == 1) | |||
| showPathsWindow (true); | |||
| else if (retVal == 0) | |||
| settings->setDontAskAboutJUCEPathAgain(); | |||
| })); | |||
| } | |||
| void rescanModules (AvailableModuleList& list, const Array<File>& paths, bool async) | |||
| void rescanModules (AvailableModulesList& list, const Array<File>& paths, bool async) | |||
| { | |||
| if (async) | |||
| list.scanPathsAsync (paths); | |||
| @@ -1338,12 +1449,32 @@ void rescanModules (AvailableModuleList& list, const Array<File>& paths, bool as | |||
| void ProjucerApplication::rescanJUCEPathModules() | |||
| { | |||
| rescanModules (jucePathModuleList, { getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()).get().toString() }, ! isRunningCommandLine); | |||
| rescanModules (jucePathModulesList, { getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()).get().toString() }, ! isRunningCommandLine); | |||
| } | |||
| void ProjucerApplication::rescanUserPathModules() | |||
| { | |||
| rescanModules (userPathsModuleList, { getAppSettings().getStoredPath (Ids::defaultUserModulePath, TargetOS::getThisOS()).get().toString() }, ! isRunningCommandLine); | |||
| rescanModules (userPathsModulesList, { getAppSettings().getStoredPath (Ids::defaultUserModulePath, TargetOS::getThisOS()).get().toString() }, ! isRunningCommandLine); | |||
| } | |||
| bool ProjucerApplication::isAutomaticVersionCheckingEnabled() const | |||
| { | |||
| return ! getGlobalProperties().getBoolValue (Ids::dontQueryForUpdate); | |||
| } | |||
| void ProjucerApplication::setAutomaticVersionCheckingEnabled (bool enabled) | |||
| { | |||
| getGlobalProperties().setValue (Ids::dontQueryForUpdate, ! enabled); | |||
| } | |||
| bool ProjucerApplication::shouldPromptUserAboutIncorrectJUCEPath() const | |||
| { | |||
| return ! getGlobalProperties().getBoolValue (Ids::dontAskAboutJUCEPath); | |||
| } | |||
| void ProjucerApplication::setShouldPromptUserAboutIncorrectJUCEPath (bool shouldPrompt) | |||
| { | |||
| getGlobalProperties().setValue (Ids::dontAskAboutJUCEPath, ! shouldPrompt); | |||
| } | |||
| void ProjucerApplication::selectEditorColourSchemeWithName (const String& schemeName) | |||
| @@ -1383,7 +1514,7 @@ void ProjucerApplication::setColourScheme (int index, bool saveSetting) | |||
| if (saveSetting) | |||
| { | |||
| auto& properties = settings->getGlobalProperties(); | |||
| auto& properties = getGlobalProperties(); | |||
| properties.setValue ("COLOUR SCHEME", index); | |||
| } | |||
| @@ -1403,7 +1534,7 @@ void ProjucerApplication::setEditorColourScheme (int index, bool saveSetting) | |||
| if (saveSetting) | |||
| { | |||
| auto& properties = settings->getGlobalProperties(); | |||
| auto& properties = getGlobalProperties(); | |||
| properties.setValue ("EDITOR COLOUR SCHEME", index); | |||
| } | |||
| @@ -1412,13 +1543,13 @@ void ProjucerApplication::setEditorColourScheme (int index, bool saveSetting) | |||
| getCommandManager().commandStatusChanged(); | |||
| } | |||
| bool ProjucerApplication::isEditorColourSchemeADefaultScheme (const StringArray& schemes, int editorColourSchemeIndex) | |||
| bool isEditorColourSchemeADefaultScheme (const StringArray& schemes, int editorColourSchemeIndex) | |||
| { | |||
| auto& schemeName = schemes[editorColourSchemeIndex]; | |||
| return (schemeName == "Default (Dark)" || schemeName == "Default (Light)"); | |||
| } | |||
| int ProjucerApplication::getEditorColourSchemeForGUIColourScheme (const StringArray& schemes, int guiColourSchemeIndex) | |||
| int getEditorColourSchemeForGUIColourScheme (const StringArray& schemes, int guiColourSchemeIndex) | |||
| { | |||
| auto defaultDarkEditorIndex = schemes.indexOf ("Default (Dark)"); | |||
| auto defaultLightEditorIndex = schemes.indexOf ("Default (Light)"); | |||
| @@ -18,8 +18,9 @@ | |||
| #pragma once | |||
| #include "UserAccount/jucer_LicenseController.h" | |||
| #include "jucer_MainWindow.h" | |||
| #include "../Project/jucer_Module.h" | |||
| #include "../Project/Modules/jucer_Modules.h" | |||
| #include "jucer_AutoUpdater.h" | |||
| #include "../CodeEditor/jucer_SourceCodeEditor.h" | |||
| #include "../Utility/UI/jucer_ProjucerLookAndFeel.h" | |||
| @@ -31,7 +32,7 @@ class ProjucerApplication : public JUCEApplication, | |||
| private AsyncUpdater | |||
| { | |||
| public: | |||
| ProjucerApplication(); | |||
| ProjucerApplication() = default; | |||
| static ProjucerApplication& getApp(); | |||
| static ApplicationCommandManager& getCommandManager(); | |||
| @@ -42,7 +43,6 @@ public: | |||
| void systemRequestedQuit() override; | |||
| void deleteLogger(); | |||
| //============================================================================== | |||
| const String getApplicationName() override { return "Projucer"; } | |||
| const String getApplicationVersion() override { return ProjectInfo::versionString; } | |||
| @@ -53,67 +53,34 @@ public: | |||
| //============================================================================== | |||
| MenuBarModel* getMenuModel(); | |||
| StringArray getMenuNames(); | |||
| void createMenu (PopupMenu&, const String& menuName); | |||
| void createFileMenu (PopupMenu&); | |||
| void createEditMenu (PopupMenu&); | |||
| void createViewMenu (PopupMenu&); | |||
| void createBuildMenu (PopupMenu&); | |||
| void createColourSchemeItems (PopupMenu&); | |||
| void createWindowMenu (PopupMenu&); | |||
| void createDocumentMenu (PopupMenu&); | |||
| void createToolsMenu (PopupMenu&); | |||
| void createHelpMenu (PopupMenu&); | |||
| void createExtraAppleMenuItems (PopupMenu&); | |||
| void handleMainMenuCommand (int menuItemID); | |||
| //============================================================================== | |||
| void getAllCommands (Array<CommandID>&) override; | |||
| void getCommandInfo (CommandID commandID, ApplicationCommandInfo&) override; | |||
| bool perform (const InvocationInfo&) override; | |||
| //============================================================================== | |||
| void createNewProject(); | |||
| void createNewProjectFromClipboard(); | |||
| void createNewPIP(); | |||
| void askUserToOpenFile(); | |||
| bool openFile (const File&); | |||
| void saveAllDocuments(); | |||
| bool closeAllDocuments (bool askUserToSave); | |||
| bool closeAllMainWindows(); | |||
| void closeAllMainWindowsAndQuitIfNeeded(); | |||
| void clearRecentFiles(); | |||
| PropertiesFile::Options getPropertyFileOptionsFor (const String& filename, bool isProjectSettings); | |||
| bool isLiveBuildEnabled() const; | |||
| bool isGUIEditorEnabled() const; | |||
| //============================================================================== | |||
| void showUTF8ToolWindow(); | |||
| void showSVGPathDataToolWindow(); | |||
| void showAboutWindow(); | |||
| bool openFile (const File&); | |||
| void showPathsWindow (bool highlightJUCEPath = false); | |||
| void showEditorColourSchemeWindow(); | |||
| void showPIPCreatorWindow(); | |||
| void launchForumBrowser(); | |||
| void launchModulesBrowser(); | |||
| void launchClassesBrowser(); | |||
| void launchTutorialsBrowser(); | |||
| void updateAllBuildTabs(); | |||
| //============================================================================== | |||
| PropertiesFile::Options getPropertyFileOptionsFor (const String& filename, bool isProjectSettings); | |||
| void selectEditorColourSchemeWithName (const String& schemeName); | |||
| static bool isEditorColourSchemeADefaultScheme (const StringArray& schemes, int editorColourSchemeIndex); | |||
| static int getEditorColourSchemeForGUIColourScheme (const StringArray& schemes, int guiColourSchemeIndex); | |||
| //============================================================================== | |||
| void rescanJUCEPathModules(); | |||
| void rescanUserPathModules(); | |||
| AvailableModuleList& getJUCEPathModuleList() { return jucePathModuleList; } | |||
| AvailableModuleList& getUserPathsModuleList() { return userPathsModuleList; } | |||
| AvailableModulesList& getJUCEPathModulesList() { return jucePathModulesList; } | |||
| AvailableModulesList& getUserPathsModulesList() { return userPathsModulesList; } | |||
| LicenseController& getLicenseController() { return *licenseController; } | |||
| bool isAutomaticVersionCheckingEnabled() const; | |||
| void setAutomaticVersionCheckingEnabled (bool shouldBeEnabled); | |||
| bool shouldPromptUserAboutIncorrectJUCEPath() const; | |||
| void setShouldPromptUserAboutIncorrectJUCEPath (bool shouldPrompt); | |||
| //============================================================================== | |||
| ProjucerLookAndFeel lookAndFeel; | |||
| @@ -128,23 +95,42 @@ public: | |||
| OpenDocumentManager openDocumentManager; | |||
| std::unique_ptr<ApplicationCommandManager> commandManager; | |||
| std::unique_ptr<Component> utf8Window, svgPathWindow, aboutWindow, pathsWindow, | |||
| editorColourSchemeWindow, pipCreatorWindow; | |||
| std::unique_ptr<FileLogger> logger; | |||
| bool isRunningCommandLine; | |||
| bool isRunningCommandLine = false; | |||
| std::unique_ptr<ChildProcessCache> childProcessCache; | |||
| private: | |||
| //============================================================================== | |||
| void handleAsyncUpdate() override; | |||
| void initCommandManager(); | |||
| void initCommandManager(); | |||
| bool initialiseLogger (const char* filePrefix); | |||
| void initialiseWindows (const String& commandLine); | |||
| void createExamplesPopupMenu (PopupMenu&) noexcept; | |||
| void createNewProject(); | |||
| void createNewProjectFromClipboard(); | |||
| void createNewPIP(); | |||
| void askUserToOpenFile(); | |||
| void saveAllDocuments(); | |||
| bool closeAllDocuments (OpenDocumentManager::SaveIfNeeded askUserToSave); | |||
| bool closeAllMainWindows(); | |||
| void closeAllMainWindowsAndQuitIfNeeded(); | |||
| void clearRecentFiles(); | |||
| StringArray getMenuNames(); | |||
| PopupMenu createMenu (const String& menuName); | |||
| PopupMenu createFileMenu(); | |||
| PopupMenu createEditMenu(); | |||
| PopupMenu createViewMenu(); | |||
| PopupMenu createBuildMenu(); | |||
| void createColourSchemeItems (PopupMenu&); | |||
| PopupMenu createWindowMenu(); | |||
| PopupMenu createDocumentMenu(); | |||
| PopupMenu createToolsMenu(); | |||
| PopupMenu createHelpMenu(); | |||
| PopupMenu createExtraAppleMenuItems(); | |||
| void handleMainMenuCommand (int menuItemID); | |||
| PopupMenu createExamplesPopupMenu() noexcept; | |||
| Array<File> getSortedExampleDirectories() noexcept; | |||
| Array<File> getSortedExampleFilesInDirectory (const File&) const noexcept; | |||
| bool findWindowAndOpenPIP (const File&); | |||
| @@ -155,22 +141,74 @@ private: | |||
| File tryToFindDemoRunnerProject(); | |||
| void launchDemoRunner(); | |||
| void showSetJUCEPathAlert(); | |||
| void setColourScheme (int index, bool saveSetting); | |||
| void setEditorColourScheme (int index, bool saveSetting); | |||
| void updateEditorColourSchemeIfNeeded(); | |||
| void showUTF8ToolWindow(); | |||
| void showSVGPathDataToolWindow(); | |||
| void showAboutWindow(); | |||
| void showEditorColourSchemeWindow(); | |||
| void showPIPCreatorWindow(); | |||
| void launchForumBrowser(); | |||
| void launchModulesBrowser(); | |||
| void launchClassesBrowser(); | |||
| void launchTutorialsBrowser(); | |||
| void doLoginOrLogout(); | |||
| void showLoginForm(); | |||
| void enableOrDisableLiveBuild(); | |||
| void enableOrDisableGUIEditor(); | |||
| //============================================================================== | |||
| void* server = nullptr; | |||
| #if JUCE_MAC | |||
| class AppleMenuRebuildListener : private MenuBarModel::Listener | |||
| { | |||
| public: | |||
| AppleMenuRebuildListener() | |||
| { | |||
| if (auto* model = ProjucerApplication::getApp().getMenuModel()) | |||
| model->addListener (this); | |||
| } | |||
| ~AppleMenuRebuildListener() override | |||
| { | |||
| if (auto* model = ProjucerApplication::getApp().getMenuModel()) | |||
| model->removeListener (this); | |||
| } | |||
| private: | |||
| void menuBarItemsChanged (MenuBarModel*) override {} | |||
| void menuCommandInvoked (MenuBarModel*, | |||
| const ApplicationCommandTarget::InvocationInfo& info) override | |||
| { | |||
| if (info.commandID == CommandIDs::enableNewVersionCheck) | |||
| Timer::callAfterDelay (50, [] { ProjucerApplication::getApp().rebuildAppleMenu(); }); | |||
| } | |||
| }; | |||
| void rebuildAppleMenu(); | |||
| std::unique_ptr<AppleMenuRebuildListener> appleMenuRebuildListener; | |||
| #endif | |||
| //============================================================================== | |||
| std::unique_ptr<LicenseController> licenseController; | |||
| void* server = nullptr; | |||
| std::unique_ptr<TooltipWindow> tooltipWindow; | |||
| AvailableModulesList jucePathModulesList, userPathsModulesList; | |||
| AvailableModuleList jucePathModuleList, userPathsModuleList; | |||
| std::unique_ptr<Component> utf8Window, svgPathWindow, aboutWindow, pathsWindow, | |||
| editorColourSchemeWindow, pipCreatorWindow; | |||
| std::unique_ptr<FileLogger> logger; | |||
| int numExamples = 0; | |||
| std::unique_ptr<AlertWindow> demoRunnerAlert; | |||
| std::unique_ptr<AlertWindow> pathAlert; | |||
| bool hasScannedForDemoRunnerExecutable = false, hasScannedForDemoRunnerProject = false; | |||
| File lastJUCEPath, lastDemoRunnerExectuableFile, lastDemoRunnerProjectFile; | |||
| #if JUCE_LINUX | |||
| @@ -32,11 +32,11 @@ LatestVersionCheckerAndUpdater::~LatestVersionCheckerAndUpdater() | |||
| clearSingletonInstance(); | |||
| } | |||
| void LatestVersionCheckerAndUpdater::checkForNewVersion (bool showAlerts) | |||
| void LatestVersionCheckerAndUpdater::checkForNewVersion (bool background) | |||
| { | |||
| if (! isThreadRunning()) | |||
| { | |||
| showAlertWindows = showAlerts; | |||
| backgroundCheck = background; | |||
| startThread (3); | |||
| } | |||
| } | |||
| @@ -48,7 +48,7 @@ void LatestVersionCheckerAndUpdater::run() | |||
| if (info == nullptr) | |||
| { | |||
| if (showAlertWindows) | |||
| if (! backgroundCheck) | |||
| AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, | |||
| "Update Server Communication Error", | |||
| "Failed to communicate with the JUCE update server.\n" | |||
| @@ -60,7 +60,7 @@ void LatestVersionCheckerAndUpdater::run() | |||
| if (! info->isNewerVersionThanCurrent()) | |||
| { | |||
| if (showAlertWindows) | |||
| if (! backgroundCheck) | |||
| AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon, | |||
| "No New Version Available", | |||
| "Your JUCE version is up to date."); | |||
| @@ -99,7 +99,7 @@ void LatestVersionCheckerAndUpdater::run() | |||
| } | |||
| } | |||
| if (showAlertWindows) | |||
| if (! backgroundCheck) | |||
| AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, | |||
| "Failed to find any new downloads", | |||
| "Please try again in a few minutes."); | |||
| @@ -137,15 +137,11 @@ public: | |||
| addAndMakeVisible (cancelButton); | |||
| cancelButton.onClick = [this] | |||
| { | |||
| if (dontAskAgainButton.getToggleState()) | |||
| getGlobalProperties().setValue (Ids::dontQueryForUpdate.toString(), 1); | |||
| else | |||
| getGlobalProperties().removeValue (Ids::dontQueryForUpdate); | |||
| ProjucerApplication::getApp().setAutomaticVersionCheckingEnabled (! dontAskAgainButton.getToggleState()); | |||
| exitModalStateWithResult (-1); | |||
| }; | |||
| dontAskAgainButton.setToggleState (getGlobalProperties().getValue (Ids::dontQueryForUpdate, {}).isNotEmpty(), dontSendNotification); | |||
| dontAskAgainButton.setToggleState (! ProjucerApplication::getApp().isAutomaticVersionCheckingEnabled(), dontSendNotification); | |||
| addAndMakeVisible (dontAskAgainButton); | |||
| juceIcon = Drawable::createFromImageData (BinaryData::juce_icon_png, | |||
| @@ -287,10 +283,21 @@ void LatestVersionCheckerAndUpdater::askUserForLocationToDownload (const Version | |||
| void LatestVersionCheckerAndUpdater::askUserAboutNewVersion (const String& newVersionString, | |||
| const String& releaseNotes, | |||
| const VersionInfo::Asset& asset) | |||
| { | |||
| if (backgroundCheck) | |||
| addNotificationToOpenProjects (asset); | |||
| else | |||
| showDialogWindow (newVersionString, releaseNotes, asset); | |||
| } | |||
| void LatestVersionCheckerAndUpdater::showDialogWindow (const String& newVersionString, | |||
| const String& releaseNotes, | |||
| const VersionInfo::Asset& asset) | |||
| { | |||
| dialogWindow = UpdateDialog::launchDialog (newVersionString, releaseNotes); | |||
| if (auto* mm = ModalComponentManager::getInstance()) | |||
| { | |||
| mm->attachCallback (dialogWindow.get(), | |||
| ModalCallbackFunction::create ([this, asset] (int result) | |||
| { | |||
| @@ -299,6 +306,35 @@ void LatestVersionCheckerAndUpdater::askUserAboutNewVersion (const String& newVe | |||
| dialogWindow.reset(); | |||
| })); | |||
| } | |||
| } | |||
| void LatestVersionCheckerAndUpdater::addNotificationToOpenProjects (const VersionInfo::Asset& asset) | |||
| { | |||
| for (auto* window : ProjucerApplication::getApp().mainWindowList.windows) | |||
| { | |||
| if (auto* project = window->getProject()) | |||
| { | |||
| Component::SafePointer<MainWindow> safeWindow (window); | |||
| auto ignore = [safeWindow] | |||
| { | |||
| if (safeWindow != nullptr) | |||
| safeWindow->getProject()->removeProjectMessage (ProjectMessages::Ids::newVersionAvailable); | |||
| }; | |||
| auto dontAskAgain = [ignore] | |||
| { | |||
| ignore(); | |||
| ProjucerApplication::getApp().setAutomaticVersionCheckingEnabled (false); | |||
| }; | |||
| project->addProjectMessage (ProjectMessages::Ids::newVersionAvailable, | |||
| { { "Download", [this, asset] { askUserForLocationToDownload (asset); } }, | |||
| { "Ignore", std::move (ignore) }, | |||
| { "Don't ask again", std::move (dontAskAgain) } }); | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| @@ -29,7 +29,7 @@ public: | |||
| LatestVersionCheckerAndUpdater(); | |||
| ~LatestVersionCheckerAndUpdater() override; | |||
| void checkForNewVersion (bool showAlerts); | |||
| void checkForNewVersion (bool isBackgroundCheck); | |||
| //============================================================================== | |||
| JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (LatestVersionCheckerAndUpdater) | |||
| @@ -41,8 +41,11 @@ private: | |||
| void askUserForLocationToDownload (const VersionInfo::Asset&); | |||
| void downloadAndInstall (const VersionInfo::Asset&, const File&); | |||
| void showDialogWindow (const String&, const String&, const VersionInfo::Asset&); | |||
| void addNotificationToOpenProjects (const VersionInfo::Asset&); | |||
| //============================================================================== | |||
| bool showAlertWindows = false; | |||
| bool backgroundCheck = false; | |||
| std::unique_ptr<DownloadAndInstallThread> installer; | |||
| std::unique_ptr<Component> dialogWindow; | |||
| @@ -48,6 +48,9 @@ namespace CommandIDs | |||
| showSVGPathTool = 0x300023, | |||
| showAboutWindow = 0x300024, | |||
| checkForNewVersion = 0x300025, | |||
| enableNewVersionCheck = 0x300026, | |||
| enableLiveBuild = 0x300027, | |||
| enableGUIEditor = 0x300028, | |||
| showProjectSettings = 0x300030, | |||
| showProjectTab = 0x300031, | |||
| @@ -91,10 +94,14 @@ namespace CommandIDs | |||
| nextError = 0x300080, | |||
| prevError = 0x300081, | |||
| showForum = 0x300090, | |||
| showAPIModules = 0x300091, | |||
| showAPIClasses = 0x300092, | |||
| showTutorials = 0x300093, | |||
| loginLogout = 0x300090, | |||
| showForum = 0x300100, | |||
| showAPIModules = 0x300101, | |||
| showAPIClasses = 0x300102, | |||
| showTutorials = 0x300103, | |||
| addNewGUIFile = 0x300200, | |||
| lastCommandIDEntry | |||
| }; | |||
| @@ -90,8 +90,8 @@ namespace | |||
| if (! justSaveResources) | |||
| rescanModulePathsIfNecessary(); | |||
| auto error = justSaveResources ? project->saveResourcesOnly (project->getFile()) | |||
| : project->saveProject (project->getFile(), true); | |||
| auto error = justSaveResources ? project->saveResourcesOnly() | |||
| : project->saveProject(); | |||
| project.reset(); | |||
| @@ -230,7 +230,7 @@ namespace | |||
| << "Name: " << proj.project->getProjectNameString() << std::endl | |||
| << "UID: " << proj.project->getProjectUIDString() << std::endl; | |||
| EnabledModuleList& modules = proj.project->getEnabledModules(); | |||
| auto& modules = proj.project->getEnabledModules(); | |||
| if (int numModules = modules.getNumModules()) | |||
| { | |||
| @@ -307,7 +307,7 @@ namespace | |||
| var moduleInfo (new DynamicObject()); | |||
| moduleInfo.getDynamicObject()->setProperty ("file", getModulePackageName (module)); | |||
| moduleInfo.getDynamicObject()->setProperty ("info", module.moduleInfo.moduleInfo); | |||
| moduleInfo.getDynamicObject()->setProperty ("info", module.moduleInfo.getModuleInfo()); | |||
| infoList.append (moduleInfo); | |||
| } | |||
| } | |||
| @@ -22,6 +22,7 @@ | |||
| #include "../CodeEditor/jucer_OpenDocumentManager.h" | |||
| #include "../CodeEditor/jucer_SourceCodeEditor.h" | |||
| #include "../Utility/UI/PropertyComponents/jucer_FilePathPropertyComponent.h" | |||
| #include "../Project/UI/jucer_ProjectContentComponent.h" | |||
| #include "../Project/UI/Sidebar/jucer_TreeItemTypes.h" | |||
| #include "Windows/jucer_UTF8WindowComponent.h" | |||
| #include "Windows/jucer_SVGPathDataWindowComponent.h" | |||
| @@ -22,6 +22,90 @@ | |||
| #include "../Wizards/jucer_NewProjectWizardClasses.h" | |||
| #include "../Utility/UI/jucer_JucerTreeViewBase.h" | |||
| #include "../ProjectSaving/jucer_ProjectSaver.h" | |||
| #include "UserAccount/jucer_LoginFormComponent.h" | |||
| #include "../Project/UI/jucer_ProjectContentComponent.h" | |||
| //============================================================================== | |||
| class BlurOverlayWithComponent : public Component, | |||
| private ComponentMovementWatcher, | |||
| private AsyncUpdater | |||
| { | |||
| public: | |||
| BlurOverlayWithComponent (MainWindow& window, std::unique_ptr<Component> comp) | |||
| : ComponentMovementWatcher (&window), | |||
| mainWindow (window), | |||
| componentToShow (std::move (comp)) | |||
| { | |||
| kernel.createGaussianBlur (1.25f); | |||
| addAndMakeVisible (*componentToShow); | |||
| setAlwaysOnTop (true); | |||
| setOpaque (true); | |||
| setVisible (true); | |||
| static_cast<Component&> (mainWindow).addChildComponent (this); | |||
| componentMovedOrResized (true, true); | |||
| } | |||
| void resized() override | |||
| { | |||
| setBounds (mainWindow.getLocalBounds()); | |||
| componentToShow->centreWithSize (componentToShow->getWidth(), componentToShow->getHeight()); | |||
| refreshBackgroundImage(); | |||
| } | |||
| void paint (Graphics& g) override | |||
| { | |||
| g.drawImage (componentImage, getLocalBounds().toFloat()); | |||
| } | |||
| private: | |||
| void componentPeerChanged() override {} | |||
| void componentVisibilityChanged() override {} | |||
| using ComponentMovementWatcher::componentVisibilityChanged; | |||
| void componentMovedOrResized (bool, bool) override { triggerAsyncUpdate(); } | |||
| using ComponentMovementWatcher::componentMovedOrResized; | |||
| void handleAsyncUpdate() override { resized(); } | |||
| void mouseUp (const MouseEvent& event) override | |||
| { | |||
| if (event.eventComponent == this) | |||
| mainWindow.hideLoginFormOverlay(); | |||
| } | |||
| void lookAndFeelChanged() override | |||
| { | |||
| refreshBackgroundImage(); | |||
| repaint(); | |||
| } | |||
| void refreshBackgroundImage() | |||
| { | |||
| setVisible (false); | |||
| auto parentBounds = mainWindow.getBounds(); | |||
| componentImage = mainWindow.createComponentSnapshot (mainWindow.getLocalBounds()) | |||
| .rescaled (roundToInt (parentBounds.getWidth() / 1.75f), roundToInt (parentBounds.getHeight() / 1.75f)); | |||
| kernel.applyToImage (componentImage, componentImage, getLocalBounds()); | |||
| setVisible (true); | |||
| } | |||
| //============================================================================== | |||
| MainWindow& mainWindow; | |||
| std::unique_ptr<Component> componentToShow; | |||
| ImageConvolutionKernel kernel { 3 }; | |||
| Image componentImage; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BlurOverlayWithComponent) | |||
| }; | |||
| //============================================================================== | |||
| MainWindow::MainWindow() | |||
| @@ -32,6 +116,8 @@ MainWindow::MainWindow() | |||
| false) | |||
| { | |||
| setUsingNativeTitleBar (true); | |||
| setResizable (true, false); | |||
| setResizeLimits (600, 500, 32000, 32000); | |||
| #if ! JUCE_MAC | |||
| setMenuBar (ProjucerApplication::getApp().getMenuModel()); | |||
| @@ -39,9 +125,6 @@ MainWindow::MainWindow() | |||
| createProjectContentCompIfNeeded(); | |||
| setResizable (true, false); | |||
| centreWithSize (800, 600); | |||
| auto& commandManager = ProjucerApplication::getCommandManager(); | |||
| auto registerAllAppCommands = [&] | |||
| @@ -65,9 +148,10 @@ MainWindow::MainWindow() | |||
| setWantsKeyboardFocus (false); | |||
| getLookAndFeel().setColour (ColourSelector::backgroundColourId, Colours::transparentBlack); | |||
| projectNameValue.addListener (this); | |||
| setResizeLimits (600, 500, 32000, 32000); | |||
| centreWithSize (800, 600); | |||
| } | |||
| MainWindow::~MainWindow() | |||
| @@ -77,10 +161,11 @@ MainWindow::~MainWindow() | |||
| #endif | |||
| removeKeyListener (ProjucerApplication::getCommandManager().getKeyMappings()); | |||
| // save the current size and position to our settings file.. | |||
| getGlobalProperties().setValue ("lastMainWindowPos", getWindowStateAsString()); | |||
| clearContentComponent(); | |||
| currentProject.reset(); | |||
| } | |||
| void MainWindow::createProjectContentCompIfNeeded() | |||
| @@ -127,7 +212,7 @@ void MainWindow::closeButtonPressed() | |||
| ProjucerApplication::getApp().mainWindowList.closeWindow (this); | |||
| } | |||
| bool MainWindow::closeCurrentProject (bool askUserToSave) | |||
| bool MainWindow::closeCurrentProject (OpenDocumentManager::SaveIfNeeded askUserToSave) | |||
| { | |||
| if (currentProject == nullptr) | |||
| return true; | |||
| @@ -144,7 +229,8 @@ bool MainWindow::closeCurrentProject (bool askUserToSave) | |||
| if (ProjucerApplication::getApp().openDocumentManager | |||
| .closeAllDocumentsUsingProject (*currentProject, askUserToSave)) | |||
| { | |||
| if (! askUserToSave || (currentProject->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk)) | |||
| if (askUserToSave == OpenDocumentManager::SaveIfNeeded::no | |||
| || (currentProject->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk)) | |||
| { | |||
| setProject (nullptr); | |||
| return true; | |||
| @@ -154,19 +240,16 @@ bool MainWindow::closeCurrentProject (bool askUserToSave) | |||
| return false; | |||
| } | |||
| void MainWindow::moveProject (File newProjectFileToOpen) | |||
| void MainWindow::moveProject (File newProjectFileToOpen, OpenInIDE openInIDE) | |||
| { | |||
| auto openInIDE = currentProject->shouldOpenInIDEAfterSaving(); | |||
| closeCurrentProject (false); | |||
| closeCurrentProject (OpenDocumentManager::SaveIfNeeded::no); | |||
| openFile (newProjectFileToOpen); | |||
| if (currentProject != nullptr) | |||
| { | |||
| ProjucerApplication::getApp().getCommandManager().invokeDirectly (openInIDE ? CommandIDs::saveAndOpenInIDE | |||
| : CommandIDs::saveProject, | |||
| false); | |||
| } | |||
| ProjucerApplication::getApp().getCommandManager() | |||
| .invokeDirectly (openInIDE == OpenInIDE::yes ? CommandIDs::saveAndOpenInIDE | |||
| : CommandIDs::saveProject, | |||
| false); | |||
| } | |||
| void MainWindow::setProject (std::unique_ptr<Project> newProject) | |||
| @@ -174,7 +257,7 @@ void MainWindow::setProject (std::unique_ptr<Project> newProject) | |||
| if (newProject == nullptr) | |||
| { | |||
| getProjectContentComponent()->setProject (nullptr); | |||
| projectNameValue.referTo (Value()); | |||
| projectNameValue.referTo ({}); | |||
| currentProject.reset(); | |||
| } | |||
| @@ -222,7 +305,7 @@ bool MainWindow::openFile (const File& file) | |||
| auto newDoc = std::make_unique<Project> (file); | |||
| auto result = newDoc->loadFrom (file, true); | |||
| if (result.wasOk() && closeCurrentProject (true)) | |||
| if (result.wasOk() && closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes)) | |||
| { | |||
| setProject (std::move (newDoc)); | |||
| currentProject->setChangedFlag (false); | |||
| @@ -350,7 +433,7 @@ void MainWindow::openPIP (PIPGenerator& generator) | |||
| { | |||
| project->setTemporaryDirectory (generator.getOutputDirectory()); | |||
| ProjectSaver liveBuildSaver (*project, project->getFile()); | |||
| ProjectSaver liveBuildSaver (*project); | |||
| liveBuildSaver.saveContentNeededForLiveBuild(); | |||
| if (auto* pcc = window->getProjectContentComponent()) | |||
| @@ -440,49 +523,6 @@ void MainWindow::activeWindowStatusChanged() | |||
| pcc->updateMissingFileStatuses(); | |||
| ProjucerApplication::getApp().openDocumentManager.reloadModifiedFiles(); | |||
| if (auto* p = getProject()) | |||
| { | |||
| if (p->hasProjectBeenModified()) | |||
| { | |||
| Component::SafePointer<Component> safePointer (this); | |||
| MessageManager::callAsync ([=] () | |||
| { | |||
| if (safePointer == nullptr) | |||
| return; // bail out if the window has been deleted | |||
| auto result = AlertWindow::showOkCancelBox (AlertWindow::QuestionIcon, | |||
| TRANS ("The .jucer file has been modified since the last save."), | |||
| TRANS ("Do you want to keep the current project or re-load from disk?"), | |||
| TRANS ("Keep"), | |||
| TRANS ("Re-load from disk")); | |||
| if (safePointer == nullptr) | |||
| return; | |||
| if (result == 0) | |||
| { | |||
| if (auto* project = getProject()) | |||
| { | |||
| auto oldTemporaryDirectory = project->getTemporaryDirectory(); | |||
| auto projectFile = project->getFile(); | |||
| setProject (nullptr); | |||
| openFile (projectFile); | |||
| if (oldTemporaryDirectory != File()) | |||
| if (auto* newProject = getProject()) | |||
| newProject->setTemporaryDirectory (oldTemporaryDirectory); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| ProjucerApplication::getApp().getCommandManager().invokeDirectly (CommandIDs::saveProject, true); | |||
| } | |||
| }); | |||
| } | |||
| } | |||
| } | |||
| void MainWindow::showStartPage() | |||
| @@ -498,6 +538,18 @@ void MainWindow::showStartPage() | |||
| getContentComponent()->grabKeyboardFocus(); | |||
| } | |||
| void MainWindow::showLoginFormOverlay() | |||
| { | |||
| blurOverlayComponent = std::make_unique<BlurOverlayWithComponent> (*this, std::make_unique<LoginFormComponent> (*this)); | |||
| loginFormOpen = true; | |||
| } | |||
| void MainWindow::hideLoginFormOverlay() | |||
| { | |||
| blurOverlayComponent.reset(); | |||
| loginFormOpen = false; | |||
| } | |||
| //============================================================================== | |||
| ApplicationCommandTarget* MainWindow::getNextCommandTarget() | |||
| { | |||
| @@ -565,12 +617,11 @@ bool MainWindow::perform (const InvocationInfo& info) | |||
| return true; | |||
| } | |||
| void MainWindow::valueChanged (Value&) | |||
| void MainWindow::valueChanged (Value& value) | |||
| { | |||
| if (currentProject != nullptr) | |||
| setName (currentProject->getProjectNameString() + " - Projucer"); | |||
| else | |||
| setName ("Projucer"); | |||
| if (value == projectNameValue) | |||
| setName (currentProject != nullptr ? currentProject->getProjectNameString() + " - Projucer" | |||
| : "Projucer"); | |||
| } | |||
| //============================================================================== | |||
| @@ -589,7 +640,7 @@ bool MainWindowList::askAllWindowsToClose() | |||
| while (windows.size() > 0) | |||
| { | |||
| if (! windows[0]->closeCurrentProject (true)) | |||
| if (! windows[0]->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes)) | |||
| return false; | |||
| windows.remove (0); | |||
| @@ -616,7 +667,7 @@ void MainWindowList::closeWindow (MainWindow* w) | |||
| else | |||
| #endif | |||
| { | |||
| if (w->closeCurrentProject (true)) | |||
| if (w->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes)) | |||
| { | |||
| windows.removeObject (w); | |||
| saveCurrentlyOpenProjectList(); | |||
| @@ -766,6 +817,15 @@ MainWindow* MainWindowList::getMainWindowForFile (const File& file) | |||
| return nullptr; | |||
| } | |||
| MainWindow* MainWindowList::getMainWindowWithLoginFormOpen() | |||
| { | |||
| for (auto* window : windows) | |||
| if (window->isShowingLoginForm()) | |||
| return window; | |||
| return nullptr; | |||
| } | |||
| void MainWindowList::checkWindowBounds (MainWindow& windowToCheck) | |||
| { | |||
| auto avoidSuperimposedWindows = [&] | |||
| @@ -18,8 +18,11 @@ | |||
| #pragma once | |||
| #include "../Project/UI/jucer_ProjectContentComponent.h" | |||
| #include "../Utility/PIPs/jucer_PIPGenerator.h" | |||
| #include "../Project/jucer_Project.h" | |||
| #include "../CodeEditor/jucer_OpenDocumentManager.h" | |||
| class ProjectContentComponent; | |||
| //============================================================================== | |||
| /** | |||
| @@ -36,6 +39,8 @@ public: | |||
| MainWindow(); | |||
| ~MainWindow() override; | |||
| enum class OpenInIDE { no, yes }; | |||
| //============================================================================== | |||
| void closeButtonPressed() override; | |||
| @@ -50,11 +55,15 @@ public: | |||
| void makeVisible(); | |||
| void restoreWindowPosition(); | |||
| bool closeCurrentProject (bool askToSave); | |||
| void moveProject (File newProjectFile); | |||
| bool closeCurrentProject (OpenDocumentManager::SaveIfNeeded askToSave); | |||
| void moveProject (File newProjectFile, OpenInIDE openInIDE); | |||
| void showStartPage(); | |||
| void showLoginFormOverlay(); | |||
| void hideLoginFormOverlay(); | |||
| bool isShowingLoginForm() const noexcept { return loginFormOpen; } | |||
| bool isInterestedInFileDrag (const StringArray& files) override; | |||
| void filesDropped (const StringArray& filenames, int mouseX, int mouseY) override; | |||
| @@ -71,16 +80,18 @@ public: | |||
| bool shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails, | |||
| StringArray& files, bool& canMoveFiles) override; | |||
| private: | |||
| std::unique_ptr<Project> currentProject; | |||
| Value projectNameValue; | |||
| void valueChanged (Value&) override; | |||
| static const char* getProjectWindowPosName() { return "projectWindowPos"; } | |||
| void createProjectContentCompIfNeeded(); | |||
| void setTitleBarIcon(); | |||
| void openPIP (PIPGenerator&); | |||
| void valueChanged (Value&) override; | |||
| std::unique_ptr<Project> currentProject; | |||
| Value projectNameValue; | |||
| std::unique_ptr<Component> blurOverlayComponent; | |||
| bool loginFormOpen = false; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow) | |||
| }; | |||
| @@ -105,6 +116,7 @@ public: | |||
| MainWindow* getFrontmostWindow (bool createIfNotFound = true); | |||
| MainWindow* getOrCreateEmptyWindow(); | |||
| MainWindow* getMainWindowForFile (const File&); | |||
| MainWindow* getMainWindowWithLoginFormOpen(); | |||
| Project* getFrontmostProject(); | |||
| @@ -1,50 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <!-- Generator: Adobe Illustrator 21.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | |||
| <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | |||
| viewBox="0 0 169.7 205.2" style="enable-background:new 0 0 169.7 205.2;" xml:space="preserve"> | |||
| <style type="text/css"> | |||
| .st0{fill:#A65A95;} | |||
| .st1{fill:#001946;} | |||
| </style> | |||
| <g> | |||
| <g> | |||
| <path class="st0" d="M45.2,167.8c-3,0-5.4-1.1-7.8-4.3l3.6-3.1c1.6,2.1,2.7,2.7,4.2,2.7c2.6,0,4.4-2,4.4-5v-17.2h5V158 | |||
| C54.6,163.9,50.7,167.8,45.2,167.8z"/> | |||
| <path class="st0" d="M70.7,167.8c-5.8,0-10.8-4.2-10.8-11.2v-15.8h5v15.6c0,4.1,2.4,6.7,5.8,6.7c3.4,0,5.8-2.6,5.8-6.7v-15.6h5 | |||
| v15.8C81.5,163.5,76.5,167.8,70.7,167.8z"/> | |||
| <path class="st0" d="M99,167.8c-7.6,0-13.9-6.1-13.9-13.6c0-7.6,6.3-13.6,13.9-13.6c3.4,0,6.3,1.2,9.1,3.4l-2.9,3.6 | |||
| c-2.6-1.8-4.1-2.4-6.2-2.4c-4.8,0-8.8,3.9-8.8,9c0,5,3.9,9,8.8,9c2,0,3.6-0.6,6.1-2.4l3,3.7C104.6,167,102,167.8,99,167.8z"/> | |||
| <path class="st0" d="M111.3,167.4v-26.6h16.6v4.5h-11.6v6.4h11.2v4.5h-11.2v6.7h11.6v4.5H111.3z"/> | |||
| </g> | |||
| <g> | |||
| <circle class="st1" cx="84.9" cy="74.9" r="37.4"/> | |||
| <circle class="st0" cx="84.9" cy="74.9" r="28"/> | |||
| <circle class="st1" cx="84.9" cy="67.9" r="2.1"/> | |||
| <circle class="st1" cx="91.4" cy="72.6" r="2.1"/> | |||
| <circle class="st1" cx="88.9" cy="80.3" r="2.1"/> | |||
| <circle class="st1" cx="80.8" cy="80.3" r="2.1"/> | |||
| <circle class="st1" cx="78.3" cy="72.6" r="2.1"/> | |||
| </g> | |||
| <g> | |||
| <path class="st1" d="M48.2,131.7v-4.6h-4.3v4.6h-2V121h2v4.3h4.3V121h2v10.8H48.2z"/> | |||
| <path class="st1" d="M56.7,131.7v-0.5c-0.5,0.4-1.3,0.7-2.1,0.7c-1.9,0-3.2-1.3-3.2-3.4v-5h2v4.8c0,1.2,0.7,1.8,1.7,1.8 | |||
| c1,0,1.7-0.7,1.7-1.8v-4.8h2v8.3H56.7z"/> | |||
| <path class="st1" d="M63.8,131.9c-2.4,0-4.3-1.9-4.3-4.3c0-2.4,1.9-4.3,4.3-4.3c1.1,0,2.1,0.4,3.3,1.5l-1.3,1.2 | |||
| c-0.7-0.7-1.3-1-2-1c-1.3,0-2.3,1.1-2.3,2.5c0,1.3,1,2.5,2.3,2.5c0.6,0,1.3-0.2,2-0.9l1.3,1.2C66,131.5,65,131.9,63.8,131.9z"/> | |||
| <path class="st1" d="M73.8,131.7l-3.9-3.9v3.9h-2v-11.3h2v6.8l3.6-3.7h2.5l-3.9,4l4.3,4.3H73.8z"/> | |||
| <path class="st1" d="M77.1,131.7v-11.3h2v11.3H77.1z"/> | |||
| <path class="st1" d="M88.6,128.4h-6.2c0.1,1,1.1,1.7,2.1,1.7c0.7,0,1.5-0.3,2.3-1.1l1.3,1.2c-1.1,1.2-2.3,1.7-3.6,1.7 | |||
| c-2.4,0-4.3-1.9-4.3-4.3c0-2.4,1.9-4.3,4.3-4.3c2.4,0,4.1,1.9,4.1,4.2C88.7,127.9,88.6,128.4,88.6,128.4z M84.5,125.1 | |||
| c-1,0-1.9,0.6-2.1,1.5h4.1C86.3,125.8,85.5,125.1,84.5,125.1z"/> | |||
| <path class="st1" d="M93.9,131.9c-0.9,0-1.7-0.3-2.3-0.8v0.6h-2v-11.3h2v3.7c0.6-0.5,1.4-0.8,2.3-0.8c2.3,0,4.2,1.9,4.2,4.3 | |||
| C98.1,129.9,96.2,131.9,93.9,131.9z M93.9,125.1c-1.3,0-2.2,1.1-2.2,2.5c0,1.4,1,2.5,2.2,2.5c1.3,0,2.2-1.1,2.2-2.5 | |||
| C96.2,126.2,95.2,125.1,93.9,125.1z"/> | |||
| <path class="st1" d="M107.4,128.4h-6.2c0.1,1,1.1,1.7,2.1,1.7c0.7,0,1.5-0.3,2.3-1.1l1.3,1.2c-1.1,1.2-2.3,1.7-3.6,1.7 | |||
| c-2.4,0-4.3-1.9-4.3-4.3c0-2.4,1.9-4.3,4.3-4.3c2.4,0,4.1,1.9,4.1,4.2C107.5,127.9,107.4,128.4,107.4,128.4z M103.3,125.1 | |||
| c-1,0-1.9,0.6-2.1,1.5h4.1C105.1,125.8,104.3,125.1,103.3,125.1z"/> | |||
| <path class="st1" d="M110.4,127.8v3.9h-2v-8.3h2v0.8c0.7-0.6,1.4-0.9,2.6-1v1.8C111,125.4,110.4,126.6,110.4,127.8z"/> | |||
| <path class="st1" d="M116,127.8v3.9h-2v-8.3h2v0.8c0.7-0.6,1.4-0.9,2.6-1v1.8C116.6,125.4,116,126.6,116,127.8z"/> | |||
| <path class="st1" d="M122.2,134.6h-2.1l2.2-4.2l-3.4-6.9h2.1l2.4,4.8l2.3-4.8h2.2L122.2,134.6z"/> | |||
| </g> | |||
| </g> | |||
| </svg> | |||
| @@ -19,7 +19,7 @@ | |||
| #include "../Application/jucer_Headers.h" | |||
| #include "jucer_DocumentEditorComponent.h" | |||
| #include "../Application/jucer_Application.h" | |||
| #include "../Project/UI/jucer_ProjectContentComponent.h" | |||
| //============================================================================== | |||
| DocumentEditorComponent::DocumentEditorComponent (OpenDocumentManager::Document* doc) | |||
| @@ -181,11 +181,11 @@ FileBasedDocument::SaveResult OpenDocumentManager::saveIfNeededAndUserAgrees (Op | |||
| } | |||
| bool OpenDocumentManager::closeDocument (int index, bool saveIfNeeded) | |||
| bool OpenDocumentManager::closeDocument (int index, SaveIfNeeded saveIfNeeded) | |||
| { | |||
| if (Document* doc = documents [index]) | |||
| { | |||
| if (saveIfNeeded) | |||
| if (saveIfNeeded == SaveIfNeeded::yes) | |||
| if (saveIfNeededAndUserAgrees (doc) != FileBasedDocument::savedOk) | |||
| return false; | |||
| @@ -206,12 +206,12 @@ bool OpenDocumentManager::closeDocument (int index, bool saveIfNeeded) | |||
| return true; | |||
| } | |||
| bool OpenDocumentManager::closeDocument (Document* document, bool saveIfNeeded) | |||
| bool OpenDocumentManager::closeDocument (Document* document, SaveIfNeeded saveIfNeeded) | |||
| { | |||
| return closeDocument (documents.indexOf (document), saveIfNeeded); | |||
| } | |||
| void OpenDocumentManager::closeFile (const File& f, bool saveIfNeeded) | |||
| void OpenDocumentManager::closeFile (const File& f, SaveIfNeeded saveIfNeeded) | |||
| { | |||
| for (int i = documents.size(); --i >= 0;) | |||
| if (Document* d = documents[i]) | |||
| @@ -219,7 +219,7 @@ void OpenDocumentManager::closeFile (const File& f, bool saveIfNeeded) | |||
| closeDocument (i, saveIfNeeded); | |||
| } | |||
| bool OpenDocumentManager::closeAll (bool askUserToSave) | |||
| bool OpenDocumentManager::closeAll (SaveIfNeeded askUserToSave) | |||
| { | |||
| for (int i = getNumOpenDocuments(); --i >= 0;) | |||
| if (! closeDocument (i, askUserToSave)) | |||
| @@ -228,7 +228,7 @@ bool OpenDocumentManager::closeAll (bool askUserToSave) | |||
| return true; | |||
| } | |||
| bool OpenDocumentManager::closeAllDocumentsUsingProject (Project& project, bool saveIfNeeded) | |||
| bool OpenDocumentManager::closeAllDocumentsUsingProject (Project& project, SaveIfNeeded saveIfNeeded) | |||
| { | |||
| for (int i = documents.size(); --i >= 0;) | |||
| if (Document* d = documents[i]) | |||
| @@ -61,13 +61,15 @@ public: | |||
| Document* getOpenDocument (int index) const; | |||
| void clear(); | |||
| enum class SaveIfNeeded { no, yes }; | |||
| bool canOpenFile (const File& file); | |||
| Document* openFile (Project* project, const File& file); | |||
| bool closeDocument (int index, bool saveIfNeeded); | |||
| bool closeDocument (Document* document, bool saveIfNeeded); | |||
| bool closeAll (bool askUserToSave); | |||
| bool closeAllDocumentsUsingProject (Project& project, bool saveIfNeeded); | |||
| void closeFile (const File& f, bool saveIfNeeded); | |||
| bool closeDocument (int index, SaveIfNeeded saveIfNeeded); | |||
| bool closeDocument (Document* document, SaveIfNeeded saveIfNeeded); | |||
| bool closeAll (SaveIfNeeded askUserToSave); | |||
| bool closeAllDocumentsUsingProject (Project& project, SaveIfNeeded saveIfNeeded); | |||
| void closeFile (const File& f, SaveIfNeeded saveIfNeeded); | |||
| bool anyFilesNeedSaving() const; | |||
| bool saveAll(); | |||
| FileBasedDocument::SaveResult saveIfNeededAndUserAgrees (Document* doc); | |||
| @@ -1210,11 +1210,14 @@ Image JucerDocumentEditor::createComponentLayerSnapshot() const | |||
| const int gridSnapMenuItemBase = 0x8723620; | |||
| const int snapSizes[] = { 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32 }; | |||
| void createGUIEditorMenu (PopupMenu&); | |||
| void createGUIEditorMenu (PopupMenu& menu) | |||
| PopupMenu createGUIEditorMenu() | |||
| { | |||
| PopupMenu menu; | |||
| auto* commandManager = &ProjucerApplication::getCommandManager(); | |||
| menu.addCommandItem (commandManager, CommandIDs::addNewGUIFile); | |||
| menu.addSeparator(); | |||
| menu.addCommandItem (commandManager, JucerCommandIDs::editCompLayout); | |||
| menu.addCommandItem (commandManager, JucerCommandIDs::editCompGraphics); | |||
| menu.addSeparator(); | |||
| @@ -1283,6 +1286,7 @@ void createGUIEditorMenu (PopupMenu& menu) | |||
| menu.addSubMenu ("Component Overlay", overlays, holder != nullptr); | |||
| } | |||
| return menu; | |||
| } | |||
| void handleGUIEditorMenuCommand (int); | |||
| @@ -697,7 +697,7 @@ public: | |||
| { | |||
| if (header->save()) | |||
| { | |||
| odm.closeFile (getFile().withFileExtension(".h"), false); | |||
| odm.closeFile (getFile().withFileExtension(".h"), OpenDocumentManager::SaveIfNeeded::no); | |||
| return true; | |||
| } | |||
| } | |||
| @@ -707,10 +707,13 @@ public: | |||
| Component* createEditor() override | |||
| { | |||
| std::unique_ptr<JucerDocument> jucerDoc (JucerDocument::createForCppFile (getProject(), getFile())); | |||
| if (ProjucerApplication::getApp().isGUIEditorEnabled()) | |||
| { | |||
| std::unique_ptr<JucerDocument> jucerDoc (JucerDocument::createForCppFile (getProject(), getFile())); | |||
| if (jucerDoc != nullptr) | |||
| return new JucerDocumentEditor (jucerDoc.release()); | |||
| if (jucerDoc != nullptr) | |||
| return new JucerDocumentEditor (jucerDoc.release()); | |||
| } | |||
| return SourceCodeDocument::createEditor(); | |||
| } | |||
| @@ -766,8 +769,8 @@ struct NewGUIComponentWizard : public NewFileWizard::Type | |||
| cpp->save(); | |||
| header->save(); | |||
| odm.closeDocument (cpp, true); | |||
| odm.closeDocument (header, true); | |||
| odm.closeDocument (cpp, OpenDocumentManager::SaveIfNeeded::yes); | |||
| odm.closeDocument (header, OpenDocumentManager::SaveIfNeeded::yes); | |||
| parent.addFileRetainingSortOrder (headerFile, true); | |||
| parent.addFileRetainingSortOrder (cppFile, true); | |||
| @@ -81,13 +81,10 @@ namespace MessageTypes | |||
| { | |||
| inline bool send (MessageHandler& target, const ValueTree& v) | |||
| { | |||
| //DBG ("Send: " << v.getType().toString()); | |||
| bool result = target.sendMessage (v); | |||
| if (! result) | |||
| { | |||
| DBG ("*** Message failed: " << v.getType().toString()); | |||
| } | |||
| Logger::outputDebugString ("*** Message failed: " + v.getType().toString()); | |||
| return result; | |||
| } | |||
| @@ -29,6 +29,7 @@ | |||
| #include "jucer_CompileEngineClient.h" | |||
| #include "jucer_CompileEngineServer.h" | |||
| #include "jucer_CompileEngineSettings.h" | |||
| #include "../Project/UI/jucer_ProjectContentComponent.h" | |||
| #ifndef RUN_CLANG_IN_CHILD_PROCESS | |||
| #error | |||
| @@ -208,8 +209,6 @@ public: | |||
| if (isRunningApp && server != nullptr) | |||
| server->killServerWithoutMercy(); | |||
| server.reset(); | |||
| } | |||
| void restartServer() | |||
| @@ -511,9 +510,6 @@ CompileEngineChildProcess::CompileEngineChildProcess (Project& p) | |||
| CompileEngineChildProcess::~CompileEngineChildProcess() | |||
| { | |||
| ProjucerApplication::getApp().openDocumentManager.removeListener (this); | |||
| process.reset(); | |||
| lastComponentList.clear(); | |||
| } | |||
| void CompileEngineChildProcess::createProcess() | |||
| @@ -18,10 +18,11 @@ | |||
| #pragma once | |||
| #define DECLARE_ID(name) static const Identifier name (#name) | |||
| namespace MessageTypes | |||
| { | |||
| #define DECLARE_ID(name) const Identifier name (#name) | |||
| DECLARE_ID (PING); | |||
| DECLARE_ID (BUILDINFO); | |||
| DECLARE_ID (COMPILEUNIT); | |||
| @@ -50,6 +51,6 @@ namespace MessageTypes | |||
| DECLARE_ID (LAUNCH_APP); | |||
| DECLARE_ID (FOREGROUND); | |||
| DECLARE_ID (QUIT_SERVER); | |||
| } | |||
| #undef DECLARE_ID | |||
| #undef DECLARE_ID | |||
| } | |||
| @@ -0,0 +1,195 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE 6 technical preview. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| You may use this code under the terms of the GPL v3 | |||
| (see www.gnu.org/licenses). | |||
| For this technical preview, this file is not subject to commercial licensing. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| #pragma once | |||
| #include "jucer_ModuleDescription.h" | |||
| //============================================================================== | |||
| class AvailableModulesList | |||
| { | |||
| public: | |||
| using ModuleIDAndFolder = std::pair<String, File>; | |||
| using ModuleIDAndFolderList = std::vector<ModuleIDAndFolder>; | |||
| AvailableModulesList() = default; | |||
| //============================================================================== | |||
| void scanPaths (const Array<File>& paths) | |||
| { | |||
| auto job = createScannerJob (paths); | |||
| auto& ref = *job; | |||
| removePendingAndAddJob (std::move (job)); | |||
| scanPool.waitForJobToFinish (&ref, -1); | |||
| } | |||
| void scanPathsAsync (const Array<File>& paths) | |||
| { | |||
| removePendingAndAddJob (createScannerJob (paths)); | |||
| } | |||
| //============================================================================== | |||
| ModuleIDAndFolderList getAllModules() const | |||
| { | |||
| const ScopedLock readLock (lock); | |||
| return modulesList; | |||
| } | |||
| ModuleIDAndFolder getModuleWithID (const String& id) const | |||
| { | |||
| const ScopedLock readLock (lock); | |||
| for (auto& mod : modulesList) | |||
| if (mod.first == id) | |||
| return mod; | |||
| return {}; | |||
| } | |||
| //============================================================================== | |||
| void removeDuplicates (const ModuleIDAndFolderList& other) | |||
| { | |||
| const ScopedLock readLock (lock); | |||
| const auto predicate = [&] (const ModuleIDAndFolder& entry) | |||
| { | |||
| return std::find (other.begin(), other.end(), entry) != other.end(); | |||
| }; | |||
| modulesList.erase (std::remove_if (modulesList.begin(), modulesList.end(), predicate), | |||
| modulesList.end()); | |||
| } | |||
| //============================================================================== | |||
| struct Listener | |||
| { | |||
| virtual ~Listener() = default; | |||
| virtual void availableModulesChanged (AvailableModulesList* listThatHasChanged) = 0; | |||
| }; | |||
| void addListener (Listener* listenerToAdd) { listeners.add (listenerToAdd); } | |||
| void removeListener (Listener* listenerToRemove) { listeners.remove (listenerToRemove); } | |||
| private: | |||
| //============================================================================== | |||
| struct ModuleScannerJob : public ThreadPoolJob | |||
| { | |||
| ModuleScannerJob (const Array<File>& paths, | |||
| std::function<void (const ModuleIDAndFolderList&)>&& callback) | |||
| : ThreadPoolJob ("ModuleScannerJob"), | |||
| pathsToScan (paths), | |||
| completionCallback (std::move (callback)) | |||
| { | |||
| } | |||
| JobStatus runJob() override | |||
| { | |||
| ModuleIDAndFolderList list; | |||
| for (auto& p : pathsToScan) | |||
| addAllModulesInFolder (p, list); | |||
| if (! shouldExit()) | |||
| { | |||
| std::sort (list.begin(), list.end(), [] (const ModuleIDAndFolder& m1, | |||
| const ModuleIDAndFolder& m2) | |||
| { | |||
| return m1.first.compareIgnoreCase (m2.first) < 0; | |||
| }); | |||
| completionCallback (list); | |||
| } | |||
| return jobHasFinished; | |||
| } | |||
| static bool tryToAddModuleFromFolder (const File& path, ModuleIDAndFolderList& list) | |||
| { | |||
| ModuleDescription m (path); | |||
| if (m.isValid()) | |||
| { | |||
| list.push_back ({ m.getID(), path }); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| static void addAllModulesInSubfoldersRecursively (const File& path, int depth, ModuleIDAndFolderList& list) | |||
| { | |||
| if (depth > 0) | |||
| { | |||
| for (const auto& iter : RangedDirectoryIterator (path, false, "*", File::findDirectories)) | |||
| { | |||
| if (auto* job = ThreadPoolJob::getCurrentThreadPoolJob()) | |||
| if (job->shouldExit()) | |||
| return; | |||
| auto childPath = iter.getFile(); | |||
| if (! tryToAddModuleFromFolder (childPath, list)) | |||
| addAllModulesInSubfoldersRecursively (childPath, depth - 1, list); | |||
| } | |||
| } | |||
| } | |||
| static void addAllModulesInFolder (const File& path, ModuleIDAndFolderList& list) | |||
| { | |||
| if (! tryToAddModuleFromFolder (path, list)) | |||
| { | |||
| constexpr int subfolders = 3; | |||
| addAllModulesInSubfoldersRecursively (path, subfolders, list); | |||
| } | |||
| } | |||
| Array<File> pathsToScan; | |||
| std::function<void (const ModuleIDAndFolderList&)> completionCallback; | |||
| }; | |||
| //============================================================================== | |||
| std::unique_ptr<ThreadPoolJob> createScannerJob (const Array<File>& paths) | |||
| { | |||
| return std::make_unique<ModuleScannerJob> (paths, [this] (ModuleIDAndFolderList scannedModulesList) | |||
| { | |||
| { | |||
| const ScopedLock swapLock (lock); | |||
| modulesList.swap (scannedModulesList); | |||
| } | |||
| listeners.call ([this] (Listener& l) { MessageManager::callAsync ([&] { l.availableModulesChanged (this); }); }); | |||
| }); | |||
| } | |||
| void removePendingAndAddJob (std::unique_ptr<ThreadPoolJob> jobToAdd) | |||
| { | |||
| scanPool.removeAllJobs (false, 100); | |||
| scanPool.addJob (jobToAdd.release(), true); | |||
| } | |||
| //============================================================================== | |||
| ThreadPool scanPool { 1 }; | |||
| ModuleIDAndFolderList modulesList; | |||
| ListenerList<Listener> listeners; | |||
| CriticalSection lock; | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AvailableModulesList) | |||
| }; | |||
| @@ -0,0 +1,86 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE 6 technical preview. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| You may use this code under the terms of the GPL v3 | |||
| (see www.gnu.org/licenses). | |||
| For this technical preview, this file is not subject to commercial licensing. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| #pragma once | |||
| //============================================================================== | |||
| class ModuleDescription | |||
| { | |||
| public: | |||
| ModuleDescription() = default; | |||
| ModuleDescription (const File& folder) | |||
| : moduleFolder (folder), | |||
| moduleInfo (parseJUCEHeaderMetadata (getHeader())) | |||
| { | |||
| } | |||
| bool isValid() const { return getID().isNotEmpty(); } | |||
| String getID() const { return moduleInfo [Ids::ID_uppercase].toString(); } | |||
| String getVendor() const { return moduleInfo [Ids::vendor].toString(); } | |||
| String getVersion() const { return moduleInfo [Ids::version].toString(); } | |||
| String getName() const { return moduleInfo [Ids::name].toString(); } | |||
| String getDescription() const { return moduleInfo [Ids::description].toString(); } | |||
| String getLicense() const { return moduleInfo [Ids::license].toString(); } | |||
| String getMinimumCppStandard() const { return moduleInfo [Ids::minimumCppStandard].toString(); } | |||
| String getPreprocessorDefs() const { return moduleInfo [Ids::defines].toString(); } | |||
| String getExtraSearchPaths() const { return moduleInfo [Ids::searchpaths].toString(); } | |||
| var getModuleInfo() const { return moduleInfo; } | |||
| File getModuleFolder() const { return moduleFolder; } | |||
| File getFolder() const | |||
| { | |||
| jassert (moduleFolder != File()); | |||
| return moduleFolder; | |||
| } | |||
| File getHeader() const | |||
| { | |||
| if (moduleFolder != File()) | |||
| { | |||
| static const char* extensions[] = { ".h", ".hpp", ".hxx" }; | |||
| for (auto e : extensions) | |||
| { | |||
| auto header = moduleFolder.getChildFile (moduleFolder.getFileName() + e); | |||
| if (header.existsAsFile()) | |||
| return header; | |||
| } | |||
| } | |||
| return {}; | |||
| } | |||
| StringArray getDependencies() const | |||
| { | |||
| auto moduleDependencies = StringArray::fromTokens (moduleInfo ["dependencies"].toString(), " \t;,", "\"'"); | |||
| moduleDependencies.trim(); | |||
| moduleDependencies.removeEmptyStrings(); | |||
| return moduleDependencies; | |||
| } | |||
| private: | |||
| File moduleFolder; | |||
| var moduleInfo; | |||
| URL url; | |||
| }; | |||
| @@ -16,182 +16,10 @@ | |||
| ============================================================================== | |||
| */ | |||
| #include "../Application/jucer_Headers.h" | |||
| #include "../ProjectSaving/jucer_ProjectSaver.h" | |||
| #include "../ProjectSaving/jucer_ProjectExport_Xcode.h" | |||
| #include "../Application/jucer_Application.h" | |||
| //============================================================================== | |||
| ModuleDescription::ModuleDescription (const File& folder) | |||
| : moduleFolder (folder), | |||
| moduleInfo (parseJUCEHeaderMetadata (getHeader())) | |||
| { | |||
| } | |||
| File ModuleDescription::getHeader() const | |||
| { | |||
| if (moduleFolder != File()) | |||
| { | |||
| static const char* extensions[] = { ".h", ".hpp", ".hxx" }; | |||
| for (auto e : extensions) | |||
| { | |||
| auto header = moduleFolder.getChildFile (moduleFolder.getFileName() + e); | |||
| if (header.existsAsFile()) | |||
| return header; | |||
| } | |||
| } | |||
| return {}; | |||
| } | |||
| StringArray ModuleDescription::getDependencies() const | |||
| { | |||
| auto moduleDependencies = StringArray::fromTokens (moduleInfo ["dependencies"].toString(), " \t;,", "\"'"); | |||
| moduleDependencies.trim(); | |||
| moduleDependencies.removeEmptyStrings(); | |||
| return moduleDependencies; | |||
| } | |||
| //============================================================================== | |||
| static bool tryToAddModuleFromFolder (const File& path, AvailableModuleList::ModuleIDAndFolderList& list) | |||
| { | |||
| ModuleDescription m (path); | |||
| if (m.isValid()) | |||
| { | |||
| list.push_back ({ m.getID(), path }); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| static void addAllModulesInSubfoldersRecursively (const File& path, int depth, AvailableModuleList::ModuleIDAndFolderList& list) | |||
| { | |||
| if (depth > 0) | |||
| { | |||
| for (const auto& iter : RangedDirectoryIterator (path, false, "*", File::findDirectories)) | |||
| { | |||
| if (auto* job = ThreadPoolJob::getCurrentThreadPoolJob()) | |||
| if (job->shouldExit()) | |||
| return; | |||
| auto childPath = iter.getFile(); | |||
| if (! tryToAddModuleFromFolder (childPath, list)) | |||
| addAllModulesInSubfoldersRecursively (childPath, depth - 1, list); | |||
| } | |||
| } | |||
| } | |||
| static void addAllModulesInFolder (const File& path, AvailableModuleList::ModuleIDAndFolderList& list) | |||
| { | |||
| if (! tryToAddModuleFromFolder (path, list)) | |||
| { | |||
| static constexpr int subfolders = 3; | |||
| addAllModulesInSubfoldersRecursively (path, subfolders, list); | |||
| } | |||
| } | |||
| struct ModuleScannerJob : public ThreadPoolJob | |||
| { | |||
| ModuleScannerJob (const Array<File>& paths, | |||
| std::function<void (const AvailableModuleList::ModuleIDAndFolderList&)>&& callback) | |||
| : ThreadPoolJob ("ModuleScannerJob"), | |||
| pathsToScan (paths), | |||
| completionCallback (std::move (callback)) | |||
| { | |||
| } | |||
| JobStatus runJob() override | |||
| { | |||
| AvailableModuleList::ModuleIDAndFolderList list; | |||
| for (auto& p : pathsToScan) | |||
| addAllModulesInFolder (p, list); | |||
| if (! shouldExit()) | |||
| { | |||
| std::sort (list.begin(), list.end(), [] (const AvailableModuleList::ModuleIDAndFolder& m1, | |||
| const AvailableModuleList::ModuleIDAndFolder& m2) | |||
| { | |||
| return m1.first.compareIgnoreCase (m2.first) < 0; | |||
| }); | |||
| completionCallback (list); | |||
| } | |||
| return jobHasFinished; | |||
| } | |||
| Array<File> pathsToScan; | |||
| std::function<void (const AvailableModuleList::ModuleIDAndFolderList&)> completionCallback; | |||
| }; | |||
| ThreadPoolJob* AvailableModuleList::createScannerJob (const Array<File>& paths) | |||
| { | |||
| return new ModuleScannerJob (paths, [this] (AvailableModuleList::ModuleIDAndFolderList scannedModuleList) | |||
| { | |||
| { | |||
| const ScopedLock swapLock (lock); | |||
| moduleList.swap (scannedModuleList); | |||
| } | |||
| listeners.call ([] (Listener& l) { MessageManager::callAsync ([&] { l.availableModulesChanged(); }); }); | |||
| }); | |||
| } | |||
| void AvailableModuleList::removePendingAndAddJob (ThreadPoolJob* jobToAdd) | |||
| { | |||
| scanPool.removeAllJobs (false, 100); | |||
| scanPool.addJob (jobToAdd, true); | |||
| } | |||
| void AvailableModuleList::scanPaths (const Array<File>& paths) | |||
| { | |||
| auto* job = createScannerJob (paths); | |||
| removePendingAndAddJob (job); | |||
| scanPool.waitForJobToFinish (job, -1); | |||
| } | |||
| void AvailableModuleList::scanPathsAsync (const Array<File>& paths) | |||
| { | |||
| removePendingAndAddJob (createScannerJob (paths)); | |||
| } | |||
| AvailableModuleList::ModuleIDAndFolderList AvailableModuleList::getAllModules() const | |||
| { | |||
| const ScopedLock readLock (lock); | |||
| return moduleList; | |||
| } | |||
| AvailableModuleList::ModuleIDAndFolder AvailableModuleList::getModuleWithID (const String& id) const | |||
| { | |||
| const ScopedLock readLock (lock); | |||
| for (auto& mod : moduleList) | |||
| if (mod.first == id) | |||
| return mod; | |||
| return {}; | |||
| } | |||
| void AvailableModuleList::removeDuplicates (const ModuleIDAndFolderList& other) | |||
| { | |||
| const ScopedLock readLock (lock); | |||
| for (auto& m : other) | |||
| { | |||
| auto pos = std::find (moduleList.begin(), moduleList.end(), m); | |||
| if (pos != moduleList.end()) | |||
| moduleList.erase (pos); | |||
| } | |||
| } | |||
| #include "../../Application/jucer_Headers.h" | |||
| #include "../../ProjectSaving/jucer_ProjectSaver.h" | |||
| #include "../../ProjectSaving/jucer_ProjectExport_Xcode.h" | |||
| #include "../../Application/jucer_Application.h" | |||
| //============================================================================== | |||
| LibraryModule::LibraryModule (const ModuleDescription& d) | |||
| @@ -201,7 +29,7 @@ LibraryModule::LibraryModule (const ModuleDescription& d) | |||
| void LibraryModule::writeIncludes (ProjectSaver& projectSaver, OutputStream& out) | |||
| { | |||
| auto& project = projectSaver.project; | |||
| auto& project = projectSaver.getProject(); | |||
| auto& modules = project.getEnabledModules(); | |||
| auto moduleID = getID(); | |||
| @@ -215,7 +43,7 @@ void LibraryModule::writeIncludes (ProjectSaver& projectSaver, OutputStream& out | |||
| projectSaver.copyFolder (juceModuleFolder, localModuleFolder); | |||
| } | |||
| out << "#include <" << moduleInfo.moduleFolder.getFileName() << "/" | |||
| out << "#include <" << moduleInfo.getModuleFolder().getFileName() << "/" | |||
| << moduleInfo.getHeader().getFileName() | |||
| << ">" << newLine; | |||
| } | |||
| @@ -300,26 +128,26 @@ void LibraryModule::addLibsToExporter (ProjectExporter& exporter) const | |||
| xcodeExporter.xcodeFrameworks.add ("AudioUnit"); | |||
| } | |||
| auto frameworks = moduleInfo.moduleInfo [xcodeExporter.isOSX() ? "OSXFrameworks" : "iOSFrameworks"].toString(); | |||
| auto frameworks = moduleInfo.getModuleInfo() [xcodeExporter.isOSX() ? "OSXFrameworks" : "iOSFrameworks"].toString(); | |||
| xcodeExporter.xcodeFrameworks.addTokens (frameworks, ", ", {}); | |||
| parseAndAddLibsToList (xcodeExporter.xcodeLibs, moduleInfo.moduleInfo [exporter.isOSX() ? "OSXLibs" : "iOSLibs"].toString()); | |||
| parseAndAddLibsToList (xcodeExporter.xcodeLibs, moduleInfo.getModuleInfo() [exporter.isOSX() ? "OSXLibs" : "iOSLibs"].toString()); | |||
| } | |||
| else if (exporter.isLinux()) | |||
| { | |||
| parseAndAddLibsToList (exporter.linuxLibs, moduleInfo.moduleInfo ["linuxLibs"].toString()); | |||
| parseAndAddLibsToList (exporter.linuxPackages, moduleInfo.moduleInfo ["linuxPackages"].toString()); | |||
| parseAndAddLibsToList (exporter.linuxLibs, moduleInfo.getModuleInfo() ["linuxLibs"].toString()); | |||
| parseAndAddLibsToList (exporter.linuxPackages, moduleInfo.getModuleInfo() ["linuxPackages"].toString()); | |||
| } | |||
| else if (exporter.isWindows()) | |||
| { | |||
| if (exporter.isCodeBlocks()) | |||
| parseAndAddLibsToList (exporter.mingwLibs, moduleInfo.moduleInfo ["mingwLibs"].toString()); | |||
| parseAndAddLibsToList (exporter.mingwLibs, moduleInfo.getModuleInfo() ["mingwLibs"].toString()); | |||
| else | |||
| parseAndAddLibsToList (exporter.windowsLibs, moduleInfo.moduleInfo ["windowsLibs"].toString()); | |||
| parseAndAddLibsToList (exporter.windowsLibs, moduleInfo.getModuleInfo() ["windowsLibs"].toString()); | |||
| } | |||
| else if (exporter.isAndroid()) | |||
| { | |||
| parseAndAddLibsToList (exporter.androidLibs, moduleInfo.moduleInfo ["androidLibs"].toString()); | |||
| parseAndAddLibsToList (exporter.androidLibs, moduleInfo.getModuleInfo() ["androidLibs"].toString()); | |||
| } | |||
| } | |||
| @@ -544,12 +372,12 @@ void LibraryModule::addBrowseableCode (ProjectExporter& exporter, const Array<Fi | |||
| } | |||
| //============================================================================== | |||
| EnabledModuleList::EnabledModuleList (Project& p, const ValueTree& s) | |||
| EnabledModulesList::EnabledModulesList (Project& p, const ValueTree& s) | |||
| : project (p), state (s) | |||
| { | |||
| } | |||
| StringArray EnabledModuleList::getAllModules() const | |||
| StringArray EnabledModulesList::getAllModules() const | |||
| { | |||
| StringArray moduleIDs; | |||
| @@ -559,13 +387,13 @@ StringArray EnabledModuleList::getAllModules() const | |||
| return moduleIDs; | |||
| } | |||
| void EnabledModuleList::createRequiredModules (OwnedArray<LibraryModule>& modules) | |||
| void EnabledModulesList::createRequiredModules (OwnedArray<LibraryModule>& modules) | |||
| { | |||
| for (int i = 0; i < getNumModules(); ++i) | |||
| modules.add (new LibraryModule (getModuleInfo (getModuleID (i)))); | |||
| } | |||
| void EnabledModuleList::sortAlphabetically() | |||
| void EnabledModulesList::sortAlphabetically() | |||
| { | |||
| struct ModuleTreeSorter | |||
| { | |||
| @@ -579,14 +407,14 @@ void EnabledModuleList::sortAlphabetically() | |||
| state.sort (sorter, getUndoManager(), false); | |||
| } | |||
| File EnabledModuleList::getDefaultModulesFolder() const | |||
| File EnabledModulesList::getDefaultModulesFolder() const | |||
| { | |||
| File globalPath (getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()).get().toString()); | |||
| if (globalPath.exists()) | |||
| return globalPath; | |||
| for (auto& exporterPathModule : project.getExporterPathsModuleList().getAllModules()) | |||
| for (auto& exporterPathModule : project.getExporterPathsModulesList().getAllModules()) | |||
| { | |||
| auto f = exporterPathModule.second; | |||
| @@ -597,12 +425,12 @@ File EnabledModuleList::getDefaultModulesFolder() const | |||
| return File::getCurrentWorkingDirectory(); | |||
| } | |||
| ModuleDescription EnabledModuleList::getModuleInfo (const String& moduleID) | |||
| ModuleDescription EnabledModulesList::getModuleInfo (const String& moduleID) const | |||
| { | |||
| return ModuleDescription (project.getModuleWithID (moduleID).second); | |||
| } | |||
| bool EnabledModuleList::isModuleEnabled (const String& moduleID) const | |||
| bool EnabledModulesList::isModuleEnabled (const String& moduleID) const | |||
| { | |||
| return state.getChildWithProperty (Ids::ID, moduleID).isValid(); | |||
| } | |||
| @@ -621,7 +449,7 @@ static void getDependencies (Project& project, const String& moduleID, StringArr | |||
| } | |||
| } | |||
| StringArray EnabledModuleList::getExtraDependenciesNeeded (const String& moduleID) const | |||
| StringArray EnabledModulesList::getExtraDependenciesNeeded (const String& moduleID) const | |||
| { | |||
| StringArray dependencies, extraDepsNeeded; | |||
| getDependencies (project, moduleID, dependencies); | |||
| @@ -633,11 +461,31 @@ StringArray EnabledModuleList::getExtraDependenciesNeeded (const String& moduleI | |||
| return extraDepsNeeded; | |||
| } | |||
| bool EnabledModuleList::doesModuleHaveHigherCppStandardThanProject (const String& moduleID) | |||
| bool EnabledModulesList::tryToFixMissingDependencies (const String& moduleID) | |||
| { | |||
| auto copyLocally = areMostModulesCopiedLocally(); | |||
| auto useGlobalPath = areMostModulesUsingGlobalPath(); | |||
| StringArray missing; | |||
| for (auto missingModule : getExtraDependenciesNeeded (moduleID)) | |||
| { | |||
| auto mod = project.getModuleWithID (missingModule); | |||
| if (mod.second != File()) | |||
| addModule (mod.second, copyLocally, useGlobalPath); | |||
| else | |||
| missing.add (missingModule); | |||
| } | |||
| return (missing.size() == 0); | |||
| } | |||
| bool EnabledModulesList::doesModuleHaveHigherCppStandardThanProject (const String& moduleID) const | |||
| { | |||
| auto projectCppStandard = project.getCppStandardString(); | |||
| if (projectCppStandard == "latest") | |||
| if (projectCppStandard == Project::getCppStandardVars().getLast().toString()) | |||
| return false; | |||
| auto moduleCppStandard = getModuleInfo (moduleID).getMinimumCppStandard(); | |||
| @@ -645,40 +493,40 @@ bool EnabledModuleList::doesModuleHaveHigherCppStandardThanProject (const String | |||
| return (moduleCppStandard.getIntValue() > projectCppStandard.getIntValue()); | |||
| } | |||
| bool EnabledModuleList::shouldUseGlobalPath (const String& moduleID) const | |||
| bool EnabledModulesList::shouldUseGlobalPath (const String& moduleID) const | |||
| { | |||
| return (bool) shouldUseGlobalPathValue (moduleID).getValue(); | |||
| } | |||
| Value EnabledModuleList::shouldUseGlobalPathValue (const String& moduleID) const | |||
| Value EnabledModulesList::shouldUseGlobalPathValue (const String& moduleID) const | |||
| { | |||
| return state.getChildWithProperty (Ids::ID, moduleID) | |||
| .getPropertyAsValue (Ids::useGlobalPath, getUndoManager()); | |||
| } | |||
| bool EnabledModuleList::shouldShowAllModuleFilesInProject (const String& moduleID) const | |||
| bool EnabledModulesList::shouldShowAllModuleFilesInProject (const String& moduleID) const | |||
| { | |||
| return (bool) shouldShowAllModuleFilesInProjectValue (moduleID).getValue(); | |||
| } | |||
| Value EnabledModuleList::shouldShowAllModuleFilesInProjectValue (const String& moduleID) const | |||
| Value EnabledModulesList::shouldShowAllModuleFilesInProjectValue (const String& moduleID) const | |||
| { | |||
| return state.getChildWithProperty (Ids::ID, moduleID) | |||
| .getPropertyAsValue (Ids::showAllCode, getUndoManager()); | |||
| } | |||
| bool EnabledModuleList::shouldCopyModuleFilesLocally (const String& moduleID) const | |||
| bool EnabledModulesList::shouldCopyModuleFilesLocally (const String& moduleID) const | |||
| { | |||
| return (bool) shouldCopyModuleFilesLocallyValue (moduleID).getValue(); | |||
| } | |||
| Value EnabledModuleList::shouldCopyModuleFilesLocallyValue (const String& moduleID) const | |||
| Value EnabledModulesList::shouldCopyModuleFilesLocallyValue (const String& moduleID) const | |||
| { | |||
| return state.getChildWithProperty (Ids::ID, moduleID) | |||
| .getPropertyAsValue (Ids::useLocalCopy, getUndoManager()); | |||
| } | |||
| bool EnabledModuleList::areMostModulesUsingGlobalPath() const | |||
| bool EnabledModulesList::areMostModulesUsingGlobalPath() const | |||
| { | |||
| int numYes = 0, numNo = 0; | |||
| @@ -693,7 +541,7 @@ bool EnabledModuleList::areMostModulesUsingGlobalPath() const | |||
| return numYes > numNo; | |||
| } | |||
| bool EnabledModuleList::areMostModulesCopiedLocally() const | |||
| bool EnabledModulesList::areMostModulesCopiedLocally() const | |||
| { | |||
| int numYes = 0, numNo = 0; | |||
| @@ -708,7 +556,47 @@ bool EnabledModuleList::areMostModulesCopiedLocally() const | |||
| return numYes > numNo; | |||
| } | |||
| void EnabledModuleList::addModule (const File& moduleFolder, bool copyLocally, bool useGlobalPath) | |||
| StringArray EnabledModulesList::getModulesWithHigherCppStandardThanProject() const | |||
| { | |||
| StringArray list; | |||
| for (auto& module : getAllModules()) | |||
| if (doesModuleHaveHigherCppStandardThanProject (module)) | |||
| list.add (module); | |||
| return list; | |||
| } | |||
| StringArray EnabledModulesList::getModulesWithMissingDependencies() const | |||
| { | |||
| StringArray list; | |||
| for (auto& module : getAllModules()) | |||
| if (getExtraDependenciesNeeded (module).size() > 0) | |||
| list.add (module); | |||
| return list; | |||
| } | |||
| String EnabledModulesList::getHighestModuleCppStandard() const | |||
| { | |||
| auto highestCppStandard = Project::getCppStandardVars()[0].toString(); | |||
| for (auto& mod : getAllModules()) | |||
| { | |||
| auto moduleCppStandard = getModuleInfo (mod).getMinimumCppStandard(); | |||
| if (moduleCppStandard == "latest") | |||
| return moduleCppStandard; | |||
| if (moduleCppStandard.getIntValue() > highestCppStandard.getIntValue()) | |||
| highestCppStandard = moduleCppStandard; | |||
| } | |||
| return highestCppStandard; | |||
| } | |||
| void EnabledModulesList::addModule (const File& moduleFolder, bool copyLocally, bool useGlobalPath) | |||
| { | |||
| ModuleDescription info (moduleFolder); | |||
| @@ -741,7 +629,7 @@ void EnabledModuleList::addModule (const File& moduleFolder, bool copyLocally, b | |||
| } | |||
| } | |||
| void EnabledModuleList::addModuleInteractive (const String& moduleID) | |||
| void EnabledModulesList::addModuleInteractive (const String& moduleID) | |||
| { | |||
| auto f = project.getModuleWithID (moduleID).second; | |||
| @@ -754,7 +642,7 @@ void EnabledModuleList::addModuleInteractive (const String& moduleID) | |||
| addModuleFromUserSelectedFile(); | |||
| } | |||
| void EnabledModuleList::addModuleFromUserSelectedFile() | |||
| void EnabledModulesList::addModuleFromUserSelectedFile() | |||
| { | |||
| auto lastLocation = getDefaultModulesFolder(); | |||
| @@ -767,7 +655,7 @@ void EnabledModuleList::addModuleFromUserSelectedFile() | |||
| } | |||
| } | |||
| void EnabledModuleList::addModuleOfferingToCopy (const File& f, bool isFromUserSpecifiedFolder) | |||
| void EnabledModulesList::addModuleOfferingToCopy (const File& f, bool isFromUserSpecifiedFolder) | |||
| { | |||
| ModuleDescription m (f); | |||
| @@ -785,11 +673,11 @@ void EnabledModuleList::addModuleOfferingToCopy (const File& f, bool isFromUserS | |||
| return; | |||
| } | |||
| addModule (m.moduleFolder, areMostModulesCopiedLocally(), | |||
| addModule (m.getModuleFolder(), areMostModulesCopiedLocally(), | |||
| isFromUserSpecifiedFolder ? false : areMostModulesUsingGlobalPath()); | |||
| } | |||
| void EnabledModuleList::removeModule (String moduleID) // must be pass-by-value, and not a const ref! | |||
| void EnabledModulesList::removeModule (String moduleID) // must be pass-by-value, and not a const ref! | |||
| { | |||
| for (auto i = state.getNumChildren(); --i >= 0;) | |||
| if (state.getChild(i) [Ids::ID] == moduleID) | |||
| @@ -18,37 +18,11 @@ | |||
| #pragma once | |||
| #include "jucer_Project.h" | |||
| #include "../jucer_Project.h" | |||
| class ProjectExporter; | |||
| class ProjectSaver; | |||
| //============================================================================== | |||
| struct ModuleDescription | |||
| { | |||
| ModuleDescription() = default; | |||
| ModuleDescription (const File& folder); | |||
| bool isValid() const { return getID().isNotEmpty(); } | |||
| String getID() const { return moduleInfo [Ids::ID_uppercase].toString(); } | |||
| String getVendor() const { return moduleInfo [Ids::vendor].toString(); } | |||
| String getVersion() const { return moduleInfo [Ids::version].toString(); } | |||
| String getName() const { return moduleInfo [Ids::name].toString(); } | |||
| String getDescription() const { return moduleInfo [Ids::description].toString(); } | |||
| String getLicense() const { return moduleInfo [Ids::license].toString(); } | |||
| String getMinimumCppStandard() const { return moduleInfo [Ids::minimumCppStandard].toString(); } | |||
| String getPreprocessorDefs() const { return moduleInfo [Ids::defines].toString(); } | |||
| String getExtraSearchPaths() const { return moduleInfo [Ids::searchpaths].toString(); } | |||
| StringArray getDependencies() const; | |||
| File getFolder() const { jassert (moduleFolder != File()); return moduleFolder; } | |||
| File getHeader() const; | |||
| File moduleFolder; | |||
| var moduleInfo; | |||
| URL url; | |||
| }; | |||
| //============================================================================== | |||
| class LibraryModule | |||
| { | |||
| @@ -102,51 +76,10 @@ private: | |||
| }; | |||
| //============================================================================== | |||
| class AvailableModuleList | |||
| class EnabledModulesList | |||
| { | |||
| public: | |||
| using ModuleIDAndFolder = std::pair<String, File>; | |||
| using ModuleIDAndFolderList = std::vector<ModuleIDAndFolder>; | |||
| AvailableModuleList() = default; | |||
| void scanPaths (const Array<File>&); | |||
| void scanPathsAsync (const Array<File>&); | |||
| ModuleIDAndFolderList getAllModules() const; | |||
| ModuleIDAndFolder getModuleWithID (const String&) const; | |||
| void removeDuplicates (const ModuleIDAndFolderList& other); | |||
| //============================================================================== | |||
| struct Listener | |||
| { | |||
| virtual ~Listener() {} | |||
| virtual void availableModulesChanged() = 0; | |||
| }; | |||
| void addListener (Listener* listenerToAdd) { listeners.add (listenerToAdd); } | |||
| void removeListener (Listener* listenerToRemove) { listeners.remove (listenerToRemove); } | |||
| private: | |||
| ThreadPoolJob* createScannerJob (const Array<File>&); | |||
| void removePendingAndAddJob (ThreadPoolJob*); | |||
| ThreadPool scanPool { 1 }; | |||
| ModuleIDAndFolderList moduleList; | |||
| ListenerList<Listener> listeners; | |||
| CriticalSection lock; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AvailableModuleList) | |||
| }; | |||
| //============================================================================== | |||
| class EnabledModuleList | |||
| { | |||
| public: | |||
| EnabledModuleList (Project&, const ValueTree&); | |||
| EnabledModulesList (Project&, const ValueTree&); | |||
| //============================================================================== | |||
| ValueTree getState() const { return state; } | |||
| @@ -160,11 +93,14 @@ public: | |||
| int getNumModules() const { return state.getNumChildren(); } | |||
| String getModuleID (int index) const { return state.getChild (index) [Ids::ID].toString(); } | |||
| ModuleDescription getModuleInfo (const String& moduleID); | |||
| ModuleDescription getModuleInfo (const String& moduleID) const; | |||
| bool isModuleEnabled (const String& moduleID) const; | |||
| StringArray getExtraDependenciesNeeded (const String& moduleID) const; | |||
| bool doesModuleHaveHigherCppStandardThanProject (const String& moduleID); | |||
| bool tryToFixMissingDependencies (const String& moduleID); | |||
| bool doesModuleHaveHigherCppStandardThanProject (const String& moduleID) const; | |||
| bool shouldUseGlobalPath (const String& moduleID) const; | |||
| Value shouldUseGlobalPathValue (const String& moduleID) const; | |||
| @@ -178,6 +114,11 @@ public: | |||
| bool areMostModulesUsingGlobalPath() const; | |||
| bool areMostModulesCopiedLocally() const; | |||
| StringArray getModulesWithHigherCppStandardThanProject() const; | |||
| StringArray getModulesWithMissingDependencies() const; | |||
| String getHighestModuleCppStandard() const; | |||
| //============================================================================== | |||
| void addModule (const File& moduleManifestFile, bool copyLocally, bool useGlobalPath); | |||
| void addModuleInteractive (const String& moduleID); | |||
| @@ -192,5 +133,5 @@ private: | |||
| Project& project; | |||
| ValueTree state; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EnabledModuleList) | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EnabledModulesList) | |||
| }; | |||
| @@ -108,18 +108,11 @@ public: | |||
| void handlePopupMenuResult (int resultCode) override | |||
| { | |||
| if (resultCode == 1) | |||
| { | |||
| exporter->addNewConfiguration (false); | |||
| } | |||
| else if (resultCode == 2) | |||
| { | |||
| const ScopedValueSetter<String> valueSetter (project.specifiedExporterToSave, exporter->getName(), {}); | |||
| project.save (true, true); | |||
| } | |||
| project.saveProject (exporter.get()); | |||
| else if (resultCode == 3) | |||
| { | |||
| deleteAllSelectedItems(); | |||
| } | |||
| } | |||
| var getDragSourceDescription() override | |||
| @@ -110,7 +110,7 @@ public: | |||
| { | |||
| auto f = filesToTrash.getUnchecked(i); | |||
| om.closeFile (f, false); | |||
| om.closeFile (f, OpenDocumentManager::SaveIfNeeded::no); | |||
| if (! f.moveToTrash()) | |||
| { | |||
| @@ -129,7 +129,7 @@ public: | |||
| pcc->hideEditor(); | |||
| } | |||
| om.closeFile (itemToRemove->getFile(), false); | |||
| om.closeFile (itemToRemove->getFile(), OpenDocumentManager::SaveIfNeeded::no); | |||
| itemToRemove->deleteItem(); | |||
| } | |||
| } | |||
| @@ -61,9 +61,8 @@ class LiveBuildTab : public Component, | |||
| public: | |||
| LiveBuildTab (const CompileEngineChildProcess::Ptr& child, String lastErrorMessage) | |||
| { | |||
| settingsButton.reset (new IconButton ("Settings", &getIcons().settings)); | |||
| addAndMakeVisible (settingsButton.get()); | |||
| settingsButton->onClick = [this] | |||
| addAndMakeVisible (settingsButton); | |||
| settingsButton.onClick = [this] | |||
| { | |||
| if (auto* pcc = findParentComponentOfClass<ProjectContentComponent>()) | |||
| pcc->showLiveBuildSettings(); | |||
| @@ -122,9 +121,9 @@ public: | |||
| { | |||
| auto bounds = getLocalBounds(); | |||
| settingsButton->setBounds (bounds.removeFromBottom (25) | |||
| .removeFromRight (25) | |||
| .reduced (3)); | |||
| settingsButton.setBounds (bounds.removeFromBottom (25) | |||
| .removeFromRight (25) | |||
| .reduced (3)); | |||
| if (errorMessageLabel != nullptr) | |||
| { | |||
| @@ -155,7 +154,7 @@ public: | |||
| private: | |||
| OwnedArray<ConcertinaHeader> headers; | |||
| ConcertinaPanel concertinaPanel; | |||
| std::unique_ptr<IconButton> settingsButton; | |||
| IconButton settingsButton { "Settings", getIcons().settings }; | |||
| std::unique_ptr<TextButton> downloadButton, enableButton; | |||
| std::unique_ptr<Label> errorMessageLabel; | |||
| @@ -361,17 +361,21 @@ private: | |||
| void fixDependencies() | |||
| { | |||
| if (! tryToFix()) | |||
| auto& enabledModules = project.getEnabledModules(); | |||
| if (enabledModules.tryToFixMissingDependencies (moduleID)) | |||
| { | |||
| missingDependencies.clear(); | |||
| } | |||
| else | |||
| { | |||
| missingDependencies = enabledModules.getExtraDependenciesNeeded (moduleID); | |||
| AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, | |||
| "Adding Missing Dependencies", | |||
| "Couldn't locate some of these modules - you'll need to find their " | |||
| "folders manually and add them to the list."); | |||
| return; | |||
| } | |||
| refreshAndReselectItem(); | |||
| } | |||
| void resized() override | |||
| @@ -381,59 +385,11 @@ private: | |||
| private: | |||
| Project& project; | |||
| String moduleID; | |||
| StringArray missingDependencies; | |||
| TextButton fixButton { "Add Required Modules" }; | |||
| bool tryToFix() | |||
| { | |||
| auto& enabledModules = project.getEnabledModules(); | |||
| auto copyLocally = enabledModules.areMostModulesCopiedLocally(); | |||
| auto useGlobalPath = enabledModules.areMostModulesUsingGlobalPath(); | |||
| StringArray missing; | |||
| for (auto missingModule : missingDependencies) | |||
| { | |||
| auto mod = project.getModuleWithID (missingModule); | |||
| if (mod.second != File()) | |||
| enabledModules.addModule (mod.second, copyLocally, useGlobalPath); | |||
| else | |||
| missing.add (missingModule); | |||
| } | |||
| missingDependencies.swapWith (missing); | |||
| return (missingDependencies.size() == 0); | |||
| } | |||
| void refreshAndReselectItem() | |||
| { | |||
| if (auto* settingsPanel = findParentComponentOfClass<ModuleSettingsPanel>()) | |||
| { | |||
| if (settingsPanel->modulesTree == nullptr) | |||
| return; | |||
| auto* rootItem = settingsPanel->modulesTree->getRootItem(); | |||
| if (rootItem == nullptr) | |||
| return; | |||
| for (int i = 0; i < rootItem->getNumSubItems(); ++i) | |||
| { | |||
| if (auto* subItem = dynamic_cast<ProjectTreeItemBase*> (rootItem->getSubItem (i))) | |||
| { | |||
| if (subItem->getDisplayName() == moduleID) | |||
| { | |||
| subItem->setSelected (true, true); | |||
| return; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MissingDependenciesComponent) | |||
| }; | |||
| @@ -468,28 +424,30 @@ private: | |||
| //============================================================================== | |||
| class EnabledModulesItem : public ProjectTreeItemBase, | |||
| private Value::Listener, | |||
| private AvailableModuleList::Listener | |||
| private AvailableModulesList::Listener | |||
| { | |||
| public: | |||
| EnabledModulesItem (Project& p) | |||
| : project (p), | |||
| moduleListTree (project.getEnabledModules().getState()) | |||
| modulesListTree (project.getEnabledModules().getState()) | |||
| { | |||
| moduleListTree.addListener (this); | |||
| modulesListTree.addListener (this); | |||
| projectCppStandardValue.referTo (project.getProjectValue (Ids::cppLanguageStandard)); | |||
| projectCppStandardValue.addListener (this); | |||
| ProjucerApplication::getApp().getJUCEPathModuleList().addListener (this); | |||
| ProjucerApplication::getApp().getUserPathsModuleList().addListener (this); | |||
| project.getExporterPathsModuleList().addListener (this); | |||
| ProjucerApplication::getApp().getJUCEPathModulesList().addListener (this); | |||
| ProjucerApplication::getApp().getUserPathsModulesList().addListener (this); | |||
| project.getExporterPathsModulesList().addListener (this); | |||
| } | |||
| ~EnabledModulesItem() override | |||
| { | |||
| ProjucerApplication::getApp().getJUCEPathModuleList().removeListener (this); | |||
| ProjucerApplication::getApp().getUserPathsModuleList().removeListener (this); | |||
| project.getExporterPathsModuleList().removeListener (this); | |||
| ProjucerApplication::getApp().getJUCEPathModulesList().removeListener (this); | |||
| ProjucerApplication::getApp().getUserPathsModulesList().removeListener (this); | |||
| project.getExporterPathsModulesList().removeListener (this); | |||
| } | |||
| int getItemHeight() const override { return 22; } | |||
| @@ -539,7 +497,7 @@ public: | |||
| } | |||
| for (int i = 0; i < modules.size(); ++i) | |||
| project.getEnabledModules().addModule (modules.getReference(i).moduleFolder, | |||
| project.getEnabledModules().addModule (modules.getReference (i).getModuleFolder(), | |||
| project.getEnabledModules().areMostModulesCopiedLocally(), | |||
| project.getEnabledModules().areMostModulesUsingGlobalPath()); | |||
| } | |||
| @@ -560,7 +518,7 @@ public: | |||
| // JUCE path | |||
| PopupMenu jucePathModules; | |||
| for (auto& mod : ProjucerApplication::getApp().getJUCEPathModuleList().getAllModules()) | |||
| for (auto& mod : ProjucerApplication::getApp().getJUCEPathModulesList().getAllModules()) | |||
| jucePathModules.addItem (index++, mod.first, ! enabledModules.isModuleEnabled (mod.first)); | |||
| jucePathModules.addSeparator(); | |||
| @@ -572,7 +530,7 @@ public: | |||
| index = 200; | |||
| PopupMenu userPathModules; | |||
| for (auto& mod : ProjucerApplication::getApp().getUserPathsModuleList().getAllModules()) | |||
| for (auto& mod : ProjucerApplication::getApp().getUserPathsModulesList().getAllModules()) | |||
| userPathModules.addItem (index++, mod.first, ! enabledModules.isModuleEnabled (mod.first)); | |||
| userPathModules.addSeparator(); | |||
| @@ -584,7 +542,7 @@ public: | |||
| index = 300; | |||
| PopupMenu exporterPathModules; | |||
| for (auto& mod : project.getExporterPathsModuleList().getAllModules()) | |||
| for (auto& mod : project.getExporterPathsModulesList().getAllModules()) | |||
| exporterPathModules.addItem (index++, mod.first, ! enabledModules.isModuleEnabled (mod.first)); | |||
| exporterPathModules.addSeparator(); | |||
| @@ -615,22 +573,22 @@ public: | |||
| } | |||
| else if (resultCode > 0) | |||
| { | |||
| std::vector<AvailableModuleList::ModuleIDAndFolder> list; | |||
| std::vector<AvailableModulesList::ModuleIDAndFolder> list; | |||
| int offset = -1; | |||
| if (resultCode < 200) | |||
| { | |||
| list = ProjucerApplication::getApp().getJUCEPathModuleList().getAllModules(); | |||
| list = ProjucerApplication::getApp().getJUCEPathModulesList().getAllModules(); | |||
| offset = 100; | |||
| } | |||
| else if (resultCode < 300) | |||
| { | |||
| list = ProjucerApplication::getApp().getUserPathsModuleList().getAllModules(); | |||
| list = ProjucerApplication::getApp().getUserPathsModulesList().getAllModules(); | |||
| offset = 200; | |||
| } | |||
| else if (resultCode < 400) | |||
| { | |||
| list = project.getExporterPathsModuleList().getAllModules(); | |||
| list = project.getExporterPathsModulesList().getAllModules(); | |||
| offset = 300; | |||
| } | |||
| @@ -646,13 +604,20 @@ public: | |||
| void refreshIfNeeded (ValueTree& changedTree) | |||
| { | |||
| if (changedTree == moduleListTree) | |||
| if (changedTree == modulesListTree) | |||
| { | |||
| auto selectedID = getSelectedItemID(); | |||
| refreshSubItems(); | |||
| if (selectedID.isNotEmpty()) | |||
| setSelectedItem (selectedID); | |||
| } | |||
| } | |||
| private: | |||
| Project& project; | |||
| ValueTree moduleListTree; | |||
| ValueTree modulesListTree; | |||
| Value projectCppStandardValue; | |||
| //============================================================================== | |||
| @@ -676,22 +641,47 @@ private: | |||
| void removeDuplicateModules() | |||
| { | |||
| auto jucePathModuleList = ProjucerApplication::getApp().getJUCEPathModuleList().getAllModules(); | |||
| auto jucePathModulesList = ProjucerApplication::getApp().getJUCEPathModulesList().getAllModules(); | |||
| auto& userPathModules = ProjucerApplication::getApp().getUserPathsModuleList(); | |||
| userPathModules.removeDuplicates (jucePathModuleList); | |||
| auto& userPathModules = ProjucerApplication::getApp().getUserPathsModulesList(); | |||
| userPathModules.removeDuplicates (jucePathModulesList); | |||
| auto& exporterPathModules = project.getExporterPathsModuleList(); | |||
| exporterPathModules.removeDuplicates (jucePathModuleList); | |||
| auto& exporterPathModules = project.getExporterPathsModulesList(); | |||
| exporterPathModules.removeDuplicates (jucePathModulesList); | |||
| exporterPathModules.removeDuplicates (userPathModules.getAllModules()); | |||
| } | |||
| void availableModulesChanged() override | |||
| void availableModulesChanged (AvailableModulesList*) override | |||
| { | |||
| removeDuplicateModules(); | |||
| refreshSubItems(); | |||
| } | |||
| String getSelectedItemID() const | |||
| { | |||
| for (int i = 0; i < getNumSubItems(); ++i) | |||
| if (auto* item = getSubItem (i)) | |||
| if (item->isSelected()) | |||
| return item->getUniqueName(); | |||
| return {}; | |||
| } | |||
| void setSelectedItem (const String& itemID) | |||
| { | |||
| for (int i = 0; i < getNumSubItems(); ++i) | |||
| { | |||
| if (auto* item = getSubItem (i)) | |||
| { | |||
| if (item->getUniqueName() == itemID) | |||
| { | |||
| item->setSelected (true, true); | |||
| return; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EnabledModulesItem) | |||
| }; | |||
| @@ -165,35 +165,27 @@ public: | |||
| { | |||
| if (hasAddButton) | |||
| { | |||
| addButton.reset (new IconButton ("Add", &getIcons().plus)); | |||
| addButton = std::make_unique<IconButton> ("Add", getIcons().plus); | |||
| addAndMakeVisible (addButton.get()); | |||
| addButton->onClick = [this] { showAddMenu(); }; | |||
| } | |||
| if (hasSettingsButton) | |||
| { | |||
| settingsButton.reset (new IconButton ("Settings", &getIcons().settings)); | |||
| settingsButton = std::make_unique<IconButton> ("Settings", getIcons().settings); | |||
| addAndMakeVisible (settingsButton.get()); | |||
| settingsButton->onClick = [this] { showSettings(); }; | |||
| } | |||
| if (hasFindPanel) | |||
| { | |||
| findPanel.reset (new FindPanel ([this] (const String& filter) { treeToDisplay->rootItem->setSearchFilter (filter); })); | |||
| findPanel = std::make_unique<FindPanel> ([this] (const String& filter) { treeToDisplay->rootItem->setSearchFilter (filter); }); | |||
| addAndMakeVisible (findPanel.get()); | |||
| } | |||
| addAndMakeVisible (treeToDisplay.get()); | |||
| } | |||
| ~ConcertinaTreeComponent() override | |||
| { | |||
| treeToDisplay.reset(); | |||
| addButton.reset(); | |||
| findPanel.reset(); | |||
| settingsButton.reset(); | |||
| } | |||
| void resized() override | |||
| { | |||
| auto bounds = getLocalBounds(); | |||
| @@ -30,17 +30,19 @@ | |||
| #include "../../LiveBuildEngine/jucer_CompileEngineClient.h" | |||
| //============================================================================== | |||
| HeaderComponent::HeaderComponent() | |||
| HeaderComponent::HeaderComponent (ProjectContentComponent* pcc) | |||
| : projectContentComponent (pcc) | |||
| { | |||
| addAndMakeVisible (configLabel); | |||
| addAndMakeVisible (exporterBox); | |||
| exporterBox.onChange = [this] { updateExporterButton(); }; | |||
| juceIcon.reset (new ImageComponent ("icon")); | |||
| addAndMakeVisible (juceIcon.get()); | |||
| juceIcon->setImage (ImageCache::getFromMemory (BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize), | |||
| RectanglePlacement::centred); | |||
| juceIcon.setImage (ImageCache::getFromMemory (BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize), RectanglePlacement::centred); | |||
| addAndMakeVisible (juceIcon); | |||
| addAndMakeVisible (userAvatar); | |||
| userAvatar.addChangeListener (this); | |||
| projectNameLabel.setText ({}, dontSendNotification); | |||
| addAndMakeVisible (projectNameLabel); | |||
| @@ -52,7 +54,7 @@ HeaderComponent::~HeaderComponent() | |||
| { | |||
| if (childProcess != nullptr) | |||
| { | |||
| childProcess->activityList.removeChangeListener(this); | |||
| childProcess->activityList.removeChangeListener (this); | |||
| childProcess->errorList.removeChangeListener (this); | |||
| } | |||
| } | |||
| @@ -63,33 +65,36 @@ void HeaderComponent::resized() | |||
| auto bounds = getLocalBounds(); | |||
| configLabel.setFont ({ bounds.getHeight() / 3.0f }); | |||
| //============================================================================== | |||
| { | |||
| auto headerBounds = bounds.removeFromLeft (tabsWidth); | |||
| const int buttonSize = 25; | |||
| auto buttonBounds = headerBounds.removeFromRight (buttonSize); | |||
| projectSettingsButton->setBounds (buttonBounds.removeFromBottom (buttonSize).reduced (2)); | |||
| projectSettingsButton.setBounds (buttonBounds.removeFromBottom (buttonSize).reduced (2)); | |||
| juceIcon->setBounds (headerBounds.removeFromLeft (headerBounds.getHeight()).reduced (2)); | |||
| juceIcon.setBounds (headerBounds.removeFromLeft (headerBounds.getHeight()).reduced (2)); | |||
| headerBounds.removeFromRight (5); | |||
| projectNameLabel.setBounds (headerBounds); | |||
| } | |||
| //============================================================================== | |||
| auto exporterWidth = jmin (400, bounds.getWidth() / 2); | |||
| Rectangle<int> exporterBounds (0, 0, exporterWidth, bounds.getHeight()); | |||
| { | |||
| auto exporterWidth = jmin (400, bounds.getWidth() / 2); | |||
| Rectangle<int> exporterBounds (0, 0, exporterWidth, bounds.getHeight()); | |||
| exporterBounds.setCentre (bounds.getCentre()); | |||
| exporterBounds.setCentre (bounds.getCentre()); | |||
| runAppButton.setBounds (exporterBounds.removeFromRight (exporterBounds.getHeight()).reduced (2)); | |||
| saveAndOpenInIDEButton.setBounds (exporterBounds.removeFromRight (exporterBounds.getHeight()).reduced (2)); | |||
| runAppButton->setBounds (exporterBounds.removeFromRight (exporterBounds.getHeight()).reduced (2)); | |||
| saveAndOpenInIDEButton->setBounds (exporterBounds.removeFromRight (exporterBounds.getHeight()).reduced (2)); | |||
| exporterBounds.removeFromRight (5); | |||
| exporterBox.setBounds (exporterBounds.removeFromBottom (roundToInt (exporterBounds.getHeight() / 1.8f))); | |||
| configLabel.setBounds (exporterBounds); | |||
| } | |||
| exporterBounds.removeFromRight (5); | |||
| exporterBox.setBounds (exporterBounds.removeFromBottom (roundToInt (exporterBounds.getHeight() / 1.8f))); | |||
| configLabel.setBounds (exporterBounds); | |||
| userAvatar.setBounds (bounds.removeFromRight (userAvatar.isDisplaingGPLLogo() ? roundToInt (bounds.getHeight() * 1.9f) | |||
| : bounds.getHeight()).reduced (2)); | |||
| } | |||
| void HeaderComponent::paint (Graphics& g) | |||
| @@ -98,48 +103,53 @@ void HeaderComponent::paint (Graphics& g) | |||
| if (isBuilding) | |||
| getLookAndFeel().drawSpinningWaitAnimation (g, findColour (treeIconColourId), | |||
| runAppButton->getX(), runAppButton->getY(), | |||
| runAppButton->getWidth(), runAppButton->getHeight()); | |||
| runAppButton.getX(), runAppButton.getY(), | |||
| runAppButton.getWidth(), runAppButton.getHeight()); | |||
| } | |||
| //============================================================================== | |||
| void HeaderComponent::setCurrentProject (Project* p) noexcept | |||
| void HeaderComponent::setCurrentProject (Project* newProject) | |||
| { | |||
| project = p; | |||
| exportersTree = project->getExporters(); | |||
| exportersTree.addListener (this); | |||
| updateExporters(); | |||
| projectNameValue.referTo (project->getProjectValue (Ids::name)); | |||
| projectNameValue.addListener (this); | |||
| updateName(); | |||
| isBuilding = false; | |||
| stopTimer(); | |||
| repaint(); | |||
| childProcess = ProjucerApplication::getApp().childProcessCache->getExisting (*project); | |||
| projectNameLabel.setText ({}, dontSendNotification); | |||
| if (childProcess != nullptr) | |||
| { | |||
| childProcess->activityList.addChangeListener (this); | |||
| childProcess->errorList.addChangeListener (this); | |||
| project = newProject; | |||
| runAppButton->setTooltip ({}); | |||
| runAppButton->setEnabled (true); | |||
| } | |||
| else | |||
| if (project != nullptr) | |||
| { | |||
| runAppButton->setTooltip ("Enable live-build engine to launch application"); | |||
| runAppButton->setEnabled (false); | |||
| exportersTree = project->getExporters(); | |||
| exportersTree.addListener (this); | |||
| updateExporters(); | |||
| projectNameValue.referTo (project->getProjectValue (Ids::name)); | |||
| projectNameValue.addListener (this); | |||
| updateName(); | |||
| childProcess = ProjucerApplication::getApp().childProcessCache->getExisting (*project); | |||
| if (childProcess != nullptr) | |||
| { | |||
| childProcess->activityList.addChangeListener (this); | |||
| childProcess->errorList.addChangeListener (this); | |||
| runAppButton.setTooltip ({}); | |||
| runAppButton.setEnabled (true); | |||
| } | |||
| else | |||
| { | |||
| runAppButton.setTooltip ("Enable live-build engine to launch application"); | |||
| runAppButton.setEnabled (false); | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void HeaderComponent::updateExporters() noexcept | |||
| void HeaderComponent::updateExporters() | |||
| { | |||
| auto selectedName = getSelectedExporterName(); | |||
| auto selectedExporter = getSelectedExporter(); | |||
| exporterBox.clear(); | |||
| auto preferredExporterIndex = -1; | |||
| @@ -149,7 +159,7 @@ void HeaderComponent::updateExporters() noexcept | |||
| { | |||
| exporterBox.addItem (exporter->getName(), i + 1); | |||
| if (selectedName == exporter->getName()) | |||
| if (selectedExporter != nullptr && exporter->getName() == selectedExporter->getName()) | |||
| exporterBox.setSelectedId (i + 1); | |||
| if (exporter->getName().contains (ProjectExporter::getCurrentPlatformExporterName()) && preferredExporterIndex == -1) | |||
| @@ -177,31 +187,56 @@ void HeaderComponent::updateExporters() noexcept | |||
| updateExporterButton(); | |||
| } | |||
| String HeaderComponent::getSelectedExporterName() const noexcept | |||
| std::unique_ptr<ProjectExporter> HeaderComponent::getSelectedExporter() const | |||
| { | |||
| return exporterBox.getItemText (exporterBox.getSelectedItemIndex()); | |||
| if (project != nullptr) | |||
| { | |||
| int i = 0; | |||
| auto selectedIndex = exporterBox.getSelectedItemIndex(); | |||
| for (Project::ExporterIterator exporter (*project); exporter.next();) | |||
| if (i++ == selectedIndex) | |||
| return std::move (exporter.exporter); | |||
| } | |||
| return nullptr; | |||
| } | |||
| bool HeaderComponent::canCurrentExporterLaunchProject() const noexcept | |||
| bool HeaderComponent::canCurrentExporterLaunchProject() const | |||
| { | |||
| for (Project::ExporterIterator exporter (*project); exporter.next();) | |||
| if (exporter->getName() == getSelectedExporterName() && exporter->canLaunchProject()) | |||
| return true; | |||
| if (project != nullptr) | |||
| { | |||
| if (auto selectedExporter = getSelectedExporter()) | |||
| { | |||
| for (Project::ExporterIterator exporter (*project); exporter.next();) | |||
| if (exporter->canLaunchProject() && exporter->getName() == selectedExporter->getName()) | |||
| return true; | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| //============================================================================== | |||
| void HeaderComponent::sidebarTabsWidthChanged (int newWidth) noexcept | |||
| void HeaderComponent::sidebarTabsWidthChanged (int newWidth) | |||
| { | |||
| tabsWidth = newWidth; | |||
| resized(); | |||
| } | |||
| void HeaderComponent::liveBuildEnablementChanged (bool isEnabled) | |||
| { | |||
| runAppButton.setVisible (isEnabled); | |||
| } | |||
| //============================================================================== | |||
| void HeaderComponent::changeListenerCallback (ChangeBroadcaster*) | |||
| void HeaderComponent::changeListenerCallback (ChangeBroadcaster* source) | |||
| { | |||
| if (childProcess != nullptr) | |||
| if (source == &userAvatar) | |||
| { | |||
| resized(); | |||
| } | |||
| else if (childProcess != nullptr && source == &childProcess->activityList) | |||
| { | |||
| if (childProcess->activityList.getNumActivities() > 0) | |||
| buildPing(); | |||
| @@ -221,30 +256,37 @@ void HeaderComponent::timerCallback() | |||
| } | |||
| //============================================================================== | |||
| void HeaderComponent::initialiseButtons() noexcept | |||
| void HeaderComponent::initialiseButtons() | |||
| { | |||
| auto& icons = getIcons(); | |||
| addAndMakeVisible (projectSettingsButton); | |||
| projectSettingsButton.onClick = [this] { projectContentComponent->showProjectSettings(); }; | |||
| projectSettingsButton.reset (new IconButton ("Project Settings", &icons.settings)); | |||
| addAndMakeVisible (projectSettingsButton.get()); | |||
| projectSettingsButton->onClick = [this] | |||
| addAndMakeVisible (saveAndOpenInIDEButton); | |||
| saveAndOpenInIDEButton.setBackgroundColour (Colours::white); | |||
| saveAndOpenInIDEButton.setIconInset (7); | |||
| saveAndOpenInIDEButton.onClick = [this] | |||
| { | |||
| if (auto* pcc = findParentComponentOfClass<ProjectContentComponent>()) | |||
| pcc->showProjectSettings(); | |||
| }; | |||
| if (project != nullptr) | |||
| { | |||
| if (project->hasIncompatibleLicenseTypeAndSplashScreenSetting()) | |||
| { | |||
| auto child = project->getProjectMessages().getChildWithName (ProjectMessages::Ids::warning) | |||
| .getChildWithName (ProjectMessages::Ids::incompatibleLicense); | |||
| saveAndOpenInIDEButton.reset (new IconButton ("Save and Open in IDE", nullptr)); | |||
| addAndMakeVisible (saveAndOpenInIDEButton.get()); | |||
| saveAndOpenInIDEButton->isIDEButton = true; | |||
| saveAndOpenInIDEButton->onClick = [this] | |||
| { | |||
| if (auto* pcc = findParentComponentOfClass<ProjectContentComponent>()) | |||
| pcc->openInSelectedIDE (true); | |||
| if (child.isValid()) | |||
| child.setProperty (ProjectMessages::Ids::isVisible, true, nullptr); | |||
| } | |||
| else | |||
| { | |||
| if (auto exporter = getSelectedExporter()) | |||
| project->openProjectInIDE (*exporter, true); | |||
| } | |||
| } | |||
| }; | |||
| runAppButton.reset (new IconButton ("Run Application", &icons.play)); | |||
| addAndMakeVisible (runAppButton.get()); | |||
| runAppButton->onClick = [this] | |||
| addAndMakeVisible (runAppButton); | |||
| runAppButton.setIconInset (7); | |||
| runAppButton.onClick = [this] | |||
| { | |||
| if (childProcess != nullptr) | |||
| childProcess->launchApp(); | |||
| @@ -253,22 +295,26 @@ void HeaderComponent::initialiseButtons() noexcept | |||
| updateExporterButton(); | |||
| } | |||
| void HeaderComponent::updateName() noexcept | |||
| void HeaderComponent::updateName() | |||
| { | |||
| projectNameLabel.setText (project->getDocumentTitle(), dontSendNotification); | |||
| if (project != nullptr) | |||
| projectNameLabel.setText (project->getDocumentTitle(), dontSendNotification); | |||
| } | |||
| void HeaderComponent::updateExporterButton() noexcept | |||
| void HeaderComponent::updateExporterButton() | |||
| { | |||
| auto currentExporterName = getSelectedExporterName(); | |||
| for (auto info : ProjectExporter::getExporterTypes()) | |||
| if (auto selectedExporter = getSelectedExporter()) | |||
| { | |||
| if (currentExporterName.contains (info.name)) | |||
| auto selectedName = selectedExporter->getName(); | |||
| for (auto info : ProjectExporter::getExporterTypes()) | |||
| { | |||
| saveAndOpenInIDEButton->iconImage = info.getIcon(); | |||
| saveAndOpenInIDEButton->repaint(); | |||
| saveAndOpenInIDEButton->setEnabled (canCurrentExporterLaunchProject()); | |||
| if (selectedName.contains (info.name)) | |||
| { | |||
| saveAndOpenInIDEButton.setImage (info.getIcon()); | |||
| saveAndOpenInIDEButton.repaint(); | |||
| saveAndOpenInIDEButton.setEnabled (canCurrentExporterLaunchProject()); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -279,8 +325,8 @@ void HeaderComponent::buildPing() | |||
| if (! isTimerRunning()) | |||
| { | |||
| isBuilding = true; | |||
| runAppButton->setEnabled (false); | |||
| runAppButton->setTooltip ("Building..."); | |||
| runAppButton.setEnabled (false); | |||
| runAppButton.setTooltip ("Building..."); | |||
| startTimer (50); | |||
| } | |||
| @@ -306,23 +352,23 @@ void HeaderComponent::setRunAppButtonState (bool buildWasSuccessful) | |||
| { | |||
| if (childProcess->isAppRunning() || (! childProcess->isAppRunning() && childProcess->canLaunchApp())) | |||
| { | |||
| runAppButton->setTooltip ("Launch application"); | |||
| runAppButton.setTooltip ("Launch application"); | |||
| shouldEnableButton = true; | |||
| } | |||
| else | |||
| { | |||
| runAppButton->setTooltip ("Application can't be launched"); | |||
| runAppButton.setTooltip ("Application can't be launched"); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| runAppButton->setTooltip ("Enable live-build engine to launch application"); | |||
| runAppButton.setTooltip ("Enable live-build engine to launch application"); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| runAppButton->setTooltip ("Error building application"); | |||
| runAppButton.setTooltip ("Error building application"); | |||
| } | |||
| runAppButton->setEnabled (shouldEnableButton); | |||
| runAppButton.setEnabled (shouldEnableButton); | |||
| } | |||
| @@ -20,8 +20,11 @@ | |||
| #include "../../Application/jucer_Headers.h" | |||
| #include "../../Utility/UI/jucer_IconButton.h" | |||
| #include "jucer_UserAvatarComponent.h" | |||
| class Project; | |||
| class ProjectContentComponent; | |||
| class ProjectExporter; | |||
| class CompileEngineChildProcess; | |||
| //============================================================================== | |||
| @@ -32,7 +35,7 @@ class HeaderComponent : public Component, | |||
| private Timer | |||
| { | |||
| public: | |||
| HeaderComponent(); | |||
| HeaderComponent (ProjectContentComponent* projectContentComponent); | |||
| ~HeaderComponent() override; | |||
| //============================================================================== | |||
| @@ -40,13 +43,14 @@ public: | |||
| void paint (Graphics&) override; | |||
| //============================================================================== | |||
| void setCurrentProject (Project*) noexcept; | |||
| void setCurrentProject (Project*); | |||
| void updateExporters() noexcept; | |||
| String getSelectedExporterName() const noexcept; | |||
| bool canCurrentExporterLaunchProject() const noexcept; | |||
| void updateExporters(); | |||
| std::unique_ptr<ProjectExporter> getSelectedExporter() const; | |||
| bool canCurrentExporterLaunchProject() const; | |||
| void sidebarTabsWidthChanged (int newWidth) noexcept; | |||
| void sidebarTabsWidthChanged (int newWidth); | |||
| void liveBuildEnablementChanged (bool isEnabled); | |||
| private: | |||
| //============================================================================== | |||
| @@ -59,17 +63,17 @@ private: | |||
| void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int) override { updateIfNeeded (parentTree); } | |||
| void valueTreeChildOrderChanged (ValueTree& parentTree, int, int) override { updateIfNeeded (parentTree); } | |||
| void updateIfNeeded (ValueTree tree) noexcept | |||
| void updateIfNeeded (ValueTree tree) | |||
| { | |||
| if (tree == exportersTree) | |||
| updateExporters(); | |||
| } | |||
| //============================================================================== | |||
| void initialiseButtons() noexcept; | |||
| void initialiseButtons(); | |||
| void updateName() noexcept; | |||
| void updateExporterButton() noexcept; | |||
| void updateName(); | |||
| void updateExporterButton(); | |||
| //============================================================================== | |||
| void buildPing(); | |||
| @@ -80,17 +84,21 @@ private: | |||
| int tabsWidth = 200; | |||
| bool isBuilding = false; | |||
| ProjectContentComponent* projectContentComponent = nullptr; | |||
| Project* project = nullptr; | |||
| ValueTree exportersTree; | |||
| Value projectNameValue; | |||
| ComboBox exporterBox; | |||
| Label configLabel { "Config Label", "Selected exporter" }, | |||
| projectNameLabel; | |||
| Label configLabel { "Config Label", "Selected exporter" }, projectNameLabel; | |||
| std::unique_ptr<ImageComponent> juceIcon; | |||
| std::unique_ptr<IconButton> projectSettingsButton, saveAndOpenInIDEButton, runAppButton; | |||
| ImageComponent juceIcon; | |||
| UserAvatarComponent userAvatar { true, true }; | |||
| IconButton projectSettingsButton { "Project Settings", getIcons().settings }, | |||
| saveAndOpenInIDEButton { "Save and Open in IDE", Image() }, | |||
| runAppButton { "Run Application", getIcons().play }; | |||
| ReferenceCountedObjectPtr<CompileEngineChildProcess> childProcess; | |||
| @@ -275,13 +275,13 @@ private: | |||
| m.addItem (PopupMenu::Item ("Copy the paths from the module '" + moduleToCopy + "' to all other modules") | |||
| .setAction ([this, moduleToCopy] | |||
| { | |||
| auto& moduleList = project.getEnabledModules(); | |||
| auto& modulesList = project.getEnabledModules(); | |||
| for (Project::ExporterIterator exporter (project); exporter.next();) | |||
| { | |||
| for (int i = 0; i < moduleList.getNumModules(); ++i) | |||
| for (int i = 0; i < modulesList.getNumModules(); ++i) | |||
| { | |||
| auto modID = moduleList.getModuleID (i); | |||
| auto modID = modulesList.getModuleID (i); | |||
| if (modID != moduleToCopy) | |||
| exporter->getPathForModuleValue (modID) = exporter->getPathForModuleValue (moduleToCopy).get(); | |||
| @@ -21,44 +21,50 @@ | |||
| #include "../../LiveBuildEngine/jucer_DownloadCompileEngineThread.h" | |||
| #include "../../LiveBuildEngine/jucer_CompileEngineSettings.h" | |||
| #include "jucer_HeaderComponent.h" | |||
| #include "Sidebar/jucer_TabComponents.h" | |||
| #include "Sidebar/jucer_ProjectTab.h" | |||
| #include "Sidebar/jucer_LiveBuildTab.h" | |||
| NewFileWizard::Type* createGUIComponentWizard(); | |||
| //============================================================================== | |||
| struct LogoComponent : public Component | |||
| ProjectContentComponent::LogoComponent::LogoComponent() | |||
| { | |||
| LogoComponent() | |||
| { | |||
| if (auto svg = parseXML (BinaryData::background_logo_svg)) | |||
| logo = Drawable::createFromSVG (*svg); | |||
| else | |||
| jassertfalse; | |||
| } | |||
| if (auto svg = parseXML (BinaryData::background_logo_svg)) | |||
| logo = Drawable::createFromSVG (*svg); | |||
| } | |||
| void paint (Graphics& g) override | |||
| { | |||
| g.setColour (findColour (defaultTextColourId)); | |||
| void ProjectContentComponent::LogoComponent::paint (Graphics& g) | |||
| { | |||
| g.setColour (findColour (defaultTextColourId)); | |||
| auto r = getLocalBounds(); | |||
| auto r = getLocalBounds(); | |||
| g.setFont (15.0f); | |||
| g.drawFittedText (getVersionInfo(), r.removeFromBottom (50), Justification::centredBottom, 3); | |||
| g.setFont (15.0f); | |||
| g.drawFittedText (getVersionInfo(), r.removeFromBottom (50), Justification::centredBottom, 3); | |||
| logo->drawWithin (g, r.withTrimmedBottom (r.getHeight() / 4).toFloat(), | |||
| RectanglePlacement (RectanglePlacement::centred), 1.0f); | |||
| } | |||
| logo->drawWithin (g, r.withTrimmedBottom (r.getHeight() / 4).toFloat(), | |||
| RectanglePlacement (RectanglePlacement::centred), 1.0f); | |||
| } | |||
| static String getVersionInfo() | |||
| { | |||
| return SystemStats::getJUCEVersion() | |||
| + newLine | |||
| + ProjucerApplication::getApp().getVersionDescription(); | |||
| } | |||
| String ProjectContentComponent::LogoComponent::getVersionInfo() | |||
| { | |||
| return SystemStats::getJUCEVersion() | |||
| + newLine | |||
| + ProjucerApplication::getApp().getVersionDescription(); | |||
| } | |||
| std::unique_ptr<Drawable> logo; | |||
| }; | |||
| //============================================================================== | |||
| ProjectContentComponent::ContentViewport::ContentViewport (Component* content) | |||
| { | |||
| addAndMakeVisible (viewport); | |||
| viewport.setViewedComponent (content, true); | |||
| } | |||
| void ProjectContentComponent::ContentViewport::resized() | |||
| { | |||
| viewport.setBounds (getLocalBounds()); | |||
| } | |||
| //============================================================================== | |||
| ProjectContentComponent::ProjectContentComponent() | |||
| @@ -66,15 +72,12 @@ ProjectContentComponent::ProjectContentComponent() | |||
| setOpaque (true); | |||
| setWantsKeyboardFocus (true); | |||
| logo.reset (new LogoComponent()); | |||
| addAndMakeVisible (logo.get()); | |||
| header.reset (new HeaderComponent()); | |||
| addAndMakeVisible (header.get()); | |||
| addAndMakeVisible (logoComponent); | |||
| addAndMakeVisible (headerComponent); | |||
| addAndMakeVisible (projectMessagesComponent); | |||
| fileNameLabel.reset (new Label()); | |||
| addAndMakeVisible (fileNameLabel.get()); | |||
| fileNameLabel->setJustificationType (Justification::centred); | |||
| addAndMakeVisible (fileNameLabel); | |||
| fileNameLabel.setJustificationType (Justification::centred); | |||
| sidebarSizeConstrainer.setMinimumWidth (200); | |||
| sidebarSizeConstrainer.setMaximumWidth (500); | |||
| @@ -84,6 +87,10 @@ ProjectContentComponent::ProjectContentComponent() | |||
| ProjucerApplication::getApp().openDocumentManager.addListener (this); | |||
| isLiveBuildEnabled = getGlobalProperties().getBoolValue (Ids::liveBuildEnabled); | |||
| getGlobalProperties().addChangeListener (this); | |||
| liveBuildEnablementChanged (isLiveBuildEnabled); | |||
| Desktop::getInstance().addFocusChangeListener (this); | |||
| startTimer (1600); | |||
| } | |||
| @@ -93,15 +100,11 @@ ProjectContentComponent::~ProjectContentComponent() | |||
| Desktop::getInstance().removeFocusChangeListener (this); | |||
| killChildProcess(); | |||
| getGlobalProperties().removeChangeListener (this); | |||
| ProjucerApplication::getApp().openDocumentManager.removeListener (this); | |||
| logo.reset(); | |||
| header.reset(); | |||
| setProject (nullptr); | |||
| contentView.reset(); | |||
| fileNameLabel.reset(); | |||
| removeChildComponent (&bubbleMessage); | |||
| jassert (getNumChildComponents() <= 1); | |||
| } | |||
| void ProjectContentComponent::paint (Graphics& g) | |||
| @@ -115,11 +118,10 @@ void ProjectContentComponent::resized() | |||
| r.removeFromRight (10); | |||
| r.removeFromLeft (15); | |||
| r.removeFromBottom (40); | |||
| r.removeFromTop (5); | |||
| if (header != nullptr) | |||
| header->setBounds (r.removeFromTop (40)); | |||
| projectMessagesComponent.setBounds (r.removeFromBottom (40).withWidth (100).reduced (0, 5)); | |||
| headerComponent.setBounds (r.removeFromTop (40)); | |||
| r.removeFromTop (10); | |||
| @@ -132,19 +134,15 @@ void ProjectContentComponent::resized() | |||
| if (resizerBar != nullptr) | |||
| resizerBar->setBounds (r.withWidth (4)); | |||
| if (auto* h = dynamic_cast<HeaderComponent*> (header.get())) | |||
| h->sidebarTabsWidthChanged (sidebarTabs.getWidth()); | |||
| headerComponent.sidebarTabsWidthChanged (sidebarTabs.getWidth()); | |||
| if (contentView != nullptr) | |||
| { | |||
| if (fileNameLabel != nullptr && fileNameLabel->isVisible()) | |||
| fileNameLabel->setBounds (r.removeFromTop (15)); | |||
| fileNameLabel.setBounds (r.removeFromTop (15)); | |||
| contentView->setBounds (r); | |||
| } | |||
| if (logo != nullptr) | |||
| logo->setBounds (r.reduced (r.getWidth() / 6, r.getHeight() / 6)); | |||
| logoComponent.setBounds (r.reduced (r.getWidth() / 6, r.getHeight() / 6)); | |||
| } | |||
| void ProjectContentComponent::lookAndFeelChanged() | |||
| @@ -165,7 +163,7 @@ void ProjectContentComponent::setProject (Project* newProject) | |||
| { | |||
| if (project != newProject) | |||
| { | |||
| lastCrashMessage = String(); | |||
| lastCrashMessage = {}; | |||
| killChildProcess(); | |||
| if (project != nullptr) | |||
| @@ -179,6 +177,8 @@ void ProjectContentComponent::setProject (Project* newProject) | |||
| if (project != nullptr) | |||
| rebuildProjectTabs(); | |||
| projectMessagesComponent.setProject (newProject); | |||
| } | |||
| } | |||
| @@ -201,20 +201,21 @@ void ProjectContentComponent::createProjectTabs() | |||
| auto tabColour = Colours::transparentBlack; | |||
| auto* pTab = new ProjectTab (project); | |||
| sidebarTabs.addTab ("Project", tabColour, pTab, true); | |||
| CompileEngineChildProcess::Ptr childProc (getChildProcess()); | |||
| sidebarTabs.addTab (getProjectTabName(), tabColour, new ProjectTab (project), true); | |||
| sidebarTabs.addTab ("Build", tabColour, new LiveBuildTab (childProc, lastCrashMessage), true); | |||
| if (childProc != nullptr) | |||
| if (isLiveBuildEnabled) | |||
| { | |||
| childProc->crashHandler = [this] (const String& m) { this->handleCrash (m); }; | |||
| CompileEngineChildProcess::Ptr childProc (getChildProcess()); | |||
| sidebarTabs.addTab (getBuildTabName(), tabColour, new LiveBuildTab (childProc, lastCrashMessage), true); | |||
| sidebarTabs.getTabbedButtonBar().getTabButton (1)->setExtraComponent (new BuildStatusTabComp (childProc->errorList, | |||
| childProc->activityList), | |||
| TabBarButton::afterText); | |||
| if (childProc != nullptr) | |||
| { | |||
| childProc->crashHandler = [this] (const String& m) { this->handleCrash (m); }; | |||
| sidebarTabs.getTabbedButtonBar().getTabButton (1)->setExtraComponent (new BuildStatusTabComp (childProc->errorList, | |||
| childProc->activityList), | |||
| TabBarButton::afterText); | |||
| } | |||
| } | |||
| } | |||
| @@ -255,7 +256,12 @@ void ProjectContentComponent::rebuildProjectTabs() | |||
| sidebarTabs.setBounds (0, 0, lastTreeWidth, getHeight()); | |||
| sidebarTabs.setCurrentTabIndex (settings.getValue ("lastViewedTabIndex", "0").getIntValue()); | |||
| auto lastTabIndex = settings.getValue ("lastViewedTabIndex", "0").getIntValue(); | |||
| if (lastTabIndex >= sidebarTabs.getNumTabs()) | |||
| lastTabIndex = 0; | |||
| sidebarTabs.setCurrentTabIndex (lastTabIndex); | |||
| auto* projectTab = getProjectTab(); | |||
| for (int i = 2; i >= 0; --i) | |||
| @@ -272,16 +278,13 @@ void ProjectContentComponent::rebuildProjectTabs() | |||
| updateMissingFileStatuses(); | |||
| if (auto* h = dynamic_cast<HeaderComponent*> (header.get())) | |||
| { | |||
| h->setVisible (true); | |||
| h->setCurrentProject (project); | |||
| } | |||
| headerComponent.setVisible (true); | |||
| headerComponent.setCurrentProject (project); | |||
| } | |||
| else | |||
| { | |||
| sidebarTabs.setVisible (false); | |||
| header->setVisible (false); | |||
| headerComponent.setVisible (false); | |||
| } | |||
| resized(); | |||
| @@ -320,9 +323,19 @@ bool ProjectContentComponent::documentAboutToClose (OpenDocumentManager::Documen | |||
| return true; | |||
| } | |||
| void ProjectContentComponent::changeListenerCallback (ChangeBroadcaster*) | |||
| void ProjectContentComponent::changeListenerCallback (ChangeBroadcaster* broadcaster) | |||
| { | |||
| updateMissingFileStatuses(); | |||
| if (broadcaster == project) | |||
| { | |||
| updateMissingFileStatuses(); | |||
| } | |||
| else if (broadcaster == &getGlobalProperties()) | |||
| { | |||
| auto isEnabled = ProjucerApplication::getApp().isLiveBuildEnabled(); | |||
| if (isLiveBuildEnabled != isEnabled) | |||
| liveBuildEnablementChanged (isEnabled); | |||
| } | |||
| } | |||
| void ProjectContentComponent::refreshProjectTreeFileStatuses() | |||
| @@ -344,8 +357,7 @@ bool ProjectContentComponent::showEditorForFile (const File& f, bool grabFocus) | |||
| if (getCurrentFile() == f | |||
| || showDocument (ProjucerApplication::getApp().openDocumentManager.openFile (project, f), grabFocus)) | |||
| { | |||
| fileNameLabel->setText (f.getFileName(), dontSendNotification); | |||
| fileNameLabel.setText (f.getFileName(), dontSendNotification); | |||
| return true; | |||
| } | |||
| @@ -394,8 +406,7 @@ void ProjectContentComponent::hideEditor() | |||
| currentDocument = nullptr; | |||
| contentView.reset(); | |||
| if (fileNameLabel != nullptr) | |||
| fileNameLabel->setVisible (false); | |||
| fileNameLabel.setVisible (false); | |||
| ProjucerApplication::getCommandManager().commandStatusChanged(); | |||
| resized(); | |||
| @@ -425,7 +436,7 @@ bool ProjectContentComponent::setEditorComponent (Component* editor, | |||
| contentView.reset (viewport); | |||
| currentDocument = nullptr; | |||
| fileNameLabel->setVisible (false); | |||
| fileNameLabel.setVisible (false); | |||
| addAndMakeVisible (viewport); | |||
| } | |||
| @@ -433,8 +444,8 @@ bool ProjectContentComponent::setEditorComponent (Component* editor, | |||
| { | |||
| contentView.reset (editor); | |||
| currentDocument = doc; | |||
| fileNameLabel->setText (doc->getFile().getFileName(), dontSendNotification); | |||
| fileNameLabel->setVisible (true); | |||
| fileNameLabel.setText (doc->getFile().getFileName(), dontSendNotification); | |||
| fileNameLabel.setVisible (true); | |||
| addAndMakeVisible (editor); | |||
| } | |||
| @@ -460,7 +471,8 @@ Component* ProjectContentComponent::getEditorComponentContent() const | |||
| void ProjectContentComponent::closeDocument() | |||
| { | |||
| if (currentDocument != nullptr) | |||
| ProjucerApplication::getApp().openDocumentManager.closeDocument (currentDocument, true); | |||
| ProjucerApplication::getApp().openDocumentManager | |||
| .closeDocument (currentDocument, OpenDocumentManager::SaveIfNeeded::yes); | |||
| else if (contentView != nullptr) | |||
| if (! goToPreviousFile()) | |||
| hideEditor(); | |||
| @@ -534,15 +546,10 @@ bool ProjectContentComponent::goToCounterpart() | |||
| return false; | |||
| } | |||
| bool ProjectContentComponent::saveProject (bool shouldWait, bool openInIDE) | |||
| bool ProjectContentComponent::saveProject() | |||
| { | |||
| if (project != nullptr) | |||
| { | |||
| const ScopedValueSetter<bool> valueSetter (project->shouldWaitAfterSaving, shouldWait, false); | |||
| project->setOpenInIDEAfterSaving (openInIDE); | |||
| return (project->save (true, true) == FileBasedDocument::savedOk); | |||
| } | |||
| return false; | |||
| } | |||
| @@ -550,7 +557,7 @@ bool ProjectContentComponent::saveProject (bool shouldWait, bool openInIDE) | |||
| void ProjectContentComponent::closeProject() | |||
| { | |||
| if (auto* mw = findParentComponentOfClass<MainWindow>()) | |||
| mw->closeCurrentProject (true); | |||
| mw->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes); | |||
| } | |||
| void ProjectContentComponent::showProjectSettings() | |||
| @@ -560,8 +567,8 @@ void ProjectContentComponent::showProjectSettings() | |||
| void ProjectContentComponent::showCurrentExporterSettings() | |||
| { | |||
| if (auto* h = dynamic_cast<HeaderComponent*> (header.get())) | |||
| showExporterSettings (h->getSelectedExporterName()); | |||
| if (auto selected = headerComponent.getSelectedExporter()) | |||
| showExporterSettings (selected->getName()); | |||
| } | |||
| void ProjectContentComponent::showExporterSettings (const String& exporterName) | |||
| @@ -573,7 +580,7 @@ void ProjectContentComponent::showExporterSettings (const String& exporterName) | |||
| if (auto* exportersPanel = getProjectTab()->getExportersTreePanel()) | |||
| { | |||
| if (auto* exporters = dynamic_cast<TreeItemTypes::ExportersTreeRoot*>(exportersPanel->rootItem.get())) | |||
| if (auto* exporters = dynamic_cast<TreeItemTypes::ExportersTreeRoot*> (exportersPanel->rootItem.get())) | |||
| { | |||
| for (auto i = exporters->getNumSubItems(); i >= 0; --i) | |||
| { | |||
| @@ -637,29 +644,8 @@ StringArray ProjectContentComponent::getExportersWhichCanLaunch() const | |||
| void ProjectContentComponent::openInSelectedIDE (bool saveFirst) | |||
| { | |||
| if (project != nullptr) | |||
| { | |||
| if (auto* headerComp = dynamic_cast<HeaderComponent*> (header.get())) | |||
| { | |||
| auto selectedIDE = headerComp->getSelectedExporterName(); | |||
| for (Project::ExporterIterator exporter (*project); exporter.next();) | |||
| { | |||
| if (exporter->canLaunchProject() && exporter->getName().contains (selectedIDE)) | |||
| { | |||
| auto tempProject = project->isTemporaryProject(); // store this before saving as it will always be false after | |||
| if (saveFirst && ! saveProject (exporter->isXcode(), true)) | |||
| return; | |||
| if (tempProject) | |||
| return; | |||
| exporter->launchProject(); | |||
| return; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| if (auto selectedExporter = headerComponent.getSelectedExporter()) | |||
| project->openProjectInIDE (*selectedExporter, saveFirst); | |||
| } | |||
| static void newExporterMenuCallback (int result, ProjectContentComponent* comp) | |||
| @@ -801,7 +787,8 @@ void ProjectContentComponent::getAllCommands (Array <CommandID>& commands) | |||
| CommandIDs::reinstantiateComp, | |||
| CommandIDs::showWarnings, | |||
| CommandIDs::nextError, | |||
| CommandIDs::prevError }); | |||
| CommandIDs::prevError, | |||
| CommandIDs::addNewGUIFile }); | |||
| } | |||
| void ProjectContentComponent::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result) | |||
| @@ -822,7 +809,7 @@ void ProjectContentComponent::getCommandInfo (const CommandID commandID, Applica | |||
| result.setInfo ("Save Project", | |||
| "Saves the current project", | |||
| CommandCategories::general, 0); | |||
| result.setActive (project != nullptr && ! project->isCurrentlySaving()); | |||
| result.setActive (project != nullptr && ! project->isSaveAndExportDisabled() && ! project->isCurrentlySaving()); | |||
| result.defaultKeypresses.add ({ 'p', ModifierKeys::commandModifier, 0 }); | |||
| break; | |||
| @@ -901,7 +888,7 @@ void ProjectContentComponent::getCommandInfo (const CommandID commandID, Applica | |||
| result.setInfo ("Show Build Tab", | |||
| "Shows the tab containing the build panel", | |||
| CommandCategories::general, 0); | |||
| result.setActive (project != nullptr); | |||
| result.setActive (project != nullptr && isLiveBuildEnabled); | |||
| result.defaultKeypresses.add ({ 'b', cmdCtrl, 0 }); | |||
| break; | |||
| @@ -941,14 +928,14 @@ void ProjectContentComponent::getCommandInfo (const CommandID commandID, Applica | |||
| result.setInfo ("Open in IDE...", | |||
| "Launches the project in an external IDE", | |||
| CommandCategories::general, 0); | |||
| result.setActive (ProjectExporter::canProjectBeLaunched (project)); | |||
| result.setActive (ProjectExporter::canProjectBeLaunched (project) && ! project->isSaveAndExportDisabled()); | |||
| break; | |||
| case CommandIDs::saveAndOpenInIDE: | |||
| result.setInfo ("Save Project and Open in IDE...", | |||
| "Saves the project and launches it in an external IDE", | |||
| CommandCategories::general, 0); | |||
| result.setActive (ProjectExporter::canProjectBeLaunched (project) && ! project->isCurrentlySaving()); | |||
| result.setActive (ProjectExporter::canProjectBeLaunched (project) && ! project->isSaveAndExportDisabled() && ! project->isCurrentlySaving()); | |||
| result.defaultKeypresses.add ({ 'l', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0 }); | |||
| break; | |||
| @@ -1055,6 +1042,13 @@ void ProjectContentComponent::getCommandInfo (const CommandID commandID, Applica | |||
| result.setActive (childProcess != nullptr && ! childProcess->errorList.isEmpty()); | |||
| break; | |||
| case CommandIDs::addNewGUIFile: | |||
| result.setInfo ("Add new GUI Component...", | |||
| "Adds a new GUI Component file to the project", | |||
| CommandCategories::general, | |||
| (! ProjucerApplication::getApp().isGUIEditorEnabled() ? ApplicationCommandInfo::isDisabled : 0)); | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| @@ -1131,6 +1125,8 @@ bool ProjectContentComponent::perform (const InvocationInfo& info) | |||
| case CommandIDs::nextError: showNextError(); break; | |||
| case CommandIDs::prevError: showPreviousError(); break; | |||
| case CommandIDs::addNewGUIFile: addNewGUIFile(); break; | |||
| default: | |||
| return false; | |||
| } | |||
| @@ -1175,7 +1171,7 @@ void ProjectContentComponent::setBuildEnabled (bool isEnabled, bool displayError | |||
| void ProjectContentComponent::cleanAll() | |||
| { | |||
| lastCrashMessage = String(); | |||
| lastCrashMessage = {}; | |||
| if (childProcess != nullptr) | |||
| childProcess->cleanAll(); | |||
| @@ -1204,9 +1200,11 @@ bool ProjectContentComponent::isBuildEnabled() const | |||
| void ProjectContentComponent::refreshTabsIfBuildStatusChanged() | |||
| { | |||
| if (project != nullptr | |||
| && (sidebarTabs.getNumTabs() < 2 | |||
| || isBuildEnabled() != isBuildTabEnabled())) | |||
| && isLiveBuildEnabled | |||
| && (sidebarTabs.getNumTabs() < 2 || isBuildEnabled() != isBuildTabEnabled())) | |||
| { | |||
| rebuildProjectTabs(); | |||
| } | |||
| } | |||
| bool ProjectContentComponent::areWarningsEnabled() const | |||
| @@ -1261,6 +1259,15 @@ void ProjectContentComponent::reinstantiateLivePreviewWindows() | |||
| childProcess->reinstantiatePreviews(); | |||
| } | |||
| void ProjectContentComponent::addNewGUIFile() | |||
| { | |||
| if (project != nullptr) | |||
| { | |||
| std::unique_ptr<NewFileWizard::Type> wizard (createGUIComponentWizard()); | |||
| wizard->createNewFile (*project, project->getMainGroup()); | |||
| } | |||
| } | |||
| void ProjectContentComponent::launchApp() | |||
| { | |||
| if (childProcess != nullptr) | |||
| @@ -1301,6 +1308,17 @@ void ProjectContentComponent::timerCallback() | |||
| refreshTabsIfBuildStatusChanged(); | |||
| } | |||
| void ProjectContentComponent::liveBuildEnablementChanged (bool isEnabled) | |||
| { | |||
| isLiveBuildEnabled = isEnabled; | |||
| if (! isLiveBuildEnabled) | |||
| killChildProcess(); | |||
| rebuildProjectTabs(); | |||
| headerComponent.liveBuildEnablementChanged (isLiveBuildEnabled); | |||
| } | |||
| bool ProjectContentComponent::isContinuousRebuildEnabled() | |||
| { | |||
| return project != nullptr && project->getCompileEngineSettings().isContinuousRebuildEnabled(); | |||
| @@ -19,11 +19,12 @@ | |||
| #pragma once | |||
| #include "../../CodeEditor/jucer_OpenDocumentManager.h" | |||
| #include "jucer_HeaderComponent.h" | |||
| #include "jucer_ProjectMessagesComponent.h" | |||
| class CompileEngineChildProcess; | |||
| class ProjectTab; | |||
| class LiveBuildTab; | |||
| class HeaderComponent; | |||
| //============================================================================== | |||
| class ProjectContentComponent : public Component, | |||
| @@ -39,7 +40,7 @@ public: | |||
| ~ProjectContentComponent() override; | |||
| Project* getProject() const noexcept { return project; } | |||
| virtual void setProject (Project*); | |||
| void setProject (Project*); | |||
| void saveTreeViewState(); | |||
| void saveOpenDocumentList(); | |||
| @@ -67,14 +68,14 @@ public: | |||
| bool canGoToCounterpart() const; | |||
| bool goToCounterpart(); | |||
| bool saveProject (bool shouldWait = false, bool openInIDE = false); | |||
| bool saveProject(); | |||
| void closeProject(); | |||
| void openInSelectedIDE (bool saveFirst); | |||
| void showNewExporterMenu(); | |||
| void showProjectTab() { sidebarTabs.setCurrentTabIndex (0); } | |||
| void showBuildTab() { sidebarTabs.setCurrentTabIndex (1); } | |||
| int getCurrentTabIndex() { return sidebarTabs.getCurrentTabIndex(); } | |||
| void showProjectTab() { sidebarTabs.setCurrentTabIndex (0); } | |||
| void showBuildTab() { sidebarTabs.setCurrentTabIndex (1); } | |||
| int getCurrentTabIndex() { return sidebarTabs.getCurrentTabIndex(); } | |||
| void showFilesPanel() { showProjectPanel (0); } | |||
| void showModulesPanel() { showProjectPanel (1); } | |||
| @@ -99,6 +100,7 @@ public: | |||
| void showNextError(); | |||
| void showPreviousError(); | |||
| void reinstantiateLivePreviewWindows(); | |||
| void addNewGUIFile(); | |||
| void showBubbleMessage (Rectangle<int>, const String&); | |||
| @@ -129,28 +131,31 @@ public: | |||
| void childBoundsChanged (Component*) override; | |||
| void lookAndFeelChanged() override; | |||
| String lastCrashMessage; | |||
| ProjectMessagesComponent& getProjectMessagesComponent() { return projectMessagesComponent; } | |||
| private: | |||
| friend HeaderComponent; | |||
| static String getProjectTabName() { return "Project"; } | |||
| static String getBuildTabName() { return "Build"; } | |||
| private: | |||
| //============================================================================== | |||
| Project* project = nullptr; | |||
| OpenDocumentManager::Document* currentDocument = nullptr; | |||
| RecentDocumentList recentDocumentList; | |||
| std::unique_ptr<Component> logo, translationTool, contentView, header; | |||
| struct LogoComponent : public Component | |||
| { | |||
| LogoComponent(); | |||
| void paint (Graphics& g) override; | |||
| static String getVersionInfo(); | |||
| TabbedComponent sidebarTabs { TabbedButtonBar::TabsAtTop }; | |||
| std::unique_ptr<ResizableEdgeComponent> resizerBar; | |||
| ComponentBoundsConstrainer sidebarSizeConstrainer; | |||
| std::unique_ptr<Drawable> logo; | |||
| }; | |||
| BubbleMessageComponent bubbleMessage; | |||
| ReferenceCountedObjectPtr<CompileEngineChildProcess> childProcess; | |||
| bool isForeground = false; | |||
| struct ContentViewport : public Component | |||
| { | |||
| ContentViewport (Component* content); | |||
| void resized() override; | |||
| std::unique_ptr<Label> fileNameLabel; | |||
| Viewport viewport; | |||
| int lastViewedTab = 0; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContentViewport) | |||
| }; | |||
| //============================================================================== | |||
| bool documentAboutToClose (OpenDocumentManager::Document*) override; | |||
| @@ -160,6 +165,8 @@ private: | |||
| void globalFocusChanged (Component*) override; | |||
| void timerCallback() override; | |||
| void liveBuildEnablementChanged (bool isEnabled); | |||
| bool isContinuousRebuildEnabled(); | |||
| void setContinuousRebuildEnabled (bool b); | |||
| @@ -178,23 +185,26 @@ private: | |||
| bool canSelectedProjectBeLaunch(); | |||
| //============================================================================== | |||
| struct ContentViewport : public Component | |||
| { | |||
| ContentViewport (Component* content) | |||
| { | |||
| addAndMakeVisible (viewport); | |||
| viewport.setViewedComponent (content, true); | |||
| } | |||
| Project* project = nullptr; | |||
| OpenDocumentManager::Document* currentDocument = nullptr; | |||
| RecentDocumentList recentDocumentList; | |||
| void resized() override | |||
| { | |||
| viewport.setBounds (getLocalBounds()); | |||
| } | |||
| LogoComponent logoComponent; | |||
| HeaderComponent headerComponent { this }; | |||
| ProjectMessagesComponent projectMessagesComponent; | |||
| Label fileNameLabel; | |||
| TabbedComponent sidebarTabs { TabbedButtonBar::TabsAtTop }; | |||
| std::unique_ptr<ResizableEdgeComponent> resizerBar; | |||
| ComponentBoundsConstrainer sidebarSizeConstrainer; | |||
| std::unique_ptr<Component> translationTool, contentView; | |||
| BubbleMessageComponent bubbleMessage; | |||
| Viewport viewport; | |||
| ReferenceCountedObjectPtr<CompileEngineChildProcess> childProcess; | |||
| String lastCrashMessage; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContentViewport) | |||
| }; | |||
| bool isForeground = false, isLiveBuildEnabled = false; | |||
| int lastViewedTab = 0; | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectContentComponent) | |||
| }; | |||
| @@ -0,0 +1,544 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE 6 technical preview. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| You may use this code under the terms of the GPL v3 | |||
| (see www.gnu.org/licenses). | |||
| For this technical preview, this file is not subject to commercial licensing. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| #pragma once | |||
| #include "../../Application/jucer_CommonHeaders.h" | |||
| #include "../../Application/jucer_Application.h" | |||
| //============================================================================== | |||
| class MessagesPopupWindow : public Component, | |||
| private ComponentMovementWatcher | |||
| { | |||
| public: | |||
| MessagesPopupWindow (Component& target, Component& parent, Project& project) | |||
| : ComponentMovementWatcher (&parent), | |||
| targetComponent (target), | |||
| parentComponent (parent), | |||
| messagesListComponent (*this, project) | |||
| { | |||
| parentComponent.addAndMakeVisible (this); | |||
| setAlwaysOnTop (true); | |||
| addAndMakeVisible (viewport); | |||
| viewport.setScrollBarsShown (true, false); | |||
| viewport.setViewedComponent (&messagesListComponent, false); | |||
| setOpaque (true); | |||
| } | |||
| void paint (Graphics& g) override | |||
| { | |||
| g.fillAll (findColour (secondaryBackgroundColourId)); | |||
| } | |||
| void resized() override | |||
| { | |||
| viewport.setBounds (getLocalBounds()); | |||
| } | |||
| bool isListShowing() const | |||
| { | |||
| return messagesListComponent.getRequiredHeight() > 0; | |||
| } | |||
| void updateBounds (bool animate) | |||
| { | |||
| auto targetBounds = parentComponent.getLocalArea (&targetComponent, targetComponent.getLocalBounds()); | |||
| auto height = jmin (messagesListComponent.getRequiredHeight(), maxHeight); | |||
| auto yPos = jmax (indent, targetBounds.getY() - height); | |||
| Rectangle<int> bounds (targetBounds.getX(), yPos, | |||
| jmin (width, parentComponent.getWidth() - targetBounds.getX() - indent), targetBounds.getY() - yPos); | |||
| auto& animator = Desktop::getInstance().getAnimator(); | |||
| if (animate) | |||
| { | |||
| setBounds (bounds.withY (targetBounds.getY())); | |||
| animator.animateComponent (this, bounds, 1.0f, 150, false, 1.0, 1.0); | |||
| } | |||
| else | |||
| { | |||
| if (animator.isAnimating (this)) | |||
| animator.cancelAnimation (this, false); | |||
| setBounds (bounds); | |||
| } | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| class MessagesListComponent : public Component, | |||
| private ValueTree::Listener, | |||
| private AsyncUpdater | |||
| { | |||
| public: | |||
| MessagesListComponent (MessagesPopupWindow& o, Project& currentProject) | |||
| : owner (o), | |||
| project (currentProject) | |||
| { | |||
| messagesTree = project.getProjectMessages(); | |||
| messagesTree.addListener (this); | |||
| setOpaque (true); | |||
| messagesChanged(); | |||
| } | |||
| void resized() override | |||
| { | |||
| auto bounds = getLocalBounds(); | |||
| auto numMessages = messages.size(); | |||
| for (size_t i = 0; i < numMessages; ++i) | |||
| { | |||
| messages[i]->setBounds (bounds.removeFromTop (messageHeight)); | |||
| if (numMessages > 1 && i != (numMessages - 1)) | |||
| bounds.removeFromTop (messageSpacing); | |||
| } | |||
| } | |||
| void paint (Graphics& g) override | |||
| { | |||
| g.fillAll (findColour (backgroundColourId).contrasting (0.2f)); | |||
| } | |||
| int getRequiredHeight() const | |||
| { | |||
| auto numMessages = (int) messages.size(); | |||
| if (numMessages > 0) | |||
| return (numMessages * messageHeight) + ((numMessages - 1) * messageSpacing); | |||
| return 0; | |||
| } | |||
| void updateSize (int parentWidth) | |||
| { | |||
| setSize (parentWidth, getRequiredHeight()); | |||
| } | |||
| private: | |||
| static constexpr int messageHeight = 65; | |||
| static constexpr int messageSpacing = 2; | |||
| //============================================================================== | |||
| struct MessageComponent : public Component | |||
| { | |||
| MessageComponent (MessagesListComponent& listComponent, | |||
| const Identifier& messageToDisplay, | |||
| std::vector<ProjectMessages::MessageAction> messageActions) | |||
| : message (messageToDisplay) | |||
| { | |||
| for (auto& action : messageActions) | |||
| { | |||
| auto button = std::make_unique<TextButton> (action.first); | |||
| addAndMakeVisible (*button); | |||
| button->onClick = action.second; | |||
| buttons.push_back (std::move (button)); | |||
| } | |||
| icon = (ProjectMessages::getTypeForMessage (message) == ProjectMessages::Ids::warning ? getIcons().warning : getIcons().info); | |||
| messageTitleLabel.setText (ProjectMessages::getTitleForMessage (message), dontSendNotification); | |||
| messageTitleLabel.setFont (Font (11.0f).boldened()); | |||
| addAndMakeVisible (messageTitleLabel); | |||
| messageDescriptionLabel.setText (ProjectMessages::getDescriptionForMessage (message), dontSendNotification); | |||
| messageDescriptionLabel.setFont (Font (11.0f)); | |||
| messageDescriptionLabel.setJustificationType (Justification::topLeft); | |||
| addAndMakeVisible (messageDescriptionLabel); | |||
| dismissButton.setShape (getLookAndFeel().getCrossShape (1.0f), false, true, false); | |||
| addAndMakeVisible (dismissButton); | |||
| dismissButton.onClick = [this, &listComponent] | |||
| { | |||
| listComponent.messagesTree.getChildWithName (ProjectMessages::getTypeForMessage (message)) | |||
| .getChildWithName (message) | |||
| .setProperty (ProjectMessages::Ids::isVisible, false, nullptr); | |||
| }; | |||
| } | |||
| void paint (Graphics& g) override | |||
| { | |||
| g.fillAll (findColour (secondaryBackgroundColourId).contrasting (0.1f)); | |||
| auto bounds = getLocalBounds().reduced (5); | |||
| g.setColour (findColour (defaultIconColourId)); | |||
| g.fillPath (icon, icon.getTransformToScaleToFit (bounds.removeFromTop (messageTitleHeight) | |||
| .removeFromLeft (messageTitleHeight).toFloat(), true)); | |||
| } | |||
| void resized() override | |||
| { | |||
| auto bounds = getLocalBounds().reduced (5); | |||
| auto topSlice = bounds.removeFromTop (messageTitleHeight); | |||
| topSlice.removeFromLeft (messageTitleHeight + 5); | |||
| topSlice.removeFromRight (5); | |||
| dismissButton.setBounds (topSlice.removeFromRight (messageTitleHeight)); | |||
| messageTitleLabel.setBounds (topSlice); | |||
| bounds.removeFromTop (5); | |||
| auto numButtons = (int) buttons.size(); | |||
| if (numButtons > 0) | |||
| { | |||
| auto buttonBounds = bounds.removeFromBottom (buttonHeight); | |||
| auto buttonWidth = roundToInt (buttonBounds.getWidth() / 3.5f); | |||
| auto requiredWidth = (numButtons * buttonWidth) + ((numButtons - 1) * buttonSpacing); | |||
| buttonBounds.reduce ((buttonBounds.getWidth() - requiredWidth) / 2, 0); | |||
| for (auto& b : buttons) | |||
| { | |||
| b->setBounds (buttonBounds.removeFromLeft (buttonWidth)); | |||
| buttonBounds.removeFromLeft (buttonSpacing); | |||
| } | |||
| bounds.removeFromBottom (5); | |||
| } | |||
| messageDescriptionLabel.setBounds (bounds); | |||
| } | |||
| static constexpr int messageTitleHeight = 11; | |||
| static constexpr int buttonHeight = messageHeight / 4; | |||
| static constexpr int buttonSpacing = 5; | |||
| Identifier message; | |||
| Path icon; | |||
| Label messageTitleLabel, messageDescriptionLabel; | |||
| std::vector<std::unique_ptr<TextButton>> buttons; | |||
| ShapeButton dismissButton { {}, | |||
| findColour (treeIconColourId), | |||
| findColour (treeIconColourId).overlaidWith (findColour (defaultHighlightedTextColourId).withAlpha (0.2f)), | |||
| findColour (treeIconColourId).overlaidWith (findColour (defaultHighlightedTextColourId).withAlpha (0.4f)) }; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessageComponent) | |||
| }; | |||
| //============================================================================== | |||
| void valueTreePropertyChanged (ValueTree&, const Identifier&) override { triggerAsyncUpdate(); } | |||
| void valueTreeChildAdded (ValueTree&, ValueTree&) override { triggerAsyncUpdate(); } | |||
| void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override { triggerAsyncUpdate(); } | |||
| void valueTreeChildOrderChanged (ValueTree&, int, int) override { triggerAsyncUpdate(); } | |||
| void valueTreeParentChanged (ValueTree&) override { triggerAsyncUpdate(); } | |||
| void valueTreeRedirected (ValueTree&) override { triggerAsyncUpdate(); } | |||
| void handleAsyncUpdate() override | |||
| { | |||
| messagesChanged(); | |||
| } | |||
| void messagesChanged() | |||
| { | |||
| auto listWasShowing = (getHeight() > 0); | |||
| auto warningsTree = messagesTree.getChildWithName (ProjectMessages::Ids::warning); | |||
| auto notificationsTree = messagesTree.getChildWithName (ProjectMessages::Ids::notification); | |||
| auto removePredicate = [warningsTree, notificationsTree] (std::unique_ptr<MessageComponent>& messageComponent) | |||
| { | |||
| for (int i = 0; i < warningsTree.getNumChildren(); ++i) | |||
| { | |||
| auto child = warningsTree.getChild (i); | |||
| if (child.getType() == messageComponent->message | |||
| && child.getProperty (ProjectMessages::Ids::isVisible)) | |||
| { | |||
| return false; | |||
| } | |||
| } | |||
| for (int i = 0; i < notificationsTree.getNumChildren(); ++i) | |||
| { | |||
| auto child = notificationsTree.getChild (i); | |||
| if (child.getType() == messageComponent->message | |||
| && child.getProperty (ProjectMessages::Ids::isVisible)) | |||
| { | |||
| return false; | |||
| } | |||
| } | |||
| return true; | |||
| }; | |||
| messages.erase (std::remove_if (std::begin (messages), std::end (messages), removePredicate), | |||
| std::end (messages)); | |||
| for (int i = 0; i < warningsTree.getNumChildren(); ++i) | |||
| { | |||
| auto child = warningsTree.getChild (i); | |||
| if (! child.getProperty (ProjectMessages::Ids::isVisible)) | |||
| continue; | |||
| if (std::find_if (std::begin (messages), std::end (messages), | |||
| [child] (const std::unique_ptr<MessageComponent>& messageComponent) { return messageComponent->message == child.getType(); }) | |||
| == std::end (messages)) | |||
| { | |||
| messages.push_back (std::make_unique<MessageComponent> (*this, child.getType(), project.getMessageActions (child.getType()))); | |||
| addAndMakeVisible (*messages.back()); | |||
| } | |||
| } | |||
| for (int i = 0; i < notificationsTree.getNumChildren(); ++i) | |||
| { | |||
| auto child = notificationsTree.getChild (i); | |||
| if (! child.getProperty (ProjectMessages::Ids::isVisible)) | |||
| continue; | |||
| if (std::find_if (std::begin (messages), std::end (messages), | |||
| [child] (const std::unique_ptr<MessageComponent>& messageComponent) { return messageComponent->message == child.getType(); }) | |||
| == std::end (messages)) | |||
| { | |||
| messages.push_back (std::make_unique<MessageComponent> (*this, child.getType(), project.getMessageActions (child.getType()))); | |||
| addAndMakeVisible (*messages.back()); | |||
| } | |||
| } | |||
| auto isNowShowing = (messages.size() > 0); | |||
| owner.updateBounds (isNowShowing != listWasShowing); | |||
| updateSize (owner.getWidth()); | |||
| } | |||
| //============================================================================== | |||
| MessagesPopupWindow& owner; | |||
| Project& project; | |||
| ValueTree messagesTree; | |||
| std::vector<std::unique_ptr<MessageComponent>> messages; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessagesListComponent) | |||
| }; | |||
| //============================================================================== | |||
| void componentMovedOrResized (bool, bool) override | |||
| { | |||
| if (isListShowing()) | |||
| updateBounds (false); | |||
| } | |||
| using ComponentMovementWatcher::componentMovedOrResized; | |||
| void componentPeerChanged() override | |||
| { | |||
| if (isListShowing()) | |||
| updateBounds (false); | |||
| } | |||
| void componentVisibilityChanged() override | |||
| { | |||
| if (isListShowing()) | |||
| updateBounds (false); | |||
| } | |||
| using ComponentMovementWatcher::componentVisibilityChanged; | |||
| //============================================================================== | |||
| static constexpr int maxHeight = 500, width = 350, indent = 20; | |||
| Component& targetComponent; | |||
| Component& parentComponent; | |||
| Viewport viewport; | |||
| MessagesListComponent messagesListComponent; | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessagesPopupWindow) | |||
| }; | |||
| //============================================================================== | |||
| class ProjectMessagesComponent : public Component | |||
| { | |||
| public: | |||
| ProjectMessagesComponent() | |||
| { | |||
| addAndMakeVisible (warningsComponent); | |||
| addAndMakeVisible (notificationsComponent); | |||
| warningsComponent.addMouseListener (this, true); | |||
| notificationsComponent.addMouseListener (this, true); | |||
| setOpaque (true); | |||
| } | |||
| //============================================================================== | |||
| void resized() override | |||
| { | |||
| auto b = getLocalBounds(); | |||
| warningsComponent.setBounds (b.removeFromLeft (b.getWidth() / 2).reduced (5)); | |||
| notificationsComponent.setBounds (b.reduced (5)); | |||
| } | |||
| void paint (Graphics& g) override | |||
| { | |||
| auto backgroundColour = findColour (backgroundColourId); | |||
| if (isMouseDown || isMouseOver) | |||
| backgroundColour = backgroundColour.overlaidWith (findColour (defaultHighlightColourId) | |||
| .withAlpha (isMouseDown ? 1.0f : 0.8f)); | |||
| g.fillAll (backgroundColour); | |||
| } | |||
| //============================================================================== | |||
| void mouseEnter (const MouseEvent&) override | |||
| { | |||
| isMouseOver = true; | |||
| repaint(); | |||
| } | |||
| void mouseExit (const MouseEvent&) override | |||
| { | |||
| isMouseOver = false; | |||
| repaint(); | |||
| } | |||
| void mouseDown (const MouseEvent&) override | |||
| { | |||
| isMouseDown = true; | |||
| repaint(); | |||
| } | |||
| void mouseUp (const MouseEvent&) override | |||
| { | |||
| isMouseDown = false; | |||
| repaint(); | |||
| if (messagesWindow != nullptr) | |||
| showOrHideAllMessages (! messagesWindow->isListShowing()); | |||
| } | |||
| //============================================================================== | |||
| void setProject (Project* newProject) | |||
| { | |||
| if (currentProject != newProject) | |||
| { | |||
| currentProject = newProject; | |||
| if (currentProject != nullptr) | |||
| { | |||
| auto* projectWindow = ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (currentProject->getFile()); | |||
| jassert (projectWindow != nullptr); | |||
| messagesWindow = std::make_unique<MessagesPopupWindow> (*this, *projectWindow, *currentProject); | |||
| auto projectMessagesTree = currentProject->getProjectMessages(); | |||
| warningsComponent.setTree (projectMessagesTree.getChildWithName (ProjectMessages::Ids::warning)); | |||
| notificationsComponent.setTree (projectMessagesTree.getChildWithName (ProjectMessages::Ids::notification)); | |||
| } | |||
| else | |||
| { | |||
| warningsComponent.setTree ({}); | |||
| notificationsComponent.setTree ({}); | |||
| } | |||
| } | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| struct MessageCountComponent : public Component, | |||
| public ValueTree::Listener | |||
| { | |||
| MessageCountComponent (ProjectMessagesComponent& o, Path pathToUse) | |||
| : owner (o), | |||
| path (pathToUse) | |||
| { | |||
| setInterceptsMouseClicks (false, false); | |||
| } | |||
| void paint (Graphics& g) override | |||
| { | |||
| auto b = getLocalBounds().toFloat(); | |||
| g.setColour (findColour ((owner.isMouseDown || owner.isMouseOver) ? defaultHighlightedTextColourId : treeIconColourId)); | |||
| g.fillPath (path, path.getTransformToScaleToFit (b.removeFromLeft (b.getWidth() / 2.0f), true)); | |||
| b.removeFromLeft (5); | |||
| g.drawFittedText (String (numMessages), b.getSmallestIntegerContainer(), Justification::centredLeft, 1); | |||
| } | |||
| void setTree (ValueTree tree) | |||
| { | |||
| messagesTree = tree; | |||
| if (messagesTree.isValid()) | |||
| messagesTree.addListener (this); | |||
| updateNumMessages(); | |||
| } | |||
| void valueTreeChildAdded (ValueTree&, ValueTree&) override { updateNumMessages(); } | |||
| void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override { updateNumMessages(); } | |||
| void updateNumMessages() | |||
| { | |||
| numMessages = messagesTree.getNumChildren(); | |||
| repaint(); | |||
| } | |||
| ProjectMessagesComponent& owner; | |||
| ValueTree messagesTree; | |||
| Path path; | |||
| int numMessages = 0; | |||
| }; | |||
| void showOrHideAllMessages (bool shouldBeVisible) | |||
| { | |||
| if (currentProject != nullptr) | |||
| { | |||
| auto messagesTree = currentProject->getProjectMessages(); | |||
| auto setVisible = [shouldBeVisible] (ValueTree subTree) | |||
| { | |||
| for (int i = 0; i < subTree.getNumChildren(); ++i) | |||
| subTree.getChild (i).setProperty (ProjectMessages::Ids::isVisible, shouldBeVisible, nullptr); | |||
| }; | |||
| setVisible (messagesTree.getChildWithName (ProjectMessages::Ids::warning)); | |||
| setVisible (messagesTree.getChildWithName (ProjectMessages::Ids::notification)); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| Project* currentProject = nullptr; | |||
| bool isMouseOver = false, isMouseDown = false; | |||
| MessageCountComponent warningsComponent { *this, getIcons().warning }, | |||
| notificationsComponent { *this, getIcons().info }; | |||
| std::unique_ptr<MessagesPopupWindow> messagesWindow; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectMessagesComponent) | |||
| }; | |||
| @@ -0,0 +1,123 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE 6 technical preview. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| You may use this code under the terms of the GPL v3 | |||
| (see www.gnu.org/licenses). | |||
| For this technical preview, this file is not subject to commercial licensing. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| #pragma once | |||
| #include "../../Application/jucer_Application.h" | |||
| class UserAvatarComponent : public Component, | |||
| public SettableTooltipClient, | |||
| public ChangeBroadcaster, | |||
| private LicenseController::LicenseStateListener | |||
| { | |||
| public: | |||
| UserAvatarComponent (bool tooltip, bool signIn) | |||
| : displayTooltip (tooltip), | |||
| signInOnClick (signIn) | |||
| { | |||
| ProjucerApplication::getApp().getLicenseController().addListener (this); | |||
| licenseStateChanged(); | |||
| } | |||
| ~UserAvatarComponent() override | |||
| { | |||
| ProjucerApplication::getApp().getLicenseController().removeListener (this); | |||
| } | |||
| void paint (Graphics& g) override | |||
| { | |||
| auto bounds = getLocalBounds(); | |||
| if (! isGPL) | |||
| { | |||
| bounds = bounds.removeFromRight (bounds.getHeight()); | |||
| Path ellipse; | |||
| ellipse.addEllipse (bounds.toFloat()); | |||
| g.reduceClipRegion (ellipse); | |||
| } | |||
| g.drawImage (userAvatarImage, bounds.toFloat(), RectanglePlacement::fillDestination); | |||
| } | |||
| void mouseUp (const MouseEvent&) override | |||
| { | |||
| if (signInOnClick) | |||
| { | |||
| PopupMenu menu; | |||
| menu.addCommandItem (ProjucerApplication::getApp().commandManager.get(), CommandIDs::loginLogout); | |||
| menu.showMenuAsync (PopupMenu::Options().withTargetComponent (this)); | |||
| } | |||
| } | |||
| bool isDisplaingGPLLogo() const noexcept { return isGPL; } | |||
| private: | |||
| Image createDefaultAvatarImage() | |||
| { | |||
| Image image (Image::ARGB, 250, 250, true); | |||
| Graphics g (image); | |||
| g.setColour (findColour (defaultButtonBackgroundColourId)); | |||
| g.fillAll(); | |||
| g.setColour (findColour (defaultIconColourId)); | |||
| auto path = getIcons().user; | |||
| g.fillPath (path, RectanglePlacement (RectanglePlacement::centred) | |||
| .getTransformToFit (path.getBounds(), image.getBounds().reduced (image.getHeight() / 5).toFloat())); | |||
| return image; | |||
| } | |||
| void licenseStateChanged() override | |||
| { | |||
| auto state = ProjucerApplication::getApp().getLicenseController().getCurrentState(); | |||
| isGPL = ProjucerApplication::getApp().getLicenseController().getCurrentState().isGPL(); | |||
| if (displayTooltip) | |||
| { | |||
| auto formattedUserString = [state]() -> String | |||
| { | |||
| if (state.isValid()) | |||
| return (state.isGPL() ? "" : (state.username + " - ")) + state.getLicenseTypeString(); | |||
| return "Not logged in"; | |||
| }(); | |||
| setTooltip (formattedUserString); | |||
| } | |||
| userAvatarImage = state.isValid() ? state.avatar : defaultAvatarImage; | |||
| repaint(); | |||
| sendChangeMessage(); | |||
| } | |||
| void lookAndFeelChanged() override | |||
| { | |||
| defaultAvatarImage = createDefaultAvatarImage(); | |||
| licenseStateChanged(); | |||
| repaint(); | |||
| } | |||
| Image userAvatarImage, defaultAvatarImage { createDefaultAvatarImage() }; | |||
| bool isGPL = false, displayTooltip = false, signInOnClick = false; | |||
| }; | |||
| @@ -22,17 +22,63 @@ | |||
| #include "../Application/jucer_Application.h" | |||
| #include "../LiveBuildEngine/jucer_CompileEngineSettings.h" | |||
| namespace | |||
| //============================================================================== | |||
| Project::ProjectFileModificationPoller::ProjectFileModificationPoller (Project& p) | |||
| : project (p) | |||
| { | |||
| startTimer (250); | |||
| } | |||
| void Project::ProjectFileModificationPoller::reset() | |||
| { | |||
| String makeValid4CC (const String& seed) | |||
| project.removeProjectMessage (ProjectMessages::Ids::jucerFileModified); | |||
| showingWarning = false; | |||
| startTimer (250); | |||
| } | |||
| void Project::ProjectFileModificationPoller::timerCallback() | |||
| { | |||
| if (project.hasProjectBeenModified()) | |||
| { | |||
| auto s = build_tools::makeValidIdentifier (seed, false, true, false) + "xxxx"; | |||
| if (! showingWarning) | |||
| { | |||
| project.addProjectMessage (ProjectMessages::Ids::jucerFileModified, | |||
| { { "Keep", [this] { keepProject(); } }, | |||
| { "Re-load from disk", [this] { reloadProjectFromDisk(); } }, | |||
| { "Ignore", [this] { reset(); } } }); | |||
| return s.substring (0, 1).toUpperCase() | |||
| + s.substring (1, 4).toLowerCase(); | |||
| stopTimer(); | |||
| showingWarning = true; | |||
| } | |||
| } | |||
| } | |||
| void Project::ProjectFileModificationPoller::reloadProjectFromDisk() | |||
| { | |||
| auto oldTemporaryDirectory = project.getTemporaryDirectory(); | |||
| auto projectFile = project.getFile(); | |||
| MessageManager::callAsync ([oldTemporaryDirectory, projectFile] | |||
| { | |||
| if (auto* mw = ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (projectFile)) | |||
| { | |||
| mw->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::no); | |||
| mw->openFile (projectFile); | |||
| if (oldTemporaryDirectory != File()) | |||
| if (auto* newProject = mw->getProject()) | |||
| newProject->setTemporaryDirectory (oldTemporaryDirectory); | |||
| } | |||
| }); | |||
| } | |||
| void Project::ProjectFileModificationPoller::keepProject() | |||
| { | |||
| project.saveProject(); | |||
| reset(); | |||
| } | |||
| //============================================================================== | |||
| Project::Project (const File& f) | |||
| : FileBasedDocument (projectFileExtension, | |||
| @@ -48,16 +94,37 @@ Project::Project (const File& f) | |||
| initialiseMainGroup(); | |||
| initialiseAudioPluginValues(); | |||
| exporterPathsModuleList.reset (new AvailableModuleList()); | |||
| setChangedFlag (false); | |||
| modificationTime = getFile().getLastModificationTime(); | |||
| auto& app = ProjucerApplication::getApp(); | |||
| if (! app.isRunningCommandLine) | |||
| app.getLicenseController().addListener (this); | |||
| app.getJUCEPathModulesList().addListener (this); | |||
| app.getUserPathsModulesList().addListener (this); | |||
| updateJUCEPathWarning(); | |||
| getGlobalProperties().addChangeListener (this); | |||
| LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (true); | |||
| } | |||
| Project::~Project() | |||
| { | |||
| projectRoot.removeListener (this); | |||
| ProjucerApplication::getApp().openDocumentManager.closeAllDocumentsUsingProject (*this, false); | |||
| getGlobalProperties().removeChangeListener (this); | |||
| auto& app = ProjucerApplication::getApp(); | |||
| app.openDocumentManager.closeAllDocumentsUsingProject (*this, OpenDocumentManager::SaveIfNeeded::no); | |||
| if (! app.isRunningCommandLine) | |||
| app.getLicenseController().removeListener (this); | |||
| app.getJUCEPathModulesList().removeListener (this); | |||
| app.getUserPathsModulesList().removeListener (this); | |||
| } | |||
| const char* Project::projectFileExtension = ".jucer"; | |||
| @@ -93,6 +160,8 @@ void Project::updateCompanyNameDependencies() | |||
| bundleIdentifierValue.setDefault (getDefaultBundleIdentifierString()); | |||
| pluginAAXIdentifierValue.setDefault (getDefaultAAXIdentifierString()); | |||
| pluginManufacturerValue.setDefault (getDefaultPluginManufacturerString()); | |||
| updateLicenseWarning(); | |||
| } | |||
| void Project::updateProjectSettings() | |||
| @@ -105,7 +174,7 @@ bool Project::setCppVersionFromOldExporterSettings() | |||
| { | |||
| auto highestLanguageStandard = -1; | |||
| for (Project::ExporterIterator exporter (*this); exporter.next();) | |||
| for (ExporterIterator exporter (*this); exporter.next();) | |||
| { | |||
| if (exporter->isXcode()) // cpp version was per-build configuration for xcode exporters | |||
| { | |||
| @@ -130,7 +199,7 @@ bool Project::setCppVersionFromOldExporterSettings() | |||
| { | |||
| if (cppLanguageStandard.toString().containsIgnoreCase ("latest")) | |||
| { | |||
| cppStandardValue = "latest"; | |||
| cppStandardValue = Project::getCppStandardVars().getLast(); | |||
| return true; | |||
| } | |||
| @@ -153,7 +222,7 @@ bool Project::setCppVersionFromOldExporterSettings() | |||
| void Project::updateDeprecatedProjectSettings() | |||
| { | |||
| for (Project::ExporterIterator exporter (*this); exporter.next();) | |||
| for (ExporterIterator exporter (*this); exporter.next();) | |||
| exporter->updateDeprecatedSettings(); | |||
| } | |||
| @@ -161,7 +230,7 @@ void Project::updateDeprecatedProjectSettingsInteractively() | |||
| { | |||
| jassert (! ProjucerApplication::getApp().isRunningCommandLine); | |||
| for (Project::ExporterIterator exporter (*this); exporter.next();) | |||
| for (ExporterIterator exporter (*this); exporter.next();) | |||
| exporter->updateDeprecatedSettingsInteractively(); | |||
| } | |||
| @@ -226,6 +295,14 @@ void Project::initialiseProjectValues() | |||
| void Project::initialiseAudioPluginValues() | |||
| { | |||
| auto makeValid4CC = [] (const String& seed) | |||
| { | |||
| auto s = build_tools::makeValidIdentifier (seed, false, true, false) + "xxxx"; | |||
| return s.substring (0, 1).toUpperCase() | |||
| + s.substring (1, 4).toLowerCase(); | |||
| }; | |||
| pluginFormatsValue.referTo (projectRoot, Ids::pluginFormats, getUndoManager(), | |||
| Array<var> (Ids::buildVST3.toString(), Ids::buildAU.toString(), Ids::buildStandalone.toString()), ","); | |||
| pluginCharacteristicsValue.referTo (projectRoot, Ids::pluginCharacteristicsValue, getUndoManager(), Array<var> (), ","); | |||
| @@ -259,7 +336,7 @@ void Project::updateOldStyleConfigList() | |||
| { | |||
| projectRoot.removeChild (deprecatedConfigsList, nullptr); | |||
| for (Project::ExporterIterator exporter (*this); exporter.next();) | |||
| for (ExporterIterator exporter (*this); exporter.next();) | |||
| { | |||
| if (exporter->getNumConfigurations() == 0) | |||
| { | |||
| @@ -287,7 +364,7 @@ void Project::moveOldPropertyFromProjectToAllExporters (Identifier name) | |||
| { | |||
| if (projectRoot.hasProperty (name)) | |||
| { | |||
| for (Project::ExporterIterator exporter (*this); exporter.next();) | |||
| for (ExporterIterator exporter (*this); exporter.next();) | |||
| exporter->settings.setProperty (name, projectRoot [name], nullptr); | |||
| projectRoot.removeProperty (name, nullptr); | |||
| @@ -325,7 +402,7 @@ void Project::removeDefunctExporters() | |||
| void Project::updateOldModulePaths() | |||
| { | |||
| for (Project::ExporterIterator exporter (*this); exporter.next();) | |||
| for (ExporterIterator exporter (*this); exporter.next();) | |||
| exporter->updateOldModulePaths(); | |||
| } | |||
| @@ -499,32 +576,6 @@ static constexpr int getBuiltJuceVersion() | |||
| + JUCE_BUILDNUMBER; | |||
| } | |||
| static bool isModuleNewerThanProjucer (const ModuleDescription& module) | |||
| { | |||
| return module.getID().startsWith ("juce_") && getJuceVersion (module.getVersion()) > getBuiltJuceVersion(); | |||
| } | |||
| void Project::warnAboutOldProjucerVersion() | |||
| { | |||
| for (auto& juceModule : ProjucerApplication::getApp().getJUCEPathModuleList().getAllModules()) | |||
| { | |||
| if (isModuleNewerThanProjucer ({ juceModule.second })) | |||
| { | |||
| if (ProjucerApplication::getApp().isRunningCommandLine) | |||
| std::cout << "WARNING! This version of the Projucer is out-of-date!" << std::endl; | |||
| else | |||
| AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, | |||
| "Projucer", | |||
| "This version of the Projucer is out-of-date!" | |||
| "\n\n" | |||
| "Always make sure that you're running the very latest version, " | |||
| "preferably compiled directly from the JUCE repository that you're working with!"); | |||
| return; | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| static File lastDocumentOpened; | |||
| @@ -560,7 +611,7 @@ Result Project::loadDocument (const File& file) | |||
| registerRecentFile (file); | |||
| enabledModuleList.reset(); | |||
| enabledModulesList.reset(); | |||
| projectRoot = newTree; | |||
| projectRoot.addListener (this); | |||
| @@ -582,60 +633,303 @@ Result Project::loadDocument (const File& file) | |||
| moveOldPropertyFromProjectToAllExporters (Ids::smallIcon); | |||
| getEnabledModules().sortAlphabetically(); | |||
| compileEngineSettings.reset (new CompileEngineSettings (projectRoot)); | |||
| exporterPathsModulesList.reset (new AvailableModulesList()); | |||
| rescanExporterPathModules (! ProjucerApplication::getApp().isRunningCommandLine); | |||
| exporterPathsModulesList->addListener (this); | |||
| setCppVersionFromOldExporterSettings(); | |||
| updateDeprecatedProjectSettings(); | |||
| setChangedFlag (false); | |||
| if (! ProjucerApplication::getApp().isRunningCommandLine) | |||
| warnAboutOldProjucerVersion(); | |||
| compileEngineSettings.reset (new CompileEngineSettings (projectRoot)); | |||
| exporterPathsModuleList.reset (new AvailableModuleList()); | |||
| rescanExporterPathModules (! ProjucerApplication::getApp().isRunningCommandLine); | |||
| updateLicenseWarning(); | |||
| return Result::ok(); | |||
| } | |||
| Result Project::saveDocument (const File& file) | |||
| { | |||
| return saveProject (file, false); | |||
| jassert (file == getFile()); | |||
| ignoreUnused (file); | |||
| return saveProject(); | |||
| } | |||
| Result Project::saveProject (const File& file, bool isCommandLineApp) | |||
| Result Project::saveProject (ProjectExporter* exporterToSave) | |||
| { | |||
| if (isSaveAndExportDisabled()) | |||
| return Result::fail ("Save and export is disabled."); | |||
| if (isSaving) | |||
| return Result::ok(); | |||
| if (isTemporaryProject()) | |||
| { | |||
| askUserWhereToSaveProject(); | |||
| saveAndMoveTemporaryProject (false); | |||
| return Result::ok(); | |||
| } | |||
| updateProjectSettings(); | |||
| if (! isCommandLineApp) | |||
| if (! ProjucerApplication::getApp().isRunningCommandLine) | |||
| { | |||
| ProjucerApplication::getApp().openDocumentManager.saveAll(); | |||
| if (! isTemporaryProject()) | |||
| registerRecentFile (file); | |||
| registerRecentFile (getFile()); | |||
| } | |||
| const ScopedValueSetter<bool> vs (isSaving, true, false); | |||
| ProjectSaver saver (*this, file); | |||
| return saver.save (! isCommandLineApp, shouldWaitAfterSaving, specifiedExporterToSave); | |||
| ProjectSaver saver (*this); | |||
| return saver.save (exporterToSave); | |||
| } | |||
| Result Project::openProjectInIDE (ProjectExporter& exporterToOpen, bool saveFirst) | |||
| { | |||
| for (ExporterIterator exporter (*this); exporter.next();) | |||
| { | |||
| if (exporter->canLaunchProject() && exporter->getName() == exporterToOpen.getName()) | |||
| { | |||
| if (isTemporaryProject()) | |||
| { | |||
| saveAndMoveTemporaryProject (true); | |||
| return Result::ok(); | |||
| } | |||
| if (saveFirst) | |||
| { | |||
| auto result = saveProject(); | |||
| if (! result.wasOk()) | |||
| return result; | |||
| } | |||
| // Workaround for a bug where Xcode thinks the project is invalid if opened immediately | |||
| // after writing | |||
| if (saveFirst && exporter->isXcode()) | |||
| Thread::sleep (1000); | |||
| exporter->launchProject(); | |||
| } | |||
| } | |||
| return Result::ok(); | |||
| } | |||
| Result Project::saveResourcesOnly (const File& file) | |||
| Result Project::saveResourcesOnly() | |||
| { | |||
| ProjectSaver saver (*this, file); | |||
| ProjectSaver saver (*this); | |||
| return saver.saveResourcesOnly(); | |||
| } | |||
| bool Project::hasIncompatibleLicenseTypeAndSplashScreenSetting() const | |||
| { | |||
| auto companyName = companyNameValue.get().toString(); | |||
| auto isJUCEProject = (companyName == "ROLI Ltd." || companyName == "JUCE"); | |||
| return ! ProjucerApplication::getApp().isRunningCommandLine && ! isJUCEProject && ! shouldDisplaySplashScreen() | |||
| && ! ProjucerApplication::getApp().getLicenseController().getCurrentState().isPaidOrGPL(); | |||
| } | |||
| bool Project::isSaveAndExportDisabled() const | |||
| { | |||
| return ! ProjucerApplication::getApp().isRunningCommandLine && hasIncompatibleLicenseTypeAndSplashScreenSetting(); | |||
| } | |||
| void Project::updateLicenseWarning() | |||
| { | |||
| if (hasIncompatibleLicenseTypeAndSplashScreenSetting()) | |||
| { | |||
| addProjectMessage (ProjectMessages::Ids::incompatibleLicense, | |||
| { { "Log in", [this] { ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (getFile())->showLoginFormOverlay(); } }, | |||
| { "Enable splash screen", [this] { displaySplashScreenValue = true; } } }); | |||
| } | |||
| else | |||
| { | |||
| removeProjectMessage (ProjectMessages::Ids::incompatibleLicense); | |||
| } | |||
| } | |||
| void Project::updateJUCEPathWarning() | |||
| { | |||
| if (ProjucerApplication::getApp().shouldPromptUserAboutIncorrectJUCEPath() | |||
| && ProjucerApplication::getApp().settings->isJUCEPathIncorrect()) | |||
| { | |||
| auto dontAskAgain = [this] | |||
| { | |||
| ProjucerApplication::getApp().setShouldPromptUserAboutIncorrectJUCEPath (false); | |||
| removeProjectMessage (ProjectMessages::Ids::jucePath); | |||
| }; | |||
| addProjectMessage (ProjectMessages::Ids::jucePath, | |||
| { { "Set path", [] { ProjucerApplication::getApp().showPathsWindow (true); } }, | |||
| { "Ignore", [this] { removeProjectMessage (ProjectMessages::Ids::jucePath); } }, | |||
| { "Don't ask again", std::move (dontAskAgain) } }); | |||
| } | |||
| else | |||
| { | |||
| removeProjectMessage (ProjectMessages::Ids::jucePath); | |||
| } | |||
| } | |||
| void Project::updateModuleWarnings() | |||
| { | |||
| auto& modules = getEnabledModules(); | |||
| bool cppStandard = false, missingDependencies = false, oldProjucer = false, moduleNotFound = false; | |||
| for (auto moduleID : modules.getAllModules()) | |||
| { | |||
| if (! cppStandard && modules.doesModuleHaveHigherCppStandardThanProject (moduleID)) | |||
| cppStandard = true; | |||
| if (! missingDependencies && ! modules.getExtraDependenciesNeeded (moduleID).isEmpty()) | |||
| missingDependencies = true; | |||
| auto info = modules.getModuleInfo (moduleID); | |||
| if (! oldProjucer && (isJUCEModule (moduleID) && getJuceVersion (info.getVersion()) > getBuiltJuceVersion())) | |||
| oldProjucer = true; | |||
| if (! moduleNotFound && ! info.isValid()) | |||
| moduleNotFound = true; | |||
| } | |||
| updateCppStandardWarning (cppStandard); | |||
| updateMissingModuleDependenciesWarning (missingDependencies); | |||
| updateOldProjucerWarning (oldProjucer); | |||
| updateModuleNotFoundWarning (moduleNotFound); | |||
| } | |||
| void Project::updateCppStandardWarning (bool showWarning) | |||
| { | |||
| if (showWarning) | |||
| { | |||
| auto removeModules = [this] | |||
| { | |||
| auto& modules = getEnabledModules(); | |||
| for (auto& module : modules.getModulesWithHigherCppStandardThanProject()) | |||
| modules.removeModule (module); | |||
| }; | |||
| auto updateCppStandard = [this] | |||
| { | |||
| cppStandardValue = getEnabledModules().getHighestModuleCppStandard(); | |||
| }; | |||
| addProjectMessage (ProjectMessages::Ids::cppStandard, | |||
| { { "Update project C++ standard" , std::move (updateCppStandard) }, | |||
| { "Remove module(s)", std::move (removeModules) } }); | |||
| } | |||
| else | |||
| { | |||
| removeProjectMessage (ProjectMessages::Ids::cppStandard); | |||
| } | |||
| } | |||
| void Project::updateMissingModuleDependenciesWarning (bool showWarning) | |||
| { | |||
| if (showWarning) | |||
| { | |||
| auto removeModules = [this] | |||
| { | |||
| auto& modules = getEnabledModules(); | |||
| for (auto& mod : modules.getModulesWithMissingDependencies()) | |||
| modules.removeModule (mod); | |||
| }; | |||
| auto addMissingDependencies = [this] | |||
| { | |||
| auto& modules = getEnabledModules(); | |||
| for (auto& mod : modules.getModulesWithMissingDependencies()) | |||
| modules.tryToFixMissingDependencies (mod); | |||
| }; | |||
| addProjectMessage (ProjectMessages::Ids::missingModuleDependencies, | |||
| { { "Add missing dependencies", std::move (addMissingDependencies) }, | |||
| { "Remove module(s)", std::move (removeModules) } }); | |||
| } | |||
| else | |||
| { | |||
| removeProjectMessage (ProjectMessages::Ids::missingModuleDependencies); | |||
| } | |||
| } | |||
| void Project::updateOldProjucerWarning (bool showWarning) | |||
| { | |||
| if (showWarning) | |||
| addProjectMessage (ProjectMessages::Ids::oldProjucer, {}); | |||
| else | |||
| removeProjectMessage (ProjectMessages::Ids::oldProjucer); | |||
| } | |||
| void Project::updateModuleNotFoundWarning (bool showWarning) | |||
| { | |||
| if (showWarning) | |||
| addProjectMessage (ProjectMessages::Ids::moduleNotFound, {}); | |||
| else | |||
| removeProjectMessage (ProjectMessages::Ids::moduleNotFound); | |||
| } | |||
| void Project::licenseStateChanged() | |||
| { | |||
| updateLicenseWarning(); | |||
| } | |||
| void Project::changeListenerCallback (ChangeBroadcaster*) | |||
| { | |||
| updateJUCEPathWarning(); | |||
| } | |||
| void Project::availableModulesChanged (AvailableModulesList* listThatHasChanged) | |||
| { | |||
| if (listThatHasChanged == &ProjucerApplication::getApp().getJUCEPathModulesList()) | |||
| updateJUCEPathWarning(); | |||
| updateModuleWarnings(); | |||
| } | |||
| void Project::addProjectMessage (const Identifier& messageToAdd, | |||
| std::vector<ProjectMessages::MessageAction>&& actions) | |||
| { | |||
| removeProjectMessage (messageToAdd); | |||
| messageActions[messageToAdd] = std::move (actions); | |||
| ValueTree child (messageToAdd); | |||
| child.setProperty (ProjectMessages::Ids::isVisible, true, nullptr); | |||
| projectMessages.getChildWithName (ProjectMessages::getTypeForMessage (messageToAdd)).addChild (child, -1, nullptr); | |||
| } | |||
| void Project::removeProjectMessage (const Identifier& messageToRemove) | |||
| { | |||
| auto subTree = projectMessages.getChildWithName (ProjectMessages::getTypeForMessage (messageToRemove)); | |||
| auto child = subTree.getChildWithName (messageToRemove); | |||
| if (child.isValid()) | |||
| subTree.removeChild (child, nullptr); | |||
| messageActions.erase (messageToRemove); | |||
| } | |||
| std::vector<ProjectMessages::MessageAction> Project::getMessageActions (const Identifier& message) | |||
| { | |||
| auto iter = messageActions.find (message); | |||
| if (iter != messageActions.end()) | |||
| return iter->second; | |||
| jassertfalse; | |||
| return {}; | |||
| } | |||
| //============================================================================== | |||
| void Project::setTemporaryDirectory (const File& dir) noexcept | |||
| { | |||
| @@ -645,17 +939,16 @@ void Project::setTemporaryDirectory (const File& dir) noexcept | |||
| forgetRecentFile (getFile()); | |||
| } | |||
| void Project::askUserWhereToSaveProject() | |||
| void Project::saveAndMoveTemporaryProject (bool openInIDE) | |||
| { | |||
| FileChooser fc ("Save Project"); | |||
| fc.browseForDirectory(); | |||
| if (fc.getResult().exists()) | |||
| moveTemporaryDirectory (fc.getResult()); | |||
| } | |||
| auto newParentDirectory = fc.getResult(); | |||
| if (! newParentDirectory.exists()) | |||
| return; | |||
| void Project::moveTemporaryDirectory (const File& newParentDirectory) | |||
| { | |||
| auto newDirectory = newParentDirectory.getChildFile (tempDirectory.getFileName()); | |||
| auto oldJucerFileName = getFile().getFileName(); | |||
| @@ -670,10 +963,12 @@ void Project::moveTemporaryDirectory (const File& newParentDirectory) | |||
| { | |||
| Component::SafePointer<MainWindow> safeWindow (window); | |||
| MessageManager::callAsync ([safeWindow, newDirectory, oldJucerFileName] | |||
| MessageManager::callAsync ([safeWindow, newDirectory, oldJucerFileName, openInIDE]() mutable | |||
| { | |||
| if (safeWindow != nullptr) | |||
| safeWindow.getComponent()->moveProject (newDirectory.getChildFile (oldJucerFileName)); | |||
| safeWindow->moveProject (newDirectory.getChildFile (oldJucerFileName), | |||
| openInIDE ? MainWindow::OpenInIDE::yes | |||
| : MainWindow::OpenInIDE::no); | |||
| }); | |||
| } | |||
| } | |||
| @@ -724,14 +1019,43 @@ void Project::valueTreePropertyChanged (ValueTree& tree, const Identifier& prope | |||
| if (shouldWriteLegacyPluginCharacteristicsSettings) | |||
| writeLegacyPluginCharacteristicsSettings(); | |||
| } | |||
| else if (property == Ids::displaySplashScreen) | |||
| { | |||
| updateLicenseWarning(); | |||
| } | |||
| else if (property == Ids::cppLanguageStandard) | |||
| { | |||
| updateModuleWarnings(); | |||
| } | |||
| changed(); | |||
| } | |||
| } | |||
| void Project::valueTreeChildAdded (ValueTree&, ValueTree&) { changed(); } | |||
| void Project::valueTreeChildRemoved (ValueTree&, ValueTree&, int) { changed(); } | |||
| void Project::valueTreeChildOrderChanged (ValueTree&, int, int) { changed(); } | |||
| void Project::valueTreeChildAdded (ValueTree& parent, ValueTree& child) | |||
| { | |||
| ignoreUnused (parent); | |||
| if (child.getType() == Ids::MODULE) | |||
| updateModuleWarnings(); | |||
| changed(); | |||
| } | |||
| void Project::valueTreeChildRemoved (ValueTree& parent, ValueTree& child, int index) | |||
| { | |||
| ignoreUnused (parent, index); | |||
| if (child.getType() == Ids::MODULE) | |||
| updateModuleWarnings(); | |||
| changed(); | |||
| } | |||
| void Project::valueTreeChildOrderChanged (ValueTree&, int, int) | |||
| { | |||
| changed(); | |||
| } | |||
| //============================================================================== | |||
| bool Project::hasProjectBeenModified() | |||
| @@ -888,24 +1212,17 @@ void Project::createPropertyEditors (PropertyListBuilder& props) | |||
| "no such statement will be included. This setting used to be enabled by default, but it " | |||
| "is recommended to leave it disabled for new projects."); | |||
| { | |||
| String licenseRequiredTagline ("Required for closed source applications without an Indie or Pro JUCE license"); | |||
| String licenseRequiredInfo ("In accordance with the terms of the JUCE 5 End-Use License Agreement (www.juce.com/juce-5-licence), " | |||
| "this option can only be disabled for closed source applications if you have a JUCE Indie or Pro " | |||
| "license, or are using JUCE under the GPL v3 license."); | |||
| StringPairArray description; | |||
| description.set ("Display the JUCE splash screen", "This option controls the display of the standard JUCE splash screen."); | |||
| props.add (new ChoicePropertyComponent (displaySplashScreenValue, "Display the JUCE Splash Screen (required for closed source applications without an Indie or Pro JUCE license)"), | |||
| "This option controls the display of the standard JUCE splash screen. " | |||
| "In accordance with the terms of the JUCE 5 End-Use License Agreement (www.juce.com/juce-5-licence), " | |||
| "this option can only be disabled for closed source applications if you have a JUCE Indie or Pro " | |||
| "license, or are using JUCE under the GPL v3 license."); | |||
| props.add (new ChoicePropertyComponent (displaySplashScreenValue, String ("Display the JUCE Splash Screen") + " (" + licenseRequiredTagline + ")"), | |||
| description["Display the JUCE splash screen"] + " " + licenseRequiredInfo); | |||
| } | |||
| props.add (new ChoicePropertyComponent (splashScreenColourValue, "Splash Screen Colour", | |||
| { "Dark", "Light" }, | |||
| { "Dark", "Light" }), | |||
| props.add (new ChoicePropertyComponentWithEnablement (splashScreenColourValue, displaySplashScreenValue, "Splash Screen Colour", | |||
| { "Dark", "Light" }, { "Dark", "Light" }), | |||
| "Choose the colour of the JUCE splash screen."); | |||
| { | |||
| StringArray projectTypeNames; | |||
| Array<var> projectTypeCodes; | |||
| @@ -954,8 +1271,8 @@ void Project::createPropertyEditors (PropertyListBuilder& props) | |||
| "The namespace containing the binary assets."); | |||
| props.add (new ChoicePropertyComponent (cppStandardValue, "C++ Language Standard", | |||
| { "C++11", "C++14", "C++17", "Use Latest" }, | |||
| { "11", "14", "17", "latest" }), | |||
| getCppStandardStrings(), | |||
| getCppStandardVars()), | |||
| "The standard of the C++ language that will be used for compilation."); | |||
| props.add (new TextPropertyComponent (preprocessorDefsValue, "Preprocessor Definitions", 32768, true), | |||
| @@ -1912,12 +2229,12 @@ Array<var> Project::getDefaultRTASCategories() const noexcept | |||
| } | |||
| //============================================================================== | |||
| EnabledModuleList& Project::getEnabledModules() | |||
| EnabledModulesList& Project::getEnabledModules() | |||
| { | |||
| if (enabledModuleList == nullptr) | |||
| enabledModuleList.reset (new EnabledModuleList (*this, projectRoot.getOrCreateChildWithName (Ids::MODULES, nullptr))); | |||
| if (enabledModulesList == nullptr) | |||
| enabledModulesList.reset (new EnabledModulesList (*this, projectRoot.getOrCreateChildWithName (Ids::MODULES, nullptr))); | |||
| return *enabledModuleList; | |||
| return *enabledModulesList; | |||
| } | |||
| static StringArray getModulePathsFromExporters (Project& project, bool onlyThisOS) | |||
| @@ -1979,37 +2296,38 @@ static Array<File> getExporterModulePathsToScan (Project& project) | |||
| return files; | |||
| } | |||
| AvailableModuleList& Project::getExporterPathsModuleList() | |||
| AvailableModulesList& Project::getExporterPathsModulesList() | |||
| { | |||
| return *exporterPathsModuleList; | |||
| jassert (exporterPathsModulesList != nullptr); | |||
| return *exporterPathsModulesList; | |||
| } | |||
| void Project::rescanExporterPathModules (bool async) | |||
| { | |||
| if (async) | |||
| exporterPathsModuleList->scanPathsAsync (getExporterModulePathsToScan (*this)); | |||
| exporterPathsModulesList->scanPathsAsync (getExporterModulePathsToScan (*this)); | |||
| else | |||
| exporterPathsModuleList->scanPaths (getExporterModulePathsToScan (*this)); | |||
| exporterPathsModulesList->scanPaths (getExporterModulePathsToScan (*this)); | |||
| } | |||
| AvailableModuleList::ModuleIDAndFolder Project::getModuleWithID (const String& id) | |||
| AvailableModulesList::ModuleIDAndFolder Project::getModuleWithID (const String& id) | |||
| { | |||
| if (! getEnabledModules().shouldUseGlobalPath (id)) | |||
| { | |||
| const auto& mod = exporterPathsModuleList->getModuleWithID (id); | |||
| const auto& mod = exporterPathsModulesList->getModuleWithID (id); | |||
| if (mod.second != File()) | |||
| return mod; | |||
| } | |||
| const auto& list = (isJUCEModule (id) ? ProjucerApplication::getApp().getJUCEPathModuleList().getAllModules() | |||
| : ProjucerApplication::getApp().getUserPathsModuleList().getAllModules()); | |||
| const auto& list = (isJUCEModule (id) ? ProjucerApplication::getApp().getJUCEPathModulesList().getAllModules() | |||
| : ProjucerApplication::getApp().getUserPathsModulesList().getAllModules()); | |||
| for (auto& m : list) | |||
| if (m.first == id) | |||
| return m; | |||
| return exporterPathsModuleList->getModuleWithID (id); | |||
| return exporterPathsModulesList->getModuleWithID (id); | |||
| } | |||
| //============================================================================== | |||
| @@ -18,16 +18,96 @@ | |||
| #pragma once | |||
| #include "../Application/UserAccount/jucer_LicenseController.h" | |||
| #include "Modules/jucer_AvailableModulesList.h" | |||
| class ProjectExporter; | |||
| class LibraryModule; | |||
| class EnabledModuleList; | |||
| class AvailableModuleList; | |||
| class ProjectContentComponent; | |||
| class EnabledModulesList; | |||
| class CompileEngineSettings; | |||
| namespace ProjectMessages | |||
| { | |||
| namespace Ids | |||
| { | |||
| #define DECLARE_ID(name) static const Identifier name (#name) | |||
| DECLARE_ID (projectMessages); | |||
| DECLARE_ID (incompatibleLicense); | |||
| DECLARE_ID (cppStandard); | |||
| DECLARE_ID (moduleNotFound); | |||
| DECLARE_ID (jucePath); | |||
| DECLARE_ID (jucerFileModified); | |||
| DECLARE_ID (missingModuleDependencies); | |||
| DECLARE_ID (oldProjucer); | |||
| DECLARE_ID (newVersionAvailable); | |||
| DECLARE_ID (notification); | |||
| DECLARE_ID (warning); | |||
| DECLARE_ID (isVisible); | |||
| #undef DECLARE_ID | |||
| } | |||
| inline Identifier getTypeForMessage (const Identifier& message) | |||
| { | |||
| if (message == Ids::incompatibleLicense || message == Ids::cppStandard || message == Ids::moduleNotFound | |||
| || message == Ids::jucePath || message == Ids::jucerFileModified || message == Ids::missingModuleDependencies | |||
| || message == Ids::oldProjucer) | |||
| { | |||
| return Ids::warning; | |||
| } | |||
| if (message == Ids::newVersionAvailable) | |||
| { | |||
| return Ids::notification; | |||
| } | |||
| jassertfalse; | |||
| return {}; | |||
| } | |||
| inline String getTitleForMessage (const Identifier& message) | |||
| { | |||
| if (message == Ids::incompatibleLicense) return "Incompatible License and Splash Screen Setting"; | |||
| if (message == Ids::cppStandard) return "C++ Standard"; | |||
| if (message == Ids::moduleNotFound) return "Module Not Found"; | |||
| if (message == Ids::jucePath) return "JUCE Path"; | |||
| if (message == Ids::jucerFileModified) return "Project File Modified"; | |||
| if (message == Ids::missingModuleDependencies) return "Missing Module Dependencies"; | |||
| if (message == Ids::oldProjucer) return "Projucer Out of Date"; | |||
| if (message == Ids::newVersionAvailable) return "New Version Available"; | |||
| jassertfalse; | |||
| return {}; | |||
| } | |||
| inline String getDescriptionForMessage (const Identifier& message) | |||
| { | |||
| if (message == Ids::incompatibleLicense) return "Save and export is disabled."; | |||
| if (message == Ids::cppStandard) return "Module(s) have a higher C++ standard requirement than the project."; | |||
| if (message == Ids::moduleNotFound) return "Module(s) could not be found at the specified paths."; | |||
| if (message == Ids::jucePath) return "The path to your JUCE folder is incorrect."; | |||
| if (message == Ids::jucerFileModified) return "The .jucer file has been modified since the last save."; | |||
| if (message == Ids::missingModuleDependencies) return "Module(s) have missing dependencies."; | |||
| if (message == Ids::oldProjucer) return "The version of the Projucer you are using is out of date."; | |||
| if (message == Ids::newVersionAvailable) return "A new version of JUCE is available to download."; | |||
| jassertfalse; | |||
| return {}; | |||
| } | |||
| using MessageAction = std::pair<String, std::function<void()>>; | |||
| } | |||
| //============================================================================== | |||
| class Project : public FileBasedDocument, | |||
| public ValueTree::Listener | |||
| public ValueTree::Listener, | |||
| private LicenseController::LicenseStateListener, | |||
| private ChangeListener, | |||
| private AvailableModulesList::Listener | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| @@ -35,12 +115,14 @@ public: | |||
| ~Project() override; | |||
| //============================================================================== | |||
| // FileBasedDocument stuff.. | |||
| String getDocumentTitle() override; | |||
| Result loadDocument (const File& file) override; | |||
| Result saveDocument (const File& file) override; | |||
| Result saveProject (const File& file, bool isCommandLineApp); | |||
| Result saveResourcesOnly (const File& file); | |||
| Result saveProject (ProjectExporter* exporterToSave = nullptr); | |||
| Result saveResourcesOnly(); | |||
| Result openProjectInIDE (ProjectExporter& exporterToOpen, bool saveFirst); | |||
| File getLastDocumentOpened() override; | |||
| void setLastDocumentOpened (const File& file) override; | |||
| @@ -116,6 +198,9 @@ public: | |||
| bool shouldDisplaySplashScreen() const { return displaySplashScreenValue.get(); } | |||
| String getSplashScreenColourString() const { return splashScreenColourValue.get(); } | |||
| static StringArray getCppStandardStrings() { return { "C++11", "C++14", "C++17", "Use Latest" }; } | |||
| static Array<var> getCppStandardVars() { return { "11", "14", "17", "latest" }; } | |||
| String getCppStandardString() const { return cppStandardValue.get(); } | |||
| StringArray getCompilerFlagSchemes() const; | |||
| @@ -363,9 +448,9 @@ public: | |||
| bool isConfigFlagEnabled (const String& name, bool defaultIsEnabled = false) const; | |||
| //============================================================================== | |||
| EnabledModuleList& getEnabledModules(); | |||
| EnabledModulesList& getEnabledModules(); | |||
| AvailableModuleList& getExporterPathsModuleList(); | |||
| AvailableModulesList& getExporterPathsModulesList(); | |||
| void rescanExporterPathModules (bool async = false); | |||
| std::pair<String, File> getModuleWithID (const String&); | |||
| @@ -397,22 +482,45 @@ public: | |||
| String getUniqueTargetFolderSuffixForExporter (const String& exporterName, const String& baseTargetFolder); | |||
| //============================================================================== | |||
| bool isCurrentlySaving() const noexcept { return isSaving; } | |||
| bool shouldWaitAfterSaving = false; | |||
| String specifiedExporterToSave = {}; | |||
| bool isCurrentlySaving() const noexcept { return isSaving; } | |||
| //============================================================================== | |||
| bool isTemporaryProject() const noexcept { return tempDirectory != File(); } | |||
| File getTemporaryDirectory() const noexcept { return tempDirectory; } | |||
| void setTemporaryDirectory (const File&) noexcept; | |||
| void setOpenInIDEAfterSaving (bool open) noexcept { openInIDEAfterSaving = open; } | |||
| bool shouldOpenInIDEAfterSaving() const noexcept { return openInIDEAfterSaving; } | |||
| //============================================================================== | |||
| CompileEngineSettings& getCompileEngineSettings() { return *compileEngineSettings; } | |||
| //============================================================================== | |||
| ValueTree getProjectMessages() const { return projectMessages; } | |||
| void addProjectMessage (const Identifier& messageToAdd, std::vector<ProjectMessages::MessageAction>&& messageActions); | |||
| void removeProjectMessage (const Identifier& messageToRemove); | |||
| std::vector<ProjectMessages::MessageAction> getMessageActions (const Identifier& message); | |||
| //============================================================================== | |||
| bool hasIncompatibleLicenseTypeAndSplashScreenSetting() const; | |||
| bool isSaveAndExportDisabled() const; | |||
| private: | |||
| //============================================================================== | |||
| struct ProjectFileModificationPoller : private Timer | |||
| { | |||
| ProjectFileModificationPoller (Project& p); | |||
| private: | |||
| void timerCallback() override; | |||
| void reset(); | |||
| void keepProject(); | |||
| void reloadProjectFromDisk(); | |||
| Project& project; | |||
| bool showingWarning = false; | |||
| }; | |||
| //============================================================================== | |||
| ValueTree projectRoot { Ids::JUCERPROJECT }; | |||
| ValueWithDefault projectNameValue, projectUIDValue, projectLineFeedValue, projectTypeValue, versionValue, bundleIdentifierValue, companyNameValue, | |||
| @@ -427,8 +535,8 @@ private: | |||
| //============================================================================== | |||
| std::unique_ptr<CompileEngineSettings> compileEngineSettings; | |||
| std::unique_ptr<EnabledModuleList> enabledModuleList; | |||
| std::unique_ptr<AvailableModuleList> exporterPathsModuleList; | |||
| std::unique_ptr<EnabledModulesList> enabledModulesList; | |||
| std::unique_ptr<AvailableModulesList> exporterPathsModulesList; | |||
| //============================================================================== | |||
| void updateDeprecatedProjectSettings(); | |||
| @@ -449,10 +557,8 @@ private: | |||
| //============================================================================== | |||
| File tempDirectory = {}; | |||
| bool openInIDEAfterSaving = false; | |||
| void askUserWhereToSaveProject(); | |||
| void moveTemporaryDirectory (const File&); | |||
| void saveAndMoveTemporaryProject (bool openInIDE); | |||
| bool saveProjectRootToFile(); | |||
| //============================================================================== | |||
| @@ -481,7 +587,27 @@ private: | |||
| void moveOldPropertyFromProjectToAllExporters (Identifier name); | |||
| void removeDefunctExporters(); | |||
| void updateOldModulePaths(); | |||
| void warnAboutOldProjucerVersion(); | |||
| //============================================================================== | |||
| void licenseStateChanged() override; | |||
| void changeListenerCallback (ChangeBroadcaster*) override; | |||
| void availableModulesChanged (AvailableModulesList*) override; | |||
| void updateLicenseWarning(); | |||
| void updateJUCEPathWarning(); | |||
| void updateModuleWarnings(); | |||
| void updateCppStandardWarning (bool showWarning); | |||
| void updateMissingModuleDependenciesWarning (bool showWarning); | |||
| void updateOldProjucerWarning (bool showWarning); | |||
| void updateModuleNotFoundWarning (bool showWarning); | |||
| ValueTree projectMessages { ProjectMessages::Ids::projectMessages, {}, | |||
| { { ProjectMessages::Ids::notification, {} }, { ProjectMessages::Ids::warning, {} } } }; | |||
| std::map<Identifier, std::vector<ProjectMessages::MessageAction>> messageActions; | |||
| ProjectFileModificationPoller fileModificationPoller { *this }; | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Project) | |||
| }; | |||
| @@ -16,12 +16,655 @@ | |||
| ============================================================================== | |||
| */ | |||
| #include "../Application/jucer_Headers.h" | |||
| #include "jucer_ProjectSaver.h" | |||
| #include "jucer_ProjectExport_CLion.h" | |||
| #include "../Application/jucer_Application.h" | |||
| static constexpr const char* generatedGroupID = "__jucelibfiles"; | |||
| static constexpr const char* generatedGroupUID = "__generatedcode__"; | |||
| //============================================================================== | |||
| ProjectSaver::ProjectSaver (Project& p) | |||
| : project (p), | |||
| generatedCodeFolder (project.getGeneratedCodeFolder()), | |||
| generatedFilesGroup (Project::Item::createGroup (project, getJuceCodeGroupName(), generatedGroupUID, true)), | |||
| projectLineFeed (project.getProjectLineFeed()) | |||
| { | |||
| generatedFilesGroup.setID (generatedGroupID); | |||
| } | |||
| Result ProjectSaver::save (ProjectExporter* exporterToSave) | |||
| { | |||
| if (! ProjucerApplication::getApp().isRunningCommandLine) | |||
| { | |||
| SaveThreadWithProgressWindow thread (*this, exporterToSave); | |||
| thread.runThread(); | |||
| return thread.result; | |||
| } | |||
| return saveProject (exporterToSave); | |||
| } | |||
| Result ProjectSaver::saveResourcesOnly() | |||
| { | |||
| writeBinaryDataFiles(); | |||
| if (errors.size() > 0) | |||
| return Result::fail (errors[0]); | |||
| return Result::ok(); | |||
| } | |||
| void ProjectSaver::saveBasicProjectItems (const OwnedArray<LibraryModule>& modules, const String& appConfigUserContent) | |||
| { | |||
| writePluginDefines(); | |||
| writeAppConfigFile (modules, appConfigUserContent); | |||
| writeBinaryDataFiles(); | |||
| writeAppHeader (modules); | |||
| writeModuleCppWrappers (modules); | |||
| } | |||
| Result ProjectSaver::saveContentNeededForLiveBuild() | |||
| { | |||
| auto modules = getModules(); | |||
| if (errors.size() == 0) | |||
| { | |||
| saveBasicProjectItems (modules, loadUserContentFromAppConfig()); | |||
| return Result::ok(); | |||
| } | |||
| return Result::fail (errors[0]); | |||
| } | |||
| Project::Item ProjectSaver::addFileToGeneratedGroup (const File& file) | |||
| { | |||
| auto item = generatedFilesGroup.findItemForFile (file); | |||
| if (item.isValid()) | |||
| return item; | |||
| generatedFilesGroup.addFileAtIndex (file, -1, true); | |||
| return generatedFilesGroup.findItemForFile (file); | |||
| } | |||
| bool ProjectSaver::copyFolder (const File& source, const File& dest) | |||
| { | |||
| if (source.isDirectory() && dest.createDirectory()) | |||
| { | |||
| for (auto& f : source.findChildFiles (File::findFiles, false)) | |||
| { | |||
| auto target = dest.getChildFile (f.getFileName()); | |||
| filesCreated.add (target); | |||
| if (! f.copyFileTo (target)) | |||
| return false; | |||
| } | |||
| for (auto& f : source.findChildFiles (File::findDirectories, false)) | |||
| { | |||
| auto name = f.getFileName(); | |||
| if (name == ".git" || name == ".svn" || name == ".cvs") | |||
| continue; | |||
| if (! copyFolder (f, dest.getChildFile (f.getFileName()))) | |||
| return false; | |||
| } | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| //============================================================================== | |||
| Project::Item ProjectSaver::saveGeneratedFile (const String& filePath, const MemoryOutputStream& newData) | |||
| { | |||
| if (! generatedCodeFolder.createDirectory()) | |||
| { | |||
| addError ("Couldn't create folder: " + generatedCodeFolder.getFullPathName()); | |||
| return Project::Item (project, {}, false); | |||
| } | |||
| auto file = generatedCodeFolder.getChildFile (filePath); | |||
| if (replaceFileIfDifferent (file, newData)) | |||
| return addFileToGeneratedGroup (file); | |||
| return { project, {}, true }; | |||
| } | |||
| bool ProjectSaver::replaceFileIfDifferent (const File& f, const MemoryOutputStream& newData) | |||
| { | |||
| filesCreated.add (f); | |||
| if (! build_tools::overwriteFileWithNewDataIfDifferent (f, newData)) | |||
| { | |||
| addError ("Can't write to file: " + f.getFullPathName()); | |||
| return false; | |||
| } | |||
| return true; | |||
| } | |||
| bool ProjectSaver::deleteUnwantedFilesIn (const File& parent) | |||
| { | |||
| // Recursively clears out any files in a folder that we didn't create, but avoids | |||
| // any folders containing hidden files that might be used by version-control systems. | |||
| auto shouldFileBeKept = [] (const String& filename) | |||
| { | |||
| static StringArray filesToKeep (".svn", ".cvs", "CMakeLists.txt"); | |||
| return filesToKeep.contains (filename); | |||
| }; | |||
| bool folderIsNowEmpty = true; | |||
| Array<File> filesToDelete; | |||
| for (const auto& i : RangedDirectoryIterator (parent, false, "*", File::findFilesAndDirectories)) | |||
| { | |||
| auto f = i.getFile(); | |||
| if (filesCreated.contains (f) || shouldFileBeKept (f.getFileName())) | |||
| { | |||
| folderIsNowEmpty = false; | |||
| } | |||
| else if (i.isDirectory()) | |||
| { | |||
| if (deleteUnwantedFilesIn (f)) | |||
| filesToDelete.add (f); | |||
| else | |||
| folderIsNowEmpty = false; | |||
| } | |||
| else | |||
| { | |||
| filesToDelete.add (f); | |||
| } | |||
| } | |||
| for (int j = filesToDelete.size(); --j >= 0;) | |||
| filesToDelete.getReference (j).deleteRecursively(); | |||
| return folderIsNowEmpty; | |||
| } | |||
| //============================================================================== | |||
| void ProjectSaver::addError (const String& message) | |||
| { | |||
| const ScopedLock sl (errorLock); | |||
| errors.add (message); | |||
| } | |||
| //============================================================================== | |||
| File ProjectSaver::getAppConfigFile() const | |||
| { | |||
| return generatedCodeFolder.getChildFile (Project::getAppConfigFilename()); | |||
| } | |||
| File ProjectSaver::getPluginDefinesFile() const | |||
| { | |||
| return generatedCodeFolder.getChildFile (Project::getPluginDefinesFilename()); | |||
| } | |||
| String ProjectSaver::loadUserContentFromAppConfig() const | |||
| { | |||
| StringArray userContent; | |||
| bool foundCodeSection = false; | |||
| auto lines = StringArray::fromLines (getAppConfigFile().loadFileAsString()); | |||
| for (int i = 0; i < lines.size(); ++i) | |||
| { | |||
| if (lines[i].contains ("[BEGIN_USER_CODE_SECTION]")) | |||
| { | |||
| for (int j = i + 1; j < lines.size() && ! lines[j].contains ("[END_USER_CODE_SECTION]"); ++j) | |||
| userContent.add (lines[j]); | |||
| foundCodeSection = true; | |||
| break; | |||
| } | |||
| } | |||
| if (! foundCodeSection) | |||
| { | |||
| userContent.add ({}); | |||
| userContent.add ("// (You can add your own code in this section, and the Projucer will not overwrite it)"); | |||
| userContent.add ({}); | |||
| } | |||
| return userContent.joinIntoString (projectLineFeed) + projectLineFeed; | |||
| } | |||
| //============================================================================== | |||
| OwnedArray<LibraryModule> ProjectSaver::getModules() | |||
| { | |||
| OwnedArray<LibraryModule> modules; | |||
| project.getEnabledModules().createRequiredModules (modules); | |||
| for (auto* module : modules) | |||
| { | |||
| if (! module->isValid()) | |||
| { | |||
| addError ("At least one of your JUCE module paths is invalid!\n" | |||
| "Please go to the Modules settings page and ensure each path points to the correct JUCE modules folder."); | |||
| return {}; | |||
| } | |||
| if (project.getEnabledModules().getExtraDependenciesNeeded (module->getID()).size() > 0) | |||
| { | |||
| addError ("At least one of your modules has missing dependencies!\n" | |||
| "Please go to the settings page of the highlighted modules and add the required dependencies."); | |||
| return {}; | |||
| } | |||
| } | |||
| return modules; | |||
| } | |||
| //============================================================================== | |||
| Result ProjectSaver::saveProject (ProjectExporter* specifiedExporterToSave) | |||
| { | |||
| if (project.getNumExporters() == 0) | |||
| { | |||
| return Result::fail ("No exporters found!\n" | |||
| "Please add an exporter before saving."); | |||
| } | |||
| auto oldProjectFile = project.getFile(); | |||
| auto modules = getModules(); | |||
| if (errors.size() == 0) | |||
| { | |||
| writeMainProjectFile(); | |||
| project.updateModificationTime(); | |||
| auto projectRootHash = project.getProjectRoot().toXmlString().hashCode(); | |||
| if (project.isAudioPluginProject()) | |||
| { | |||
| if (project.shouldBuildUnityPlugin()) | |||
| writeUnityScriptFile(); | |||
| } | |||
| saveBasicProjectItems (modules, loadUserContentFromAppConfig()); | |||
| writeProjects (modules, specifiedExporterToSave); | |||
| runPostExportScript(); | |||
| // if the project root has changed after writing the other files then re-save it | |||
| if (project.getProjectRoot().toXmlString().hashCode() != projectRootHash) | |||
| { | |||
| writeMainProjectFile(); | |||
| project.updateModificationTime(); | |||
| } | |||
| if (generatedCodeFolder.exists()) | |||
| { | |||
| writeReadmeFile(); | |||
| deleteUnwantedFilesIn (generatedCodeFolder); | |||
| } | |||
| if (errors.size() == 0) | |||
| return Result::ok(); | |||
| } | |||
| project.setFile (oldProjectFile); | |||
| return Result::fail (errors[0]); | |||
| } | |||
| //============================================================================== | |||
| void ProjectSaver::writeMainProjectFile() | |||
| { | |||
| if (auto xml = project.getProjectRoot().createXml()) | |||
| { | |||
| XmlElement::TextFormat format; | |||
| format.newLineChars = projectLineFeed.toRawUTF8(); | |||
| MemoryOutputStream mo (8192); | |||
| xml->writeTo (mo, format); | |||
| replaceFileIfDifferent (project.getFile(), mo); | |||
| } | |||
| else | |||
| { | |||
| addError ("Failed to write main project file: " + project.getFile().getFullPathName()); | |||
| } | |||
| } | |||
| static void writeAutoGenWarningComment (OutputStream& out) | |||
| { | |||
| out << "/*" << newLine << newLine | |||
| << " IMPORTANT! This file is auto-generated each time you save your" << newLine | |||
| << " project - if you alter its contents, your changes may be overwritten!" << newLine | |||
| << newLine; | |||
| } | |||
| void ProjectSaver::writePluginDefines (MemoryOutputStream& out) const | |||
| { | |||
| const auto pluginDefines = getAudioPluginDefines(); | |||
| if (pluginDefines.isEmpty()) | |||
| return; | |||
| writeAutoGenWarningComment (out); | |||
| out << "*/" << newLine << newLine | |||
| << "#pragma once" << newLine << newLine | |||
| << pluginDefines << newLine; | |||
| } | |||
| void ProjectSaver::writeAppConfig (MemoryOutputStream& out, const OwnedArray<LibraryModule>& modules, const String& userContent) | |||
| { | |||
| if (! project.shouldUseAppConfig()) | |||
| return; | |||
| writeAutoGenWarningComment (out); | |||
| out << " There's a section below where you can add your own custom code safely, and the" << newLine | |||
| << " Projucer will preserve the contents of that block, but the best way to change" << newLine | |||
| << " any of these definitions is by using the Projucer's project settings." << newLine | |||
| << newLine | |||
| << " Any commented-out settings will assume their default values." << newLine | |||
| << newLine | |||
| << "*/" << newLine | |||
| << newLine; | |||
| out << "#pragma once" << newLine | |||
| << newLine | |||
| << "//==============================================================================" << newLine | |||
| << "// [BEGIN_USER_CODE_SECTION]" << newLine | |||
| << userContent | |||
| << "// [END_USER_CODE_SECTION]" << newLine; | |||
| if (getPluginDefinesFile().existsAsFile() && getAudioPluginDefines().isNotEmpty()) | |||
| out << newLine << CodeHelpers::createIncludeStatement (Project::getPluginDefinesFilename()) << newLine; | |||
| out << newLine | |||
| << "/*" << newLine | |||
| << " ==============================================================================" << newLine | |||
| << newLine | |||
| << " In accordance with the terms of the JUCE 5 End-Use License Agreement, the" << newLine | |||
| << " JUCE Code in SECTION A cannot be removed, changed or otherwise rendered" << newLine | |||
| << " ineffective unless you have a JUCE Indie or Pro license, or are using JUCE" << newLine | |||
| << " under the GPL v3 license." << newLine | |||
| << newLine | |||
| << " End User License Agreement: www.juce.com/juce-5-licence" << newLine | |||
| << newLine | |||
| << " ==============================================================================" << newLine | |||
| << "*/" << newLine | |||
| << newLine | |||
| << "// BEGIN SECTION A" << newLine | |||
| << newLine | |||
| << "#ifndef JUCE_DISPLAY_SPLASH_SCREEN" << newLine | |||
| << " #define JUCE_DISPLAY_SPLASH_SCREEN " << (project.shouldDisplaySplashScreen() ? "1" : "0") << newLine | |||
| << "#endif" << newLine << newLine | |||
| << "// END SECTION A" << newLine | |||
| << newLine | |||
| << "#define JUCE_USE_DARK_SPLASH_SCREEN " << (project.getSplashScreenColourString() == "Dark" ? "1" : "0") << newLine | |||
| << newLine | |||
| << "#define JUCE_PROJUCER_VERSION 0x" << String::toHexString (ProjectInfo::versionNumber) << newLine; | |||
| out << newLine | |||
| << "//==============================================================================" << newLine; | |||
| auto longestModuleName = [&modules]() | |||
| { | |||
| int longest = 0; | |||
| for (auto* module : modules) | |||
| longest = jmax (longest, module->getID().length()); | |||
| return longest; | |||
| }(); | |||
| for (auto* module : modules) | |||
| { | |||
| out << "#define JUCE_MODULE_AVAILABLE_" << module->getID() | |||
| << String::repeatedString (" ", longestModuleName + 5 - module->getID().length()) << " 1" << newLine; | |||
| } | |||
| out << newLine << "#define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1" << newLine; | |||
| for (auto* module : modules) | |||
| { | |||
| OwnedArray<Project::ConfigFlag> flags; | |||
| module->getConfigFlags (project, flags); | |||
| if (flags.size() > 0) | |||
| { | |||
| out << newLine | |||
| << "//==============================================================================" << newLine | |||
| << "// " << module->getID() << " flags:" << newLine; | |||
| for (auto* flag : flags) | |||
| { | |||
| out << newLine | |||
| << "#ifndef " << flag->symbol | |||
| << newLine | |||
| << (flag->value.isUsingDefault() ? " //#define " : " #define ") << flag->symbol << " " << (flag->value.get() ? "1" : "0") | |||
| << newLine | |||
| << "#endif" | |||
| << newLine; | |||
| } | |||
| } | |||
| } | |||
| auto& type = project.getProjectType(); | |||
| auto isStandaloneApplication = (! type.isAudioPlugin() && ! type.isDynamicLibrary()); | |||
| out << newLine | |||
| << "//==============================================================================" << newLine | |||
| << "#ifndef JUCE_STANDALONE_APPLICATION" << newLine | |||
| << " #if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone)" << newLine | |||
| << " #define JUCE_STANDALONE_APPLICATION JucePlugin_Build_Standalone" << newLine | |||
| << " #else" << newLine | |||
| << " #define JUCE_STANDALONE_APPLICATION " << (isStandaloneApplication ? "1" : "0") << newLine | |||
| << " #endif" << newLine | |||
| << "#endif" << newLine; | |||
| } | |||
| template <typename WriterCallback> | |||
| void ProjectSaver::writeOrRemoveGeneratedFile (const String& name, WriterCallback&& writerCallback) | |||
| { | |||
| MemoryOutputStream mem; | |||
| mem.setNewLineString (projectLineFeed); | |||
| writerCallback (mem); | |||
| if (mem.getDataSize() != 0) | |||
| { | |||
| saveGeneratedFile (name, mem); | |||
| return; | |||
| } | |||
| const auto destFile = generatedCodeFolder.getChildFile (name); | |||
| if (destFile.existsAsFile()) | |||
| { | |||
| if (! destFile.deleteFile()) | |||
| addError ("Couldn't remove unnecessary file: " + destFile.getFullPathName()); | |||
| } | |||
| } | |||
| void ProjectSaver::writePluginDefines() | |||
| { | |||
| writeOrRemoveGeneratedFile (Project::getPluginDefinesFilename(), [&] (MemoryOutputStream& mem) | |||
| { | |||
| writePluginDefines (mem); | |||
| }); | |||
| } | |||
| void ProjectSaver::writeAppConfigFile (const OwnedArray<LibraryModule>& modules, const String& userContent) | |||
| { | |||
| writeOrRemoveGeneratedFile (Project::getAppConfigFilename(), [&] (MemoryOutputStream& mem) | |||
| { | |||
| writeAppConfig (mem, modules, userContent); | |||
| }); | |||
| } | |||
| void ProjectSaver::writeAppHeader (MemoryOutputStream& out, const OwnedArray<LibraryModule>& modules) | |||
| { | |||
| writeAutoGenWarningComment (out); | |||
| out << " This is the header file that your files should include in order to get all the" << newLine | |||
| << " JUCE library headers. You should avoid including the JUCE headers directly in" << newLine | |||
| << " your own source files, because that wouldn't pick up the correct configuration" << newLine | |||
| << " options for your app." << newLine | |||
| << newLine | |||
| << "*/" << newLine << newLine; | |||
| out << "#pragma once" << newLine << newLine; | |||
| if (getAppConfigFile().exists() && project.shouldUseAppConfig()) | |||
| out << CodeHelpers::createIncludeStatement (Project::getAppConfigFilename()) << newLine; | |||
| if (modules.size() > 0) | |||
| { | |||
| out << newLine; | |||
| for (auto* module : modules) | |||
| module->writeIncludes (*this, out); | |||
| out << newLine; | |||
| } | |||
| if (hasBinaryData && project.shouldIncludeBinaryInJuceHeader()) | |||
| out << CodeHelpers::createIncludeStatement (project.getBinaryDataHeaderFile(), getAppConfigFile()) << newLine; | |||
| out << newLine | |||
| << "#if defined (JUCE_PROJUCER_VERSION) && JUCE_PROJUCER_VERSION < JUCE_VERSION" << newLine | |||
| << " /** If you've hit this error then the version of the Projucer that was used to generate this project is" << newLine | |||
| << " older than the version of the JUCE modules being included. To fix this error, re-save your project" << newLine | |||
| << " using the latest version of the Projucer or, if you aren't using the Projucer to manage your project," << newLine | |||
| << " remove the JUCE_PROJUCER_VERSION define from the AppConfig.h file." << newLine | |||
| << " */" << newLine | |||
| << " #error \"This project was last saved using an outdated version of the Projucer! Re-save this project with the latest version to fix this error.\"" << newLine | |||
| << "#endif" << newLine | |||
| << newLine; | |||
| if (project.shouldAddUsingNamespaceToJuceHeader()) | |||
| out << "#if ! DONT_SET_USING_JUCE_NAMESPACE" << newLine | |||
| << " // If your code uses a lot of JUCE classes, then this will obviously save you" << newLine | |||
| << " // a lot of typing, but can be disabled by setting DONT_SET_USING_JUCE_NAMESPACE." << newLine | |||
| << " using namespace juce;" << newLine | |||
| << "#endif" << newLine; | |||
| out << newLine | |||
| << "#if ! JUCE_DONT_DECLARE_PROJECTINFO" << newLine | |||
| << "namespace ProjectInfo" << newLine | |||
| << "{" << newLine | |||
| << " const char* const projectName = " << CppTokeniserFunctions::addEscapeChars (project.getProjectNameString()).quoted() << ";" << newLine | |||
| << " const char* const companyName = " << CppTokeniserFunctions::addEscapeChars (project.getCompanyNameString()).quoted() << ";" << newLine | |||
| << " const char* const versionString = " << CppTokeniserFunctions::addEscapeChars (project.getVersionString()).quoted() << ";" << newLine | |||
| << " const int versionNumber = " << project.getVersionAsHex() << ";" << newLine | |||
| << "}" << newLine | |||
| << "#endif" << newLine; | |||
| } | |||
| void ProjectSaver::writeAppHeader (const OwnedArray<LibraryModule>& modules) | |||
| { | |||
| MemoryOutputStream mem; | |||
| mem.setNewLineString (projectLineFeed); | |||
| writeAppHeader (mem, modules); | |||
| saveGeneratedFile (Project::getJuceSourceHFilename(), mem); | |||
| } | |||
| void ProjectSaver::writeModuleCppWrappers (const OwnedArray<LibraryModule>& modules) | |||
| { | |||
| for (auto* module : modules) | |||
| { | |||
| for (auto& cu : module->getAllCompileUnits()) | |||
| { | |||
| MemoryOutputStream mem; | |||
| mem.setNewLineString (projectLineFeed); | |||
| writeAutoGenWarningComment (mem); | |||
| mem << "*/" << newLine << newLine; | |||
| if (project.shouldUseAppConfig()) | |||
| mem << "#include " << Project::getAppConfigFilename().quoted() << newLine; | |||
| mem << "#include <"; | |||
| if (cu.file.getFileExtension() != ".r") // .r files are included without the path | |||
| mem << module->getID() << "/"; | |||
| mem << cu.file.getFileName() << ">" << newLine; | |||
| replaceFileIfDifferent (generatedCodeFolder.getChildFile (cu.getFilenameForProxyFile()), mem); | |||
| } | |||
| } | |||
| } | |||
| void ProjectSaver::writeBinaryDataFiles() | |||
| { | |||
| auto binaryDataH = project.getBinaryDataHeaderFile(); | |||
| JucerResourceFile resourceFile (project); | |||
| if (resourceFile.getNumFiles() > 0) | |||
| { | |||
| auto dataNamespace = project.getBinaryDataNamespaceString().trim(); | |||
| if (dataNamespace.isEmpty()) | |||
| dataNamespace = "BinaryData"; | |||
| resourceFile.setClassName (dataNamespace); | |||
| auto maxSize = project.getMaxBinaryFileSize(); | |||
| if (maxSize <= 0) | |||
| maxSize = 10 * 1024 * 1024; | |||
| Array<File> binaryDataFiles; | |||
| auto r = resourceFile.write (maxSize); | |||
| if (r.result.wasOk()) | |||
| { | |||
| hasBinaryData = true; | |||
| for (auto& f : r.filesCreated) | |||
| { | |||
| filesCreated.add (f); | |||
| generatedFilesGroup.addFileRetainingSortOrder (f, ! f.hasFileExtension (".h")); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| addError (r.result.getErrorMessage()); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| for (int i = 20; --i >= 0;) | |||
| project.getBinaryDataCppFile (i).deleteFile(); | |||
| binaryDataH.deleteFile(); | |||
| } | |||
| } | |||
| void ProjectSaver::writeReadmeFile() | |||
| { | |||
| MemoryOutputStream out; | |||
| out.setNewLineString (projectLineFeed); | |||
| out << newLine | |||
| << " Important Note!!" << newLine | |||
| << " ================" << newLine | |||
| << newLine | |||
| << "The purpose of this folder is to contain files that are auto-generated by the Projucer," << newLine | |||
| << "and ALL files in this folder will be mercilessly DELETED and completely re-written whenever" << newLine | |||
| << "the Projucer saves your project." << newLine | |||
| << newLine | |||
| << "Therefore, it's a bad idea to make any manual changes to the files in here, or to" << newLine | |||
| << "put any of your own files in here if you don't want to lose them. (Of course you may choose" << newLine | |||
| << "to add the folder's contents to your version-control system so that you can re-merge your own" << newLine | |||
| << "modifications after the Projucer has saved its changes)." << newLine; | |||
| replaceFileIfDifferent (generatedCodeFolder.getChildFile ("ReadMe.txt"), out); | |||
| } | |||
| String ProjectSaver::getAudioPluginDefines() const | |||
| { | |||
| const auto flags = project.getAudioPluginFlags(); | |||
| @@ -47,7 +690,27 @@ String ProjectSaver::getAudioPluginDefines() const | |||
| return mem.toString().trim(); | |||
| } | |||
| void ProjectSaver::writeProjects (const OwnedArray<LibraryModule>& modules, const String& specifiedExporterToSave, bool isCommandLineApp) | |||
| void ProjectSaver::writeUnityScriptFile() | |||
| { | |||
| auto unityScriptContents = replaceLineFeeds (BinaryData::UnityPluginGUIScript_cs_in, | |||
| projectLineFeed); | |||
| auto projectName = Project::addUnityPluginPrefixIfNecessary (project.getProjectNameString()); | |||
| unityScriptContents = unityScriptContents.replace ("${plugin_class_name}", projectName.replace (" ", "_")) | |||
| .replace ("${plugin_name}", projectName) | |||
| .replace ("${plugin_vendor}", project.getPluginManufacturerString()) | |||
| .replace ("${plugin_description}", project.getPluginDescriptionString()); | |||
| auto f = generatedCodeFolder.getChildFile (project.getUnityScriptName()); | |||
| MemoryOutputStream out; | |||
| out << unityScriptContents; | |||
| replaceFileIfDifferent (f, out); | |||
| } | |||
| void ProjectSaver::writeProjects (const OwnedArray<LibraryModule>& modules, ProjectExporter* specifiedExporterToSave) | |||
| { | |||
| ThreadPool threadPool; | |||
| @@ -55,24 +718,27 @@ void ProjectSaver::writeProjects (const OwnedArray<LibraryModule>& modules, cons | |||
| auto originalGeneratedGroup = generatedFilesGroup.state.createCopy(); | |||
| CLionProjectExporter* clionExporter = nullptr; | |||
| OwnedArray<ProjectExporter> exporters; | |||
| std::vector<std::unique_ptr<ProjectExporter>> exporters; | |||
| try | |||
| { | |||
| for (Project::ExporterIterator exp (project); exp.next();) | |||
| { | |||
| if (specifiedExporterToSave.isNotEmpty() && exp->getName() != specifiedExporterToSave) | |||
| if (specifiedExporterToSave != nullptr && exp->getName() != specifiedExporterToSave->getName()) | |||
| continue; | |||
| auto exporter = exporters.add (std::move (exp.exporter)); | |||
| exporters.push_back (std::move (exp.exporter)); | |||
| } | |||
| for (auto& exporter : exporters) | |||
| { | |||
| exporter->initialiseDependencyPathValues(); | |||
| if (exporter->getTargetFolder().createDirectory()) | |||
| { | |||
| if (exporter->isCLion()) | |||
| { | |||
| clionExporter = dynamic_cast<CLionProjectExporter*> (exporter); | |||
| clionExporter = dynamic_cast<CLionProjectExporter*> (exporter.get()); | |||
| } | |||
| else | |||
| { | |||
| @@ -84,17 +750,17 @@ void ProjectSaver::writeProjects (const OwnedArray<LibraryModule>& modules, cons | |||
| generatedFilesGroup.state = originalGeneratedGroup.createCopy(); | |||
| exporter->addSettingsForProjectType (project.getProjectType()); | |||
| for (auto& module: modules) | |||
| for (auto* module : modules) | |||
| module->addSettingsForModuleToExporter (*exporter, *this); | |||
| generatedFilesGroup.sortAlphabetically (true, true); | |||
| exporter->getAllGroups().add (generatedFilesGroup); | |||
| } | |||
| if (isCommandLineApp) | |||
| saveExporter (exporter, modules); | |||
| if (ProjucerApplication::getApp().isRunningCommandLine) | |||
| saveExporter (*exporter, modules); | |||
| else | |||
| threadPool.addJob (new ExporterJob (*this, exporter, modules), true); | |||
| threadPool.addJob (new ExporterJob (*this, *exporter, modules), true); | |||
| } | |||
| else | |||
| { | |||
| @@ -107,15 +773,69 @@ void ProjectSaver::writeProjects (const OwnedArray<LibraryModule>& modules, cons | |||
| addError (saveError.message); | |||
| } | |||
| if (! isCommandLineApp) | |||
| while (threadPool.getNumJobs() > 0) | |||
| Thread::sleep (10); | |||
| while (threadPool.getNumJobs() > 0) | |||
| Thread::sleep (10); | |||
| if (clionExporter != nullptr) | |||
| { | |||
| for (auto* exporter : exporters) | |||
| clionExporter->writeCMakeListsExporterSection (exporter); | |||
| for (auto& exporter : exporters) | |||
| clionExporter->writeCMakeListsExporterSection (exporter.get()); | |||
| std::cout << "Finished saving: " << clionExporter->getName() << std::endl; | |||
| } | |||
| } | |||
| void ProjectSaver::runPostExportScript() | |||
| { | |||
| #if JUCE_WINDOWS | |||
| auto cmdString = project.getPostExportShellCommandWinString(); | |||
| #else | |||
| auto cmdString = project.getPostExportShellCommandPosixString(); | |||
| #endif | |||
| auto shellCommand = cmdString.replace ("%%1%%", project.getProjectFolder().getFullPathName()); | |||
| if (shellCommand.isNotEmpty()) | |||
| { | |||
| #if JUCE_WINDOWS | |||
| StringArray argList ("cmd.exe", "/c"); | |||
| #else | |||
| StringArray argList ("/bin/sh", "-c"); | |||
| #endif | |||
| argList.add (shellCommand); | |||
| ChildProcess shellProcess; | |||
| if (! shellProcess.start (argList)) | |||
| { | |||
| addError ("Failed to run shell command: " + argList.joinIntoString (" ")); | |||
| return; | |||
| } | |||
| if (! shellProcess.waitForProcessToFinish (10000)) | |||
| { | |||
| addError ("Timeout running shell command: " + argList.joinIntoString (" ")); | |||
| return; | |||
| } | |||
| auto exitCode = shellProcess.getExitCode(); | |||
| if (exitCode != 0) | |||
| addError ("Shell command: " + argList.joinIntoString (" ") + " failed with exit code: " + String (exitCode)); | |||
| } | |||
| } | |||
| void ProjectSaver::saveExporter (ProjectExporter& exporter, const OwnedArray<LibraryModule>& modules) | |||
| { | |||
| try | |||
| { | |||
| exporter.create (modules); | |||
| if (! exporter.isCLion()) | |||
| std::cout << "Finished saving: " << exporter.getName() << std::endl; | |||
| } | |||
| catch (build_tools::SaveError& error) | |||
| { | |||
| addError (error.message); | |||
| } | |||
| } | |||
| @@ -18,809 +18,127 @@ | |||
| #pragma once | |||
| #include "../Application/jucer_Headers.h" | |||
| #include "jucer_ResourceFile.h" | |||
| #include "../Project/jucer_Module.h" | |||
| #include "../Project/Modules/jucer_Modules.h" | |||
| #include "jucer_ProjectExporter.h" | |||
| //============================================================================== | |||
| class ProjectSaver | |||
| { | |||
| public: | |||
| ProjectSaver (Project& p, const File& file) | |||
| : project (p), | |||
| projectFile (file), | |||
| generatedCodeFolder (project.getGeneratedCodeFolder()), | |||
| generatedFilesGroup (Project::Item::createGroup (project, getJuceCodeGroupName(), "__generatedcode__", true)) | |||
| { | |||
| generatedFilesGroup.setID (getGeneratedGroupID()); | |||
| } | |||
| ProjectSaver (Project& projectToSave); | |||
| Result save (ProjectExporter* exporterToSave = nullptr); | |||
| Result saveResourcesOnly(); | |||
| void saveBasicProjectItems (const OwnedArray<LibraryModule>& modules, const String& appConfigUserContent); | |||
| Result saveContentNeededForLiveBuild(); | |||
| Project& getProject() { return project; } | |||
| Project::Item addFileToGeneratedGroup (const File& file); | |||
| bool copyFolder (const File& source, const File& dest); | |||
| struct SaveThread : public ThreadWithProgressWindow | |||
| static String getJuceCodeGroupName() { return "JUCE Library Code"; } | |||
| private: | |||
| //============================================================================== | |||
| struct SaveThreadWithProgressWindow : public ThreadWithProgressWindow | |||
| { | |||
| public: | |||
| SaveThread (ProjectSaver& ps, bool wait, const String& exp) | |||
| SaveThreadWithProgressWindow (ProjectSaver& ps, ProjectExporter* exporterToSave) | |||
| : ThreadWithProgressWindow ("Saving...", true, false), | |||
| saver (ps), | |||
| shouldWaitAfterSaving (wait), | |||
| specifiedExporterToSave (exp) | |||
| specifiedExporterToSave (exporterToSave) | |||
| {} | |||
| void run() override | |||
| { | |||
| setProgress (-1); | |||
| result = saver.save (false, shouldWaitAfterSaving, specifiedExporterToSave); | |||
| result = saver.saveProject (specifiedExporterToSave); | |||
| } | |||
| ProjectSaver& saver; | |||
| Result result = Result::ok(); | |||
| bool shouldWaitAfterSaving; | |||
| String specifiedExporterToSave; | |||
| ProjectExporter* specifiedExporterToSave; | |||
| JUCE_DECLARE_NON_COPYABLE (SaveThread) | |||
| JUCE_DECLARE_NON_COPYABLE (SaveThreadWithProgressWindow) | |||
| }; | |||
| Result save (bool showProgressBox, bool waitAfterSaving, const String& specifiedExporterToSave) | |||
| { | |||
| if (showProgressBox) | |||
| { | |||
| SaveThread thread (*this, waitAfterSaving, specifiedExporterToSave); | |||
| thread.runThread(); | |||
| return thread.result; | |||
| } | |||
| projectLineFeed = project.getProjectLineFeed(); | |||
| auto appConfigUserContent = loadUserContentFromAppConfig(); | |||
| auto oldFile = project.getFile(); | |||
| project.setFile (projectFile); | |||
| OwnedArray<LibraryModule> modules; | |||
| project.getEnabledModules().createRequiredModules (modules); | |||
| checkModuleValidity (modules); | |||
| if (errors.size() == 0) | |||
| { | |||
| writeMainProjectFile(); | |||
| project.updateModificationTime(); | |||
| auto projectRootHash = project.getProjectRoot().toXmlString().hashCode(); | |||
| if (project.isAudioPluginProject()) | |||
| { | |||
| if (project.shouldBuildUnityPlugin()) | |||
| writeUnityScriptFile(); | |||
| } | |||
| saveBasicProjectItems (modules, appConfigUserContent); | |||
| writeProjects (modules, specifiedExporterToSave, ! showProgressBox); | |||
| runPostExportScript(); | |||
| // if the project root has changed after writing the other files then re-save it | |||
| if (project.getProjectRoot().toXmlString().hashCode() != projectRootHash) | |||
| { | |||
| writeMainProjectFile(); | |||
| project.updateModificationTime(); | |||
| } | |||
| if (generatedCodeFolder.exists()) | |||
| { | |||
| writeReadmeFile(); | |||
| deleteUnwantedFilesIn (generatedCodeFolder); | |||
| } | |||
| if (errors.size() == 0) | |||
| { | |||
| // Workaround for a bug where Xcode thinks the project is invalid if opened immediately | |||
| // after writing | |||
| if (waitAfterSaving) | |||
| Thread::sleep (2000); | |||
| return Result::ok(); | |||
| } | |||
| } | |||
| project.setFile (oldFile); | |||
| return Result::fail (errors[0]); | |||
| } | |||
| Result saveResourcesOnly() | |||
| { | |||
| writeBinaryDataFiles(); | |||
| if (errors.size() > 0) | |||
| return Result::fail (errors[0]); | |||
| return Result::ok(); | |||
| } | |||
| void saveBasicProjectItems (const OwnedArray<LibraryModule>& modules, const String& appConfigUserContent) | |||
| { | |||
| writePluginDefines(); | |||
| writeAppConfigFile (modules, appConfigUserContent); | |||
| writeBinaryDataFiles(); | |||
| writeAppHeader (modules); | |||
| writeModuleCppWrappers (modules); | |||
| } | |||
| Result saveContentNeededForLiveBuild() | |||
| { | |||
| OwnedArray<LibraryModule> modules; | |||
| project.getEnabledModules().createRequiredModules (modules); | |||
| checkModuleValidity (modules); | |||
| if (errors.size() == 0) | |||
| { | |||
| saveBasicProjectItems (modules, loadUserContentFromAppConfig()); | |||
| return Result::ok(); | |||
| } | |||
| return Result::fail (errors[0]); | |||
| } | |||
| Project::Item saveGeneratedFile (const String& filePath, const MemoryOutputStream& newData) | |||
| { | |||
| if (! generatedCodeFolder.createDirectory()) | |||
| { | |||
| addError ("Couldn't create folder: " + generatedCodeFolder.getFullPathName()); | |||
| return Project::Item (project, ValueTree(), false); | |||
| } | |||
| auto file = generatedCodeFolder.getChildFile (filePath); | |||
| if (replaceFileIfDifferent (file, newData)) | |||
| return addFileToGeneratedGroup (file); | |||
| return { project, {}, true }; | |||
| } | |||
| Project::Item addFileToGeneratedGroup (const File& file) | |||
| { | |||
| auto item = generatedFilesGroup.findItemForFile (file); | |||
| if (item.isValid()) | |||
| return item; | |||
| generatedFilesGroup.addFileAtIndex (file, -1, true); | |||
| return generatedFilesGroup.findItemForFile (file); | |||
| } | |||
| static void writeAutoGenWarningComment (OutputStream& out) | |||
| { | |||
| out << "/*" << newLine << newLine | |||
| << " IMPORTANT! This file is auto-generated each time you save your" << newLine | |||
| << " project - if you alter its contents, your changes may be overwritten!" << newLine | |||
| << newLine; | |||
| } | |||
| static const char* getGeneratedGroupID() noexcept { return "__jucelibfiles"; } | |||
| Project::Item& getGeneratedCodeGroup() { return generatedFilesGroup; } | |||
| static String getJuceCodeGroupName() { return "JUCE Library Code"; } | |||
| File getGeneratedCodeFolder() const { return generatedCodeFolder; } | |||
| bool replaceFileIfDifferent (const File& f, const MemoryOutputStream& newData) | |||
| class ExporterJob : public ThreadPoolJob | |||
| { | |||
| filesCreated.add (f); | |||
| if (!build_tools::overwriteFileWithNewDataIfDifferent (f, newData)) | |||
| { | |||
| addError ("Can't write to file: " + f.getFullPathName()); | |||
| return false; | |||
| } | |||
| public: | |||
| ExporterJob (ProjectSaver& ps, ProjectExporter& pe, const OwnedArray<LibraryModule>& modulesList) | |||
| : ThreadPoolJob ("export"), | |||
| owner (ps), | |||
| exporter (pe), | |||
| modules (modulesList) | |||
| { | |||
| } | |||
| JobStatus runJob() override | |||
| { | |||
| owner.saveExporter (exporter, modules); | |||
| return jobHasFinished; | |||
| } | |||
| return true; | |||
| } | |||
| private: | |||
| ProjectSaver& owner; | |||
| ProjectExporter& exporter; | |||
| const OwnedArray<LibraryModule>& modules; | |||
| static bool shouldFolderBeIgnoredWhenCopying (const File& f) | |||
| { | |||
| return f.getFileName() == ".git" || f.getFileName() == ".svn" || f.getFileName() == ".cvs"; | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExporterJob) | |||
| }; | |||
| bool copyFolder (const File& source, const File& dest) | |||
| { | |||
| if (source.isDirectory() && dest.createDirectory()) | |||
| { | |||
| for (auto& f : source.findChildFiles (File::findFiles, false)) | |||
| { | |||
| auto target = dest.getChildFile (f.getFileName()); | |||
| filesCreated.add (target); | |||
| //============================================================================== | |||
| Project::Item saveGeneratedFile (const String& filePath, const MemoryOutputStream& newData); | |||
| bool replaceFileIfDifferent (const File& f, const MemoryOutputStream& newData); | |||
| bool deleteUnwantedFilesIn (const File& parent); | |||
| if (! f.copyFileTo (target)) | |||
| return false; | |||
| } | |||
| void addError (const String& message); | |||
| for (auto& f : source.findChildFiles (File::findDirectories, false)) | |||
| if (! shouldFolderBeIgnoredWhenCopying (f)) | |||
| if (! copyFolder (f, dest.getChildFile (f.getFileName()))) | |||
| return false; | |||
| File getAppConfigFile() const; | |||
| File getPluginDefinesFile() const; | |||
| return true; | |||
| } | |||
| String loadUserContentFromAppConfig() const; | |||
| String getAudioPluginDefines() const; | |||
| OwnedArray<LibraryModule> getModules(); | |||
| return false; | |||
| } | |||
| Result saveProject (ProjectExporter* specifiedExporterToSave); | |||
| template <typename WriterCallback> | |||
| void writeOrRemoveGeneratedFile (const String& name, WriterCallback&& writerCallback); | |||
| void writePluginDefines (MemoryOutputStream& outStream) const; | |||
| void writePluginDefines(); | |||
| void writeAppConfigFile (const OwnedArray<LibraryModule>& modules, const String& userContent); | |||
| void writeMainProjectFile(); | |||
| void writeAppConfig (MemoryOutputStream& outStream, const OwnedArray<LibraryModule>& modules, const String& userContent); | |||
| void writeAppHeader (MemoryOutputStream& outStream, const OwnedArray<LibraryModule>& modules); | |||
| void writeAppHeader (const OwnedArray<LibraryModule>& modules); | |||
| void writeModuleCppWrappers (const OwnedArray<LibraryModule>& modules); | |||
| void writeBinaryDataFiles(); | |||
| void writeReadmeFile(); | |||
| void writePluginCharacteristicsFile(); | |||
| void writeUnityScriptFile(); | |||
| void writeProjects (const OwnedArray<LibraryModule>&, ProjectExporter*); | |||
| void runPostExportScript(); | |||
| void saveExporter (ProjectExporter& exporter, const OwnedArray<LibraryModule>& modules); | |||
| //============================================================================== | |||
| Project& project; | |||
| SortedSet<File> filesCreated; | |||
| private: | |||
| const File projectFile, generatedCodeFolder; | |||
| File generatedCodeFolder; | |||
| Project::Item generatedFilesGroup; | |||
| StringArray errors; | |||
| SortedSet<File> filesCreated; | |||
| String projectLineFeed; | |||
| CriticalSection errorLock; | |||
| StringArray errors; | |||
| bool hasBinaryData = false; | |||
| String projectLineFeed = "\r\n"; | |||
| // Recursively clears out any files in a folder that we didn't create, but avoids | |||
| // any folders containing hidden files that might be used by version-control systems. | |||
| bool deleteUnwantedFilesIn (const File& parent) | |||
| { | |||
| bool folderIsNowEmpty = true; | |||
| Array<File> filesToDelete; | |||
| for (const auto& i : RangedDirectoryIterator (parent, false, "*", File::findFilesAndDirectories)) | |||
| { | |||
| auto f = i.getFile(); | |||
| if (filesCreated.contains (f) || shouldFileBeKept (f.getFileName())) | |||
| { | |||
| folderIsNowEmpty = false; | |||
| } | |||
| else if (i.isDirectory()) | |||
| { | |||
| if (deleteUnwantedFilesIn (f)) | |||
| filesToDelete.add (f); | |||
| else | |||
| folderIsNowEmpty = false; | |||
| } | |||
| else | |||
| { | |||
| filesToDelete.add (f); | |||
| } | |||
| } | |||
| for (int j = filesToDelete.size(); --j >= 0;) | |||
| filesToDelete.getReference(j).deleteRecursively(); | |||
| return folderIsNowEmpty; | |||
| } | |||
| static bool shouldFileBeKept (const String& filename) | |||
| { | |||
| static const char* filesToKeep[] = { ".svn", ".cvs", "CMakeLists.txt" }; | |||
| for (auto* f : filesToKeep) | |||
| if (filename == f) | |||
| return true; | |||
| return false; | |||
| } | |||
| void writeMainProjectFile() | |||
| { | |||
| if (auto xml = project.getProjectRoot().createXml()) | |||
| { | |||
| XmlElement::TextFormat format; | |||
| format.newLineChars = projectLineFeed.toRawUTF8(); | |||
| MemoryOutputStream mo (8192); | |||
| xml->writeTo (mo, format); | |||
| replaceFileIfDifferent (projectFile, mo); | |||
| } | |||
| else | |||
| { | |||
| jassertfalse; | |||
| } | |||
| } | |||
| static int findLongestModuleName (const OwnedArray<LibraryModule>& modules) | |||
| { | |||
| int longest = 0; | |||
| for (auto& m : modules) | |||
| longest = jmax (longest, m->getID().length()); | |||
| return longest; | |||
| } | |||
| File getAppConfigFile() const { return generatedCodeFolder.getChildFile (Project::getAppConfigFilename()); } | |||
| File getPluginDefinesFile() const { return generatedCodeFolder.getChildFile (Project::getPluginDefinesFilename()); } | |||
| String loadUserContentFromAppConfig() const | |||
| { | |||
| StringArray userContent; | |||
| bool foundCodeSection = false; | |||
| auto lines = StringArray::fromLines (getAppConfigFile().loadFileAsString()); | |||
| for (int i = 0; i < lines.size(); ++i) | |||
| { | |||
| if (lines[i].contains ("[BEGIN_USER_CODE_SECTION]")) | |||
| { | |||
| for (int j = i + 1; j < lines.size() && ! lines[j].contains ("[END_USER_CODE_SECTION]"); ++j) | |||
| userContent.add (lines[j]); | |||
| foundCodeSection = true; | |||
| break; | |||
| } | |||
| } | |||
| if (! foundCodeSection) | |||
| { | |||
| userContent.add ({}); | |||
| userContent.add ("// (You can add your own code in this section, and the Projucer will not overwrite it)"); | |||
| userContent.add ({}); | |||
| } | |||
| return userContent.joinIntoString (projectLineFeed) + projectLineFeed; | |||
| } | |||
| void checkModuleValidity (OwnedArray<LibraryModule>& modules) | |||
| { | |||
| if (project.getNumExporters() == 0) | |||
| { | |||
| addError ("No exporters found!\n" | |||
| "Please add an exporter before saving."); | |||
| return; | |||
| } | |||
| for (auto moduleIter = modules.begin(); moduleIter != modules.end(); ++moduleIter) | |||
| { | |||
| if (auto* module = *moduleIter) | |||
| { | |||
| if (! module->isValid()) | |||
| { | |||
| addError ("At least one of your JUCE module paths is invalid!\n" | |||
| "Please go to the Modules settings page and ensure each path points to the correct JUCE modules folder."); | |||
| return; | |||
| } | |||
| if (project.getEnabledModules().getExtraDependenciesNeeded (module->getID()).size() > 0) | |||
| { | |||
| addError ("At least one of your modules has missing dependencies!\n" | |||
| "Please go to the settings page of the highlighted modules and add the required dependencies."); | |||
| return; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| // this should never happen! | |||
| jassertfalse; | |||
| } | |||
| } | |||
| } | |||
| void writePluginDefines (MemoryOutputStream& out) const | |||
| { | |||
| const auto pluginDefines = getAudioPluginDefines(); | |||
| if (pluginDefines.isEmpty()) | |||
| return; | |||
| writeAutoGenWarningComment (out); | |||
| out << "*/" << newLine << newLine | |||
| << "#pragma once" << newLine << newLine | |||
| << pluginDefines << newLine; | |||
| } | |||
| void writeAppConfig (MemoryOutputStream& out, const OwnedArray<LibraryModule>& modules, const String& userContent) | |||
| { | |||
| if (! project.shouldUseAppConfig()) | |||
| return; | |||
| writeAutoGenWarningComment (out); | |||
| out << " There's a section below where you can add your own custom code safely, and the" << newLine | |||
| << " Projucer will preserve the contents of that block, but the best way to change" << newLine | |||
| << " any of these definitions is by using the Projucer's project settings." << newLine | |||
| << newLine | |||
| << " Any commented-out settings will assume their default values." << newLine | |||
| << newLine | |||
| << "*/" << newLine | |||
| << newLine; | |||
| out << "#pragma once" << newLine | |||
| << newLine | |||
| << "//==============================================================================" << newLine | |||
| << "// [BEGIN_USER_CODE_SECTION]" << newLine | |||
| << userContent | |||
| << "// [END_USER_CODE_SECTION]" << newLine; | |||
| if (getPluginDefinesFile().existsAsFile() && getAudioPluginDefines().isNotEmpty()) | |||
| out << newLine << CodeHelpers::createIncludeStatement (Project::getPluginDefinesFilename()) << newLine; | |||
| out << newLine | |||
| << "/*" << newLine | |||
| << " ==============================================================================" << newLine | |||
| << newLine | |||
| << " In accordance with the terms of the JUCE 5 End-Use License Agreement, the" << newLine | |||
| << " JUCE Code in SECTION A cannot be removed, changed or otherwise rendered" << newLine | |||
| << " ineffective unless you have a JUCE Indie or Pro license, or are using JUCE" << newLine | |||
| << " under the GPL v3 license." << newLine | |||
| << newLine | |||
| << " End User License Agreement: www.juce.com/juce-5-licence" << newLine | |||
| << newLine | |||
| << " ==============================================================================" << newLine | |||
| << "*/" << newLine | |||
| << newLine | |||
| << "// BEGIN SECTION A" << newLine | |||
| << newLine | |||
| << "#ifndef JUCE_DISPLAY_SPLASH_SCREEN" << newLine | |||
| << " #define JUCE_DISPLAY_SPLASH_SCREEN " << (project.shouldDisplaySplashScreen() ? "1" : "0") << newLine | |||
| << "#endif" << newLine << newLine | |||
| << "// END SECTION A" << newLine | |||
| << newLine | |||
| << "#define JUCE_USE_DARK_SPLASH_SCREEN " << (project.getSplashScreenColourString() == "Dark" ? "1" : "0") << newLine | |||
| << newLine | |||
| << "#define JUCE_PROJUCER_VERSION 0x" << String::toHexString (ProjectInfo::versionNumber) << newLine; | |||
| out << newLine | |||
| << "//==============================================================================" << newLine; | |||
| auto longestName = findLongestModuleName (modules); | |||
| for (auto& m : modules) | |||
| out << "#define JUCE_MODULE_AVAILABLE_" << m->getID() | |||
| << String::repeatedString (" ", longestName + 5 - m->getID().length()) << " 1" << newLine; | |||
| out << newLine << "#define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1" << newLine; | |||
| for (auto& m : modules) | |||
| { | |||
| OwnedArray<Project::ConfigFlag> flags; | |||
| m->getConfigFlags (project, flags); | |||
| if (flags.size() > 0) | |||
| { | |||
| out << newLine | |||
| << "//==============================================================================" << newLine | |||
| << "// " << m->getID() << " flags:" << newLine; | |||
| for (auto* flag : flags) | |||
| { | |||
| out << newLine | |||
| << "#ifndef " << flag->symbol | |||
| << newLine | |||
| << (flag->value.isUsingDefault() ? " //#define " : " #define ") << flag->symbol << " " << (flag->value.get() ? "1" : "0") | |||
| << newLine | |||
| << "#endif" | |||
| << newLine; | |||
| } | |||
| } | |||
| } | |||
| { | |||
| auto& type = project.getProjectType(); | |||
| auto isStandaloneApplication = (! type.isAudioPlugin() && ! type.isDynamicLibrary()); | |||
| out << newLine | |||
| << "//==============================================================================" << newLine | |||
| << "#ifndef JUCE_STANDALONE_APPLICATION" << newLine | |||
| << " #if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone)" << newLine | |||
| << " #define JUCE_STANDALONE_APPLICATION JucePlugin_Build_Standalone" << newLine | |||
| << " #else" << newLine | |||
| << " #define JUCE_STANDALONE_APPLICATION " << (isStandaloneApplication ? "1" : "0") << newLine | |||
| << " #endif" << newLine | |||
| << "#endif" << newLine; | |||
| } | |||
| } | |||
| template <typename WriterCallback> | |||
| void writeOrRemoveGeneratedFile (const String& name, WriterCallback&& writerCallback) | |||
| { | |||
| MemoryOutputStream mem; | |||
| mem.setNewLineString (projectLineFeed); | |||
| writerCallback (mem); | |||
| if (mem.getDataSize() != 0) | |||
| { | |||
| saveGeneratedFile (name, mem); | |||
| return; | |||
| } | |||
| const auto destFile = generatedCodeFolder.getChildFile (name); | |||
| if (destFile.existsAsFile()) | |||
| { | |||
| if (! destFile.deleteFile()) | |||
| addError ("Couldn't remove unnecessary file: " + destFile.getFullPathName()); | |||
| } | |||
| } | |||
| void writePluginDefines() | |||
| { | |||
| writeOrRemoveGeneratedFile (Project::getPluginDefinesFilename(), [&] (MemoryOutputStream& mem) | |||
| { | |||
| writePluginDefines (mem); | |||
| }); | |||
| } | |||
| void writeAppConfigFile (const OwnedArray<LibraryModule>& modules, const String& userContent) | |||
| { | |||
| writeOrRemoveGeneratedFile (Project::getAppConfigFilename(), [&] (MemoryOutputStream& mem) | |||
| { | |||
| writeAppConfig (mem, modules, userContent); | |||
| }); | |||
| } | |||
| void writeAppHeader (MemoryOutputStream& out, const OwnedArray<LibraryModule>& modules) | |||
| { | |||
| writeAutoGenWarningComment (out); | |||
| out << " This is the header file that your files should include in order to get all the" << newLine | |||
| << " JUCE library headers. You should avoid including the JUCE headers directly in" << newLine | |||
| << " your own source files, because that wouldn't pick up the correct configuration" << newLine | |||
| << " options for your app." << newLine | |||
| << newLine | |||
| << "*/" << newLine << newLine; | |||
| out << "#pragma once" << newLine << newLine; | |||
| if (getAppConfigFile().exists() && project.shouldUseAppConfig()) | |||
| out << CodeHelpers::createIncludeStatement (Project::getAppConfigFilename()) << newLine; | |||
| if (modules.size() > 0) | |||
| { | |||
| out << newLine; | |||
| for (int i = 0; i < modules.size(); ++i) | |||
| modules.getUnchecked(i)->writeIncludes (*this, out); | |||
| out << newLine; | |||
| } | |||
| if (hasBinaryData && project.shouldIncludeBinaryInJuceHeader()) | |||
| out << CodeHelpers::createIncludeStatement (project.getBinaryDataHeaderFile(), getAppConfigFile()) << newLine; | |||
| out << newLine | |||
| << "#if defined (JUCE_PROJUCER_VERSION) && JUCE_PROJUCER_VERSION < JUCE_VERSION" << newLine | |||
| << " /** If you've hit this error then the version of the Projucer that was used to generate this project is" << newLine | |||
| << " older than the version of the JUCE modules being included. To fix this error, re-save your project" << newLine | |||
| << " using the latest version of the Projucer or, if you aren't using the Projucer to manage your project," << newLine | |||
| << " remove the JUCE_PROJUCER_VERSION define from the AppConfig.h file." << newLine | |||
| << " */" << newLine | |||
| << " #error \"This project was last saved using an outdated version of the Projucer! Re-save this project with the latest version to fix this error.\"" << newLine | |||
| << "#endif" << newLine | |||
| << newLine; | |||
| if (project.shouldAddUsingNamespaceToJuceHeader()) | |||
| out << "#if ! DONT_SET_USING_JUCE_NAMESPACE" << newLine | |||
| << " // If your code uses a lot of JUCE classes, then this will obviously save you" << newLine | |||
| << " // a lot of typing, but can be disabled by setting DONT_SET_USING_JUCE_NAMESPACE." << newLine | |||
| << " using namespace juce;" << newLine | |||
| << "#endif" << newLine | |||
| << newLine; | |||
| out << "#if ! JUCE_DONT_DECLARE_PROJECTINFO" << newLine | |||
| << "namespace ProjectInfo" << newLine | |||
| << "{" << newLine | |||
| << " const char* const projectName = " << CppTokeniserFunctions::addEscapeChars (project.getProjectNameString()).quoted() << ";" << newLine | |||
| << " const char* const companyName = " << CppTokeniserFunctions::addEscapeChars (project.getCompanyNameString()).quoted() << ";" << newLine | |||
| << " const char* const versionString = " << CppTokeniserFunctions::addEscapeChars (project.getVersionString()).quoted() << ";" << newLine | |||
| << " const int versionNumber = " << project.getVersionAsHex() << ";" << newLine | |||
| << "}" << newLine | |||
| << "#endif" << newLine; | |||
| } | |||
| void writeAppHeader (const OwnedArray<LibraryModule>& modules) | |||
| { | |||
| MemoryOutputStream mem; | |||
| mem.setNewLineString (projectLineFeed); | |||
| writeAppHeader (mem, modules); | |||
| saveGeneratedFile (Project::getJuceSourceHFilename(), mem); | |||
| } | |||
| void writeModuleCppWrappers (const OwnedArray<LibraryModule>& modules) | |||
| { | |||
| for (auto* module : modules) | |||
| { | |||
| for (auto& cu : module->getAllCompileUnits()) | |||
| { | |||
| MemoryOutputStream mem; | |||
| mem.setNewLineString (projectLineFeed); | |||
| writeAutoGenWarningComment (mem); | |||
| mem << "*/" << newLine << newLine; | |||
| if (project.shouldUseAppConfig()) | |||
| mem << "#include " << Project::getAppConfigFilename().quoted() << newLine; | |||
| mem << "#include <"; | |||
| if (cu.file.getFileExtension() != ".r") // .r files are included without the path | |||
| mem << module->getID() << "/"; | |||
| mem << cu.file.getFileName() << ">" << newLine; | |||
| replaceFileIfDifferent (generatedCodeFolder.getChildFile (cu.getFilenameForProxyFile()), mem); | |||
| } | |||
| } | |||
| } | |||
| void writeBinaryDataFiles() | |||
| { | |||
| auto binaryDataH = project.getBinaryDataHeaderFile(); | |||
| JucerResourceFile resourceFile (project); | |||
| if (resourceFile.getNumFiles() > 0) | |||
| { | |||
| auto dataNamespace = project.getBinaryDataNamespaceString().trim(); | |||
| if (dataNamespace.isEmpty()) | |||
| dataNamespace = "BinaryData"; | |||
| resourceFile.setClassName (dataNamespace); | |||
| auto maxSize = project.getMaxBinaryFileSize(); | |||
| if (maxSize <= 0) | |||
| maxSize = 10 * 1024 * 1024; | |||
| auto r = resourceFile.write (maxSize); | |||
| if (r.result.wasOk()) | |||
| { | |||
| hasBinaryData = true; | |||
| for (auto& f : r.filesCreated) | |||
| { | |||
| filesCreated.add (f); | |||
| generatedFilesGroup.addFileRetainingSortOrder (f, ! f.hasFileExtension (".h")); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| addError (r.result.getErrorMessage()); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| for (int i = 20; --i >= 0;) | |||
| project.getBinaryDataCppFile (i).deleteFile(); | |||
| binaryDataH.deleteFile(); | |||
| } | |||
| } | |||
| void writeReadmeFile() | |||
| { | |||
| MemoryOutputStream out; | |||
| out.setNewLineString (projectLineFeed); | |||
| out << newLine | |||
| << " Important Note!!" << newLine | |||
| << " ================" << newLine | |||
| << newLine | |||
| << "The purpose of this folder is to contain files that are auto-generated by the Projucer," << newLine | |||
| << "and ALL files in this folder will be mercilessly DELETED and completely re-written whenever" << newLine | |||
| << "the Projucer saves your project." << newLine | |||
| << newLine | |||
| << "Therefore, it's a bad idea to make any manual changes to the files in here, or to" << newLine | |||
| << "put any of your own files in here if you don't want to lose them. (Of course you may choose" << newLine | |||
| << "to add the folder's contents to your version-control system so that you can re-merge your own" << newLine | |||
| << "modifications after the Projucer has saved its changes)." << newLine; | |||
| replaceFileIfDifferent (generatedCodeFolder.getChildFile ("ReadMe.txt"), out); | |||
| } | |||
| void addError (const String& message) | |||
| { | |||
| const ScopedLock sl (errorLock); | |||
| errors.add (message); | |||
| } | |||
| String getAudioPluginDefines() const; | |||
| void writeUnityScriptFile() | |||
| { | |||
| auto unityScriptContents = replaceLineFeeds (BinaryData::UnityPluginGUIScript_cs_in, | |||
| projectLineFeed); | |||
| auto projectName = Project::addUnityPluginPrefixIfNecessary (project.getProjectNameString()); | |||
| unityScriptContents = unityScriptContents.replace ("${plugin_class_name}", projectName.replace (" ", "_")) | |||
| .replace ("${plugin_name}", projectName) | |||
| .replace ("${plugin_vendor}", project.getPluginManufacturerString()) | |||
| .replace ("${plugin_description}", project.getPluginDescriptionString()); | |||
| auto f = getGeneratedCodeFolder().getChildFile (project.getUnityScriptName()); | |||
| MemoryOutputStream out; | |||
| out << unityScriptContents; | |||
| replaceFileIfDifferent (f, out); | |||
| } | |||
| void writeProjects (const OwnedArray<LibraryModule>&, const String&, bool); | |||
| void runPostExportScript() | |||
| { | |||
| #if JUCE_WINDOWS | |||
| auto cmdString = project.getPostExportShellCommandWinString(); | |||
| #else | |||
| auto cmdString = project.getPostExportShellCommandPosixString(); | |||
| #endif | |||
| auto shellCommand = cmdString.replace ("%%1%%", project.getProjectFolder().getFullPathName()); | |||
| if (shellCommand.isNotEmpty()) | |||
| { | |||
| #if JUCE_WINDOWS | |||
| StringArray argList ("cmd.exe", "/c"); | |||
| #else | |||
| StringArray argList ("/bin/sh", "-c"); | |||
| #endif | |||
| argList.add (shellCommand); | |||
| ChildProcess shellProcess; | |||
| if (! shellProcess.start (argList)) | |||
| { | |||
| addError ("Failed to run shell command: " + argList.joinIntoString (" ")); | |||
| return; | |||
| } | |||
| if (! shellProcess.waitForProcessToFinish (10000)) | |||
| { | |||
| addError ("Timeout running shell command: " + argList.joinIntoString (" ")); | |||
| return; | |||
| } | |||
| auto exitCode = shellProcess.getExitCode(); | |||
| if (exitCode != 0) | |||
| addError ("Shell command: " + argList.joinIntoString (" ") + " failed with exit code: " + String (exitCode)); | |||
| } | |||
| } | |||
| void saveExporter (ProjectExporter* exporter, const OwnedArray<LibraryModule>& modules) | |||
| { | |||
| try | |||
| { | |||
| exporter->create (modules); | |||
| if (! exporter->isCLion()) | |||
| std::cout << "Finished saving: " << exporter->getName() << std::endl; | |||
| } | |||
| catch (build_tools::SaveError& error) | |||
| { | |||
| addError (error.message); | |||
| } | |||
| } | |||
| class ExporterJob : public ThreadPoolJob | |||
| { | |||
| public: | |||
| ExporterJob (ProjectSaver& ps, ProjectExporter* pe, | |||
| const OwnedArray<LibraryModule>& moduleList) | |||
| : ThreadPoolJob ("export"), | |||
| owner (ps), exporter (pe), modules (moduleList) | |||
| { | |||
| } | |||
| JobStatus runJob() override | |||
| { | |||
| owner.saveExporter (exporter.get(), modules); | |||
| return jobHasFinished; | |||
| } | |||
| private: | |||
| ProjectSaver& owner; | |||
| std::unique_ptr<ProjectExporter> exporter; | |||
| const OwnedArray<LibraryModule>& modules; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExporterJob) | |||
| }; | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSaver) | |||
| }; | |||
| @@ -350,18 +350,9 @@ void StoredSettings::checkJUCEPaths() | |||
| projectDefaults.getPropertyAsValue (Ids::defaultJuceModulePath, nullptr) = File (juceFolder).getChildFile ("modules").getFullPathName(); | |||
| } | |||
| bool StoredSettings::shouldAskUserToSetJUCEPath() noexcept | |||
| bool StoredSettings::isJUCEPathIncorrect() | |||
| { | |||
| if (! isGlobalPathValid ({}, Ids::jucePath, getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get().toString()) | |||
| && getGlobalProperties().getValue ("dontAskAboutJUCEPath", {}).isEmpty()) | |||
| return true; | |||
| return false; | |||
| } | |||
| void StoredSettings::setDontAskAboutJUCEPathAgain() noexcept | |||
| { | |||
| getGlobalProperties().setValue ("dontAskAboutJUCEPath", 1); | |||
| return ! isGlobalPathValid ({}, Ids::jucePath, getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get().toString()); | |||
| } | |||
| static String getFallbackPathForOS (const Identifier& key, DependencyPathOS os) | |||
| @@ -55,9 +55,7 @@ public: | |||
| //============================================================================== | |||
| ValueWithDefault getStoredPath (const Identifier& key, DependencyPathOS os); | |||
| bool shouldAskUserToSetJUCEPath() noexcept; | |||
| void setDontAskAboutJUCEPathAgain() noexcept; | |||
| bool isJUCEPathIncorrect(); | |||
| //============================================================================== | |||
| AppearanceSettings appearance; | |||
| @@ -23,7 +23,7 @@ | |||
| // Handy list of static Identifiers.. | |||
| namespace Ids | |||
| { | |||
| #define DECLARE_ID(name) const Identifier name (#name) | |||
| #define DECLARE_ID(name) const Identifier name (#name) | |||
| DECLARE_ID (name); | |||
| DECLARE_ID (file); | |||
| @@ -359,8 +359,11 @@ namespace Ids | |||
| DECLARE_ID (compilerFlagSchemes); | |||
| DECLARE_ID (compilerFlagScheme); | |||
| DECLARE_ID (dontQueryForUpdate); | |||
| DECLARE_ID (dontAskAboutJUCEPath); | |||
| DECLARE_ID (postExportShellCommandPosix); | |||
| DECLARE_ID (postExportShellCommandWin); | |||
| DECLARE_ID (liveBuildEnabled); | |||
| DECLARE_ID (guiEditorEnabled); | |||
| const Identifier ID ("id"); | |||
| const Identifier ID_uppercase ("ID"); | |||
| @@ -19,7 +19,6 @@ | |||
| #include "../../Application/jucer_Headers.h" | |||
| #include "../../ProjectSaving/jucer_ProjectExporter.h" | |||
| #include "jucer_PIPGenerator.h" | |||
| #include "../../Project/jucer_Module.h" | |||
| //============================================================================== | |||
| static void ensureSingleNewLineAfterIncludes (StringArray& lines) | |||
| @@ -105,7 +104,7 @@ PIPGenerator::PIPGenerator (const File& pip, const File& output, const File& juc | |||
| if (userModulesPath != File()) | |||
| { | |||
| availableUserModules.reset (new AvailableModuleList()); | |||
| availableUserModules.reset (new AvailableModulesList()); | |||
| availableUserModules->scanPaths ({ userModulesPath }); | |||
| } | |||
| } | |||
| @@ -357,6 +356,8 @@ Result PIPGenerator::setProjectSettings (ValueTree& jucerTree) | |||
| : "\"File->Global Paths...\"") | |||
| + " menu item."); | |||
| } | |||
| jucerTree.setProperty (Ids::displaySplashScreen, true, nullptr); | |||
| } | |||
| setPropertyIfNotEmpty (Ids::defines, defines); | |||
| @@ -19,6 +19,7 @@ | |||
| #pragma once | |||
| #include "../Helpers/jucer_MiscUtilities.h" | |||
| #include "../../Project/Modules/jucer_AvailableModulesList.h" | |||
| //============================================================================== | |||
| class PIPGenerator | |||
| @@ -70,13 +71,9 @@ private: | |||
| //============================================================================== | |||
| File pipFile, outputDirectory, juceModulesPath, userModulesPath; | |||
| std::unique_ptr<AvailableModuleList> availableUserModules; | |||
| std::unique_ptr<AvailableModulesList> availableUserModules; | |||
| var metadata; | |||
| bool isTemp = false; | |||
| bool useLocalCopy = false; | |||
| bool isTemp = false, useLocalCopy = false; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PIPGenerator) | |||
| }; | |||
| @@ -20,19 +20,52 @@ | |||
| //============================================================================== | |||
| struct IconButton : public Button | |||
| class IconButton : public Button | |||
| { | |||
| IconButton (String name, const Path* p) | |||
| : Button (name), | |||
| icon (p, Colours::transparentBlack) | |||
| public: | |||
| IconButton (String buttonName, Image imageToDisplay) | |||
| : Button (buttonName), | |||
| iconImage (imageToDisplay) | |||
| { | |||
| lookAndFeelChanged(); | |||
| setTooltip (name); | |||
| setTooltip (buttonName); | |||
| } | |||
| IconButton (String buttonName, Path pathToDisplay) | |||
| : Button (buttonName), | |||
| iconPath (pathToDisplay), | |||
| iconImage (createImageFromPath (iconPath)) | |||
| { | |||
| setTooltip (buttonName); | |||
| } | |||
| void setImage (Image newImage) | |||
| { | |||
| iconImage = newImage; | |||
| repaint(); | |||
| } | |||
| void setPath (Path newPath) | |||
| { | |||
| iconImage = createImageFromPath (newPath); | |||
| repaint(); | |||
| } | |||
| void setBackgroundColour (Colour backgroundColourToUse) | |||
| { | |||
| backgroundColour = backgroundColourToUse; | |||
| usingNonDefaultBackgroundColour = true; | |||
| } | |||
| void setIconInset (int newIconInset) | |||
| { | |||
| iconInset = newIconInset; | |||
| repaint(); | |||
| } | |||
| void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) override | |||
| { | |||
| auto alpha = 1.0f; | |||
| float alpha = 1.0f; | |||
| if (! isEnabled()) | |||
| { | |||
| isMouseOverButton = false; | |||
| @@ -41,43 +74,56 @@ struct IconButton : public Button | |||
| alpha = 0.2f; | |||
| } | |||
| auto backgroundColour = isIDEButton ? Colours::white | |||
| : isUserButton ? findColour (userButtonBackgroundColourId) | |||
| : findColour (defaultButtonBackgroundColourId); | |||
| auto fill = isButtonDown ? backgroundColour.darker (0.5f) | |||
| : isMouseOverButton ? backgroundColour.darker (0.2f) | |||
| : backgroundColour; | |||
| backgroundColour = isButtonDown ? backgroundColour.darker (0.5f) | |||
| : isMouseOverButton ? backgroundColour.darker (0.2f) | |||
| : backgroundColour; | |||
| auto bounds = getLocalBounds().toFloat(); | |||
| auto bounds = getLocalBounds(); | |||
| if (isButtonDown) | |||
| bounds.reduce (2, 2); | |||
| Path ellipse; | |||
| ellipse.addEllipse (bounds); | |||
| g.reduceClipRegion(ellipse); | |||
| ellipse.addEllipse (bounds.toFloat()); | |||
| g.reduceClipRegion (ellipse); | |||
| g.setColour (backgroundColour.withAlpha (alpha)); | |||
| g.setColour (fill.withAlpha (alpha)); | |||
| g.fillAll(); | |||
| if (iconImage != Image()) | |||
| { | |||
| if (isIDEButton) | |||
| bounds.reduce (7, 7); | |||
| g.setOpacity (alpha); | |||
| g.drawImage (iconImage, bounds.reduced (iconInset).toFloat(), RectanglePlacement::fillDestination, false); | |||
| } | |||
| g.setOpacity (alpha); | |||
| g.drawImage (iconImage, bounds, RectanglePlacement::fillDestination, false); | |||
| } | |||
| else | |||
| { | |||
| icon.withColour (findColour (defaultIconColourId).withAlpha (alpha)).draw (g, bounds.reduced (2, 2), false); | |||
| } | |||
| private: | |||
| void lookAndFeelChanged() override | |||
| { | |||
| if (! usingNonDefaultBackgroundColour) | |||
| backgroundColour = findColour (defaultButtonBackgroundColourId); | |||
| if (iconPath != Path()) | |||
| iconImage = createImageFromPath (iconPath); | |||
| repaint(); | |||
| } | |||
| Image createImageFromPath (Path path) | |||
| { | |||
| Image image (Image::ARGB, 250, 250, true); | |||
| Graphics g (image); | |||
| g.setColour (findColour (defaultIconColourId)); | |||
| g.fillPath (path, RectanglePlacement (RectanglePlacement::centred) | |||
| .getTransformToFit (path.getBounds(), image.getBounds().toFloat())); | |||
| return image; | |||
| } | |||
| Icon icon; | |||
| Path iconPath; | |||
| Image iconImage; | |||
| Colour backgroundColour { findColour (defaultButtonBackgroundColourId) }; | |||
| bool usingNonDefaultBackgroundColour = false; | |||
| int iconInset = 2; | |||
| bool isIDEButton = false; | |||
| bool isUserButton = false; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (IconButton) | |||
| }; | |||
| @@ -1515,29 +1515,6 @@ const uint8 clion[] = { 110,109,0,0,0,0,0,0,0,0,98,0,0,0,0,170,170,38,67,0,0,0,0 | |||
| 0,192,218,67,98,0,128,44,67,0,192,218,67,0,0,220,66,0,192,218,67,252,255,61,66,0,192,218,67,98,252,255,61,66,170,138,213,67,252,255,61,66,84,85,208,67,252,255,61,66,254,31,203,67,99,101,0,0 }; | |||
| } | |||
| /*static void convertSVGPathToCppData (const String& pathString) | |||
| { | |||
| XmlElement svg ("svg"); | |||
| XmlElement* path = svg.createNewChildElement ("path"); | |||
| path->setAttribute ("d", pathString); | |||
| std::unique_ptr<Drawable> d (Drawable::createFromSVG (svg)); | |||
| DrawablePath* dp = dynamic_cast<DrawablePath*> (d->getChildComponent(0)); | |||
| jassert (dp != nullptr); | |||
| Path p (dp->getPath()); | |||
| p.applyTransform (RectanglePlacement (RectanglePlacement::centred).getTransformToFit (p.getBounds(), | |||
| Rectangle<float> (500.0f, 500.0f))); | |||
| MemoryOutputStream data; | |||
| p.writePathToStream (data); | |||
| MemoryOutputStream out; | |||
| CodeHelpers::writeDataAsCppLiteral (data.getMemoryBlock(), out, false, true); | |||
| DBG (out.toString() << newLine); | |||
| }*/ | |||
| Icons::Icons() | |||
| { | |||
| #define JUCE_LOAD_PATH_DATA(name) \ | |||
| @@ -22,18 +22,22 @@ | |||
| //============================================================================== | |||
| struct Icon | |||
| { | |||
| Icon() : path (nullptr) {} | |||
| Icon (const Path& p, Colour c) : path (&p), colour (c) {} | |||
| Icon (const Path* p, Colour c) : path (p), colour (c) {} | |||
| Icon() = default; | |||
| Icon (const Path& pathToUse, Colour pathColour) | |||
| : path (pathToUse), | |||
| colour (pathColour) | |||
| { | |||
| } | |||
| void draw (Graphics& g, const juce::Rectangle<float>& area, bool isCrossedOut) const | |||
| { | |||
| if (path != nullptr) | |||
| if (! path.isEmpty()) | |||
| { | |||
| g.setColour (colour); | |||
| const RectanglePlacement placement (RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize); | |||
| g.fillPath (*path, placement.getTransformToFit (path->getBounds(), area)); | |||
| g.fillPath (path, placement.getTransformToFit (path.getBounds(), area)); | |||
| if (isCrossedOut) | |||
| { | |||
| @@ -54,7 +58,7 @@ struct Icon | |||
| return Icon (path, newColour); | |||
| } | |||
| const Path* path; | |||
| Path path; | |||
| Colour colour; | |||
| }; | |||
| @@ -19,6 +19,7 @@ | |||
| #include "../../Application/jucer_Headers.h" | |||
| #include "jucer_ProjucerLookAndFeel.h" | |||
| #include "../../Application/jucer_Application.h" | |||
| #include "../../Project/UI/jucer_ProjectContentComponent.h" | |||
| //============================================================================== | |||
| ProjucerLookAndFeel::ProjucerLookAndFeel() | |||
| @@ -40,24 +41,40 @@ void ProjucerLookAndFeel::drawTabButton (TabBarButton& button, Graphics& g, bool | |||
| const auto alpha = button.isEnabled() ? ((isMouseOver || isMouseDown) ? 1.0f : 0.8f) : 0.3f; | |||
| #ifndef BUILDING_JUCE_COMPILEENGINE | |||
| auto textColour = findColour (defaultTextColourId).withMultipliedAlpha (alpha); | |||
| auto iconColour = findColour (button.isFrontTab() ? activeTabIconColourId | |||
| : inactiveTabIconColourId); | |||
| if (button.getName() == "Project") | |||
| { | |||
| auto icon = Icon (getIcons().closedFolder, iconColour.withMultipliedAlpha (alpha)); | |||
| icon.draw (g, button.getTextArea().reduced (8, 8).toFloat(), false); | |||
| } | |||
| else if (button.getName() == "Build") | |||
| auto isProjectTab = button.getName() == ProjectContentComponent::getProjectTabName(); | |||
| auto isBuildTab = button.getName() == ProjectContentComponent::getBuildTabName(); | |||
| if (isProjectTab || isBuildTab) | |||
| { | |||
| auto icon = Icon (getIcons().buildTab, iconColour.withMultipliedAlpha (alpha)); | |||
| icon.draw (g, button.getTextArea().reduced (8, 8).toFloat(), false); | |||
| auto icon = Icon (isProjectTab ? getIcons().closedFolder : getIcons().buildTab, | |||
| iconColour.withMultipliedAlpha (alpha)); | |||
| auto isSingleTab = (button.getTabbedButtonBar().getNumTabs() == 1); | |||
| if (isSingleTab) | |||
| { | |||
| auto activeArea = button.getActiveArea().reduced (5); | |||
| activeArea.removeFromLeft (15); | |||
| icon.draw (g, activeArea.removeFromLeft (activeArea.getHeight()).toFloat(), false); | |||
| activeArea.removeFromLeft (10); | |||
| g.setColour (textColour); | |||
| g.drawFittedText (isProjectTab ? ProjectContentComponent::getProjectTabName() : ProjectContentComponent::getBuildTabName(), | |||
| activeArea, Justification::centredLeft, 1); | |||
| } | |||
| else | |||
| { | |||
| icon.draw (g, button.getTextArea().reduced (8, 8).toFloat(), false); | |||
| } | |||
| } | |||
| else | |||
| #endif | |||
| { | |||
| auto textColour = findColour (defaultTextColourId).withMultipliedAlpha (alpha); | |||
| TextLayout textLayout; | |||
| LookAndFeel_V3::createTabTextLayout (button, (float) area.getWidth(), (float) area.getHeight(), textColour, textLayout); | |||
| @@ -19,8 +19,6 @@ | |||
| #include "../Application/jucer_Headers.h" | |||
| #include "jucer_NewFileWizard.h" | |||
| NewFileWizard::Type* createGUIComponentWizard(); | |||
| //============================================================================== | |||
| namespace | |||
| { | |||
| @@ -232,7 +230,6 @@ NewFileWizard::NewFileWizard() | |||
| registerWizard (new NewCppAndHeaderFileWizard()); | |||
| registerWizard (new NewComponentFileWizard()); | |||
| registerWizard (new NewSingleFileComponentFileWizard()); | |||
| registerWizard (createGUIComponentWizard()); | |||
| } | |||
| NewFileWizard::~NewFileWizard() | |||
| @@ -122,11 +122,10 @@ struct NewProjectWizard | |||
| projectFile = targetFolder.getChildFile (File::createLegalFileName (appTitle)) | |||
| .withFileExtension (Project::projectFileExtension); | |||
| std::unique_ptr<Project> project (new Project (projectFile)); | |||
| auto project = std::make_unique<Project> (projectFile); | |||
| if (failedFiles.size() == 0) | |||
| { | |||
| project->setFile (projectFile); | |||
| project->setTitle (appTitle); | |||
| if (! initialiseProject (*project)) | |||
| @@ -136,6 +135,9 @@ struct NewProjectWizard | |||
| project->getProjectValue (Ids::useAppConfig) = false; | |||
| project->getProjectValue (Ids::addUsingNamespaceToJuceHeader) = false; | |||
| if (! ProjucerApplication::getApp().getLicenseController().getCurrentState().isPaidOrGPL()) | |||
| project->getProjectValue (Ids::displaySplashScreen) = true; | |||
| addExporters (*project, wc); | |||
| addDefaultModules (*project, useGlobalPath); | |||
| @@ -174,7 +176,7 @@ struct NewProjectWizard | |||
| { | |||
| auto defaultModules = getDefaultModules(); | |||
| AvailableModuleList list; | |||
| AvailableModulesList list; | |||
| list.scanPaths ({ modulesFolder }); | |||
| for (auto& mod : list.getAllModules()) | |||