From fba0295a44dac47ca4d5eda8bb7eea4f4753019b Mon Sep 17 00:00:00 2001 From: ed Date: Fri, 17 Apr 2020 10:02:21 +0100 Subject: [PATCH] Projucer: Added a simple sign-in form, added notification tray for project messages, general refactoring --- extras/Projucer/Builds/LinuxMakefile/Makefile | 12 +- .../MacOSX/Projucer.xcodeproj/project.pbxproj | 132 ++- .../VisualStudio2015/Projucer_App.vcxproj | 14 +- .../Projucer_App.vcxproj.filters | 42 +- .../VisualStudio2017/Projucer_App.vcxproj | 14 +- .../Projucer_App.vcxproj.filters | 42 +- .../VisualStudio2019/Projucer_App.vcxproj | 14 +- .../Projucer_App.vcxproj.filters | 42 +- extras/Projucer/CMakeLists.txt | 5 +- .../Projucer/JuceLibraryCode/BinaryData.cpp | 181 ++-- extras/Projucer/JuceLibraryCode/BinaryData.h | 4 +- extras/Projucer/Projucer.jucer | 29 +- .../UserAccount/jucer_LicenseController.h | 194 ++++ .../UserAccount/jucer_LicenseQueryThread.h | 274 ++++++ .../UserAccount/jucer_LicenseState.h | 68 ++ .../UserAccount/jucer_LoginFormComponent.h | 273 ++++++ .../Windows/jucer_AboutWindowComponent.h | 18 +- .../jucer_GlobalPathsWindowComponent.h | 39 +- .../Windows/jucer_PIPCreatorWindowComponent.h | 1 + .../Source/Application/jucer_Application.cpp | 431 ++++++--- .../Source/Application/jucer_Application.h | 164 ++-- .../Source/Application/jucer_AutoUpdater.cpp | 58 +- .../Source/Application/jucer_AutoUpdater.h | 7 +- .../Source/Application/jucer_CommandIDs.h | 15 +- .../Source/Application/jucer_CommandLine.cpp | 8 +- .../Source/Application/jucer_Main.cpp | 1 + .../Source/Application/jucer_MainWindow.cpp | 198 ++-- .../Source/Application/jucer_MainWindow.h | 26 +- .../Source/BinaryData/Icons/gpl_logo.svg | 23 + .../BinaryData/Icons/huckleberry_icon.svg | 50 - .../jucer_DocumentEditorComponent.cpp | 2 +- .../CodeEditor/jucer_OpenDocumentManager.cpp | 12 +- .../CodeEditor/jucer_OpenDocumentManager.h | 12 +- .../UI/jucer_JucerDocumentEditor.cpp | 8 +- .../ComponentEditor/jucer_JucerDocument.cpp | 15 +- .../jucer_ClientServerMessages.h | 5 +- .../jucer_CompileEngineClient.cpp | 6 +- .../Source/LiveBuildEngine/jucer_MessageIDs.h | 7 +- .../Modules/jucer_AvailableModulesList.h | 195 ++++ .../Project/Modules/jucer_ModuleDescription.h | 86 ++ .../jucer_Modules.cpp} | 308 ++----- .../jucer_Modules.h} | 89 +- .../UI/Sidebar/jucer_ExporterTreeItems.h | 9 +- .../Project/UI/Sidebar/jucer_FileTreeItems.h | 4 +- .../Project/UI/Sidebar/jucer_LiveBuildTab.h | 13 +- .../UI/Sidebar/jucer_ModuleTreeItems.h | 148 ++- .../Project/UI/Sidebar/jucer_TabComponents.h | 14 +- .../Project/UI/jucer_HeaderComponent.cpp | 228 +++-- .../Source/Project/UI/jucer_HeaderComponent.h | 36 +- .../UI/jucer_ModulesInformationComponent.h | 6 +- .../UI/jucer_ProjectContentComponent.cpp | 256 +++--- .../UI/jucer_ProjectContentComponent.h | 80 +- .../UI/jucer_ProjectMessagesComponent.h | 544 +++++++++++ .../Project/UI/jucer_UserAvatarComponent.h | 123 +++ .../Projucer/Source/Project/jucer_Project.cpp | 518 +++++++++-- .../Projucer/Source/Project/jucer_Project.h | 170 +++- .../ProjectSaving/jucer_ProjectSaver.cpp | 752 ++++++++++++++- .../Source/ProjectSaving/jucer_ProjectSaver.h | 858 ++---------------- .../Source/Settings/jucer_StoredSettings.cpp | 13 +- .../Source/Settings/jucer_StoredSettings.h | 4 +- .../Source/Utility/Helpers/jucer_PresetIDs.h | 5 +- .../Utility/PIPs/jucer_PIPGenerator.cpp | 5 +- .../Source/Utility/PIPs/jucer_PIPGenerator.h | 9 +- .../Source/Utility/UI/jucer_IconButton.h | 114 ++- .../Source/Utility/UI/jucer_Icons.cpp | 23 - .../Projucer/Source/Utility/UI/jucer_Icons.h | 16 +- .../Utility/UI/jucer_ProjucerLookAndFeel.cpp | 37 +- .../Source/Wizards/jucer_NewFileWizard.cpp | 3 - .../Source/Wizards/jucer_NewProjectWizard.h | 8 +- 69 files changed, 4915 insertions(+), 2205 deletions(-) create mode 100644 extras/Projucer/Source/Application/UserAccount/jucer_LicenseController.h create mode 100644 extras/Projucer/Source/Application/UserAccount/jucer_LicenseQueryThread.h create mode 100644 extras/Projucer/Source/Application/UserAccount/jucer_LicenseState.h create mode 100644 extras/Projucer/Source/Application/UserAccount/jucer_LoginFormComponent.h create mode 100644 extras/Projucer/Source/BinaryData/Icons/gpl_logo.svg delete mode 100644 extras/Projucer/Source/BinaryData/Icons/huckleberry_icon.svg create mode 100644 extras/Projucer/Source/Project/Modules/jucer_AvailableModulesList.h create mode 100644 extras/Projucer/Source/Project/Modules/jucer_ModuleDescription.h rename extras/Projucer/Source/Project/{jucer_Module.cpp => Modules/jucer_Modules.cpp} (68%) rename extras/Projucer/Source/Project/{jucer_Module.h => Modules/jucer_Modules.h} (61%) create mode 100644 extras/Projucer/Source/Project/UI/jucer_ProjectMessagesComponent.h create mode 100644 extras/Projucer/Source/Project/UI/jucer_UserAvatarComponent.h diff --git a/extras/Projucer/Builds/LinuxMakefile/Makefile b/extras/Projucer/Builds/LinuxMakefile/Makefile index 58ffd868f8..41d2002475 100644 --- a/extras/Projucer/Builds/LinuxMakefile/Makefile +++ b/extras/Projucer/Builds/LinuxMakefile/Makefile @@ -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,16 +302,16 @@ $(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_Modules_e20cbd10.o: ../../Source/Project/Modules/jucer_Modules.cpp + -$(V_AT)mkdir -p $(JUCE_OBJDIR) + @echo "Compiling jucer_Modules.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 -$(V_AT)mkdir -p $(JUCE_OBJDIR) @echo "Compiling jucer_HeaderComponent.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 - -$(V_AT)mkdir -p $(JUCE_OBJDIR) - @echo "Compiling jucer_Module.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 -$(V_AT)mkdir -p $(JUCE_OBJDIR) @echo "Compiling jucer_Project.cpp" diff --git a/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj b/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj index addf6942fb..0925c5e951 100644 --- a/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj +++ b/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj @@ -205,14 +205,14 @@ isa = PBXBuildFile; fileRef = ADA538034910F52FDD2DC88D; }; + 0E783907C6214ADD59EC95DC = { + isa = PBXBuildFile; + fileRef = F58B23995765C9FDBE28F871; + }; 05A08E366EBF8D650974E695 = { isa = PBXBuildFile; fileRef = 516D6D7C564DD5DF5C15CB06; }; - 3FCA61C401007B243E2E9035 = { - isa = PBXBuildFile; - fileRef = F797071D88542C813CF7222A; - }; 30B921C38DCEE787B294B746 = { isa = PBXBuildFile; fileRef = BAC43B20E14A340CCF14119C; @@ -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 = ""; + }; 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 = ""; }; + 5108FDF7F62E617332FB13B0 = { + isa = PBXGroup; + children = ( + FDABEE6B64546586368A4729, + F9A363BFBB6B1143C2E967C3, + F58B23995765C9FDBE28F871, + 582F659B801F656C2B7C51B1, + ); + name = Modules; + sourceTree = ""; + }; 236D186F5A6536C59D6E751C = { isa = PBXGroup; children = ( @@ -2891,6 +2970,8 @@ B3528C08B84CBC950252EA69, 1B0F18E1D96F727C062B05FA, 92A66A8BD87F98EB6B4FB6D0, + F63F46CA0A51C679867855A7, + 247768B490B9D759DDA79359, ); name = UI; sourceTree = ""; @@ -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, diff --git a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj index d91166dfe4..4261d77d16 100644 --- a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj @@ -214,11 +214,11 @@ + true - @@ -1483,6 +1483,10 @@ + + + + @@ -1603,6 +1607,9 @@ + + + @@ -1616,7 +1623,8 @@ - + + @@ -2105,7 +2113,7 @@ - + diff --git a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters index 952bd75d1e..97ce38359d 100644 --- a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters @@ -2,6 +2,9 @@ + + {DA27985D-8427-CE70-CA06-EAF7009CCC60} + {DC7E18A5-E854-3D99-627F-AAA88246B712} @@ -47,6 +50,9 @@ {0A3B9446-F50B-3D4E-230F-7ED493541A07} + + {F5C79836-30DE-9DC7-9392-DAAB3F04C18E} + {A0A94AE6-B447-151A-D0DA-FAE9B5410EBF} @@ -448,15 +454,15 @@ Projucer\LiveBuildEngine + + Projucer\Project\Modules + Projucer\Project\UI Projucer\Project\UI - - Projucer\Project - Projucer\Project @@ -1857,6 +1863,18 @@ + + Projucer\Application\UserAccount + + + Projucer\Application\UserAccount + + + Projucer\Application\UserAccount + + + Projucer\Application\UserAccount + Projucer\Application\Windows @@ -2217,6 +2235,15 @@ Projucer\LiveBuildEngine + + Projucer\Project\Modules + + + Projucer\Project\Modules + + + Projucer\Project\Modules + Projucer\Project\UI\Sidebar @@ -2256,8 +2283,11 @@ Projucer\Project\UI - - Projucer\Project + + Projucer\Project\UI + + + Projucer\Project\UI Projucer\Project @@ -3719,7 +3749,7 @@ Projucer\BinaryData\Icons - + Projucer\BinaryData\Icons diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj index 05406b579f..d805b54f56 100644 --- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj @@ -214,11 +214,11 @@ + true - @@ -1483,6 +1483,10 @@ + + + + @@ -1603,6 +1607,9 @@ + + + @@ -1616,7 +1623,8 @@ - + + @@ -2105,7 +2113,7 @@ - + diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters index 79ac66daad..4ff591457d 100644 --- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters @@ -2,6 +2,9 @@ + + {DA27985D-8427-CE70-CA06-EAF7009CCC60} + {DC7E18A5-E854-3D99-627F-AAA88246B712} @@ -47,6 +50,9 @@ {0A3B9446-F50B-3D4E-230F-7ED493541A07} + + {F5C79836-30DE-9DC7-9392-DAAB3F04C18E} + {A0A94AE6-B447-151A-D0DA-FAE9B5410EBF} @@ -448,15 +454,15 @@ Projucer\LiveBuildEngine + + Projucer\Project\Modules + Projucer\Project\UI Projucer\Project\UI - - Projucer\Project - Projucer\Project @@ -1857,6 +1863,18 @@ + + Projucer\Application\UserAccount + + + Projucer\Application\UserAccount + + + Projucer\Application\UserAccount + + + Projucer\Application\UserAccount + Projucer\Application\Windows @@ -2217,6 +2235,15 @@ Projucer\LiveBuildEngine + + Projucer\Project\Modules + + + Projucer\Project\Modules + + + Projucer\Project\Modules + Projucer\Project\UI\Sidebar @@ -2256,8 +2283,11 @@ Projucer\Project\UI - - Projucer\Project + + Projucer\Project\UI + + + Projucer\Project\UI Projucer\Project @@ -3719,7 +3749,7 @@ Projucer\BinaryData\Icons - + Projucer\BinaryData\Icons diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj index d83bc1df57..f31f3e70ce 100644 --- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj @@ -214,11 +214,11 @@ + true - @@ -1483,6 +1483,10 @@ + + + + @@ -1603,6 +1607,9 @@ + + + @@ -1616,7 +1623,8 @@ - + + @@ -2105,7 +2113,7 @@ - + diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters index c343122ded..694adb5e80 100644 --- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters @@ -2,6 +2,9 @@ + + {DA27985D-8427-CE70-CA06-EAF7009CCC60} + {DC7E18A5-E854-3D99-627F-AAA88246B712} @@ -47,6 +50,9 @@ {0A3B9446-F50B-3D4E-230F-7ED493541A07} + + {F5C79836-30DE-9DC7-9392-DAAB3F04C18E} + {A0A94AE6-B447-151A-D0DA-FAE9B5410EBF} @@ -448,15 +454,15 @@ Projucer\LiveBuildEngine + + Projucer\Project\Modules + Projucer\Project\UI Projucer\Project\UI - - Projucer\Project - Projucer\Project @@ -1857,6 +1863,18 @@ + + Projucer\Application\UserAccount + + + Projucer\Application\UserAccount + + + Projucer\Application\UserAccount + + + Projucer\Application\UserAccount + Projucer\Application\Windows @@ -2217,6 +2235,15 @@ Projucer\LiveBuildEngine + + Projucer\Project\Modules + + + Projucer\Project\Modules + + + Projucer\Project\Modules + Projucer\Project\UI\Sidebar @@ -2256,8 +2283,11 @@ Projucer\Project\UI - - Projucer\Project + + Projucer\Project\UI + + + Projucer\Project\UI Projucer\Project @@ -3719,7 +3749,7 @@ Projucer\BinaryData\Icons - + Projucer\BinaryData\Icons diff --git a/extras/Projucer/CMakeLists.txt b/extras/Projucer/CMakeLists.txt index 23945f395f..5134eb8e1a 100644 --- a/extras/Projucer/CMakeLists.txt +++ b/extras/Projucer/CMakeLists.txt @@ -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 diff --git a/extras/Projucer/JuceLibraryCode/BinaryData.cpp b/extras/Projucer/JuceLibraryCode/BinaryData.cpp index e50b16e285..dfb37cfd04 100644 --- a/extras/Projucer/JuceLibraryCode/BinaryData.cpp +++ b/extras/Projucer/JuceLibraryCode/BinaryData.cpp @@ -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[] = -"\n" -"\n" -"\n" -"\n" -"\n" -"\t\n" -"\t\t\n" -"\t\t\n" -"\t\t\n" -"\t\t\n" -"\t\n" -"\t\n" -"\t\t\n" -"\t\t\n" -"\t\t\n" -"\t\t\n" -"\t\t\n" -"\t\t\n" -"\t\t\n" -"\t\n" -"\t\n" -"\t\t\n" -"\t\t\n" -"\t\t\n" -"\t\t\n" -"\t\t\n" -"\t\t\n" -"\t\t\n" -"\t\t\n" -"\t\t\n" -"\t\t\n" -"\t\t\n" -"\t\n" -"\n" -"\n"; +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +""; -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", diff --git a/extras/Projucer/JuceLibraryCode/BinaryData.h b/extras/Projucer/JuceLibraryCode/BinaryData.h index 61341f7411..87f0621859 100644 --- a/extras/Projucer/JuceLibraryCode/BinaryData.h +++ b/extras/Projucer/JuceLibraryCode/BinaryData.h @@ -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; diff --git a/extras/Projucer/Projucer.jucer b/extras/Projucer/Projucer.jucer index 0783694017..4031215ba4 100644 --- a/extras/Projucer/Projucer.jucer +++ b/extras/Projucer/Projucer.jucer @@ -101,6 +101,16 @@ + + + + + + @@ -177,8 +187,7 @@ file="Source/BinaryData/Icons/export_visualStudio.svg"/> - + @@ -523,6 +532,15 @@ file="Source/LiveBuildEngine/jucer_SourceCodeRange.h"/> + + + + + + + + - - diff --git a/extras/Projucer/Source/Application/UserAccount/jucer_LicenseController.h b/extras/Projucer/Source/Application/UserAccount/jucer_LicenseController.h new file mode 100644 index 0000000000..892de29b6e --- /dev/null +++ b/extras/Projucer/Source/Application/UserAccount/jucer_LicenseController.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 stateListeners; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LicenseController) +}; diff --git a/extras/Projucer/Source/Application/UserAccount/jucer_LicenseQueryThread.h b/extras/Projucer/Source/Application/UserAccount/jucer_LicenseQueryThread.h new file mode 100644 index 0000000000..c36ae49a42 --- /dev/null +++ b/extras/Projucer/Source/Application/UserAccount/jucer_LicenseQueryThread.h @@ -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&& 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 (email, password), state); + + if (errorMessage.isEmpty()) + errorMessage = runJob (std::make_unique (state.authToken), state); + + if (errorMessage.isNotEmpty()) + state = {}; + + WeakReference 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 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 completionCallback; + + //============================================================================== + JUCE_DECLARE_WEAK_REFERENCEABLE (LicenseQueryThread) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LicenseQueryThread) +}; diff --git a/extras/Projucer/Source/Application/UserAccount/jucer_LicenseState.h b/extras/Projucer/Source/Application/UserAccount/jucer_LicenseState.h new file mode 100644 index 0000000000..eb47fae42a --- /dev/null +++ b/extras/Projucer/Source/Application/UserAccount/jucer_LicenseState.h @@ -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; +}; diff --git a/extras/Projucer/Source/Application/UserAccount/jucer_LoginFormComponent.h b/extras/Projucer/Source/Application/UserAccount/jucer_LoginFormComponent.h new file mode 100644 index 0000000000..0b710567bf --- /dev/null +++ b/extras/Projucer/Source/Application/UserAccount/jucer_LoginFormComponent.h @@ -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 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; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LoginFormComponent) +}; diff --git a/extras/Projucer/Source/Application/Windows/jucer_AboutWindowComponent.h b/extras/Projucer/Source/Application/Windows/jucer_AboutWindowComponent.h index 54413aa21b..9184ba8d9e 100644 --- a/extras/Projucer/Source/Application/Windows/jucer_AboutWindowComponent.h +++ b/extras/Projucer/Source/Application/Windows/jucer_AboutWindowComponent.h @@ -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 huckleberryLogoBounds, juceLogoBounds; + Rectangle juceLogoBounds; std::unique_ptr juceLogo { Drawable::createFromImageData (BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize) }; - std::unique_ptr huckleberryLogo { Drawable::createFromImageData (BinaryData::huckleberry_icon_svg, - BinaryData::huckleberry_icon_svgSize) }; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AboutWindowComponent) }; diff --git a/extras/Projucer/Source/Application/Windows/jucer_GlobalPathsWindowComponent.h b/extras/Projucer/Source/Application/Windows/jucer_GlobalPathsWindowComponent.h index d7aabcf1be..d97b0d2eb0 100644 --- a/extras/Projucer/Source/Application/Windows/jucer_GlobalPathsWindowComponent.h +++ b/extras/Projucer/Source/Application/Windows/jucer_GlobalPathsWindowComponent.h @@ -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,15 +245,11 @@ 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" }; diff --git a/extras/Projucer/Source/Application/Windows/jucer_PIPCreatorWindowComponent.h b/extras/Projucer/Source/Application/Windows/jucer_PIPCreatorWindowComponent.h index beec548102..ad2f4671d6 100644 --- a/extras/Projucer/Source/Application/Windows/jucer_PIPCreatorWindowComponent.h +++ b/extras/Projucer/Source/Application/Windows/jucer_PIPCreatorWindowComponent.h @@ -18,6 +18,7 @@ #pragma once + //============================================================================== static String getWidthLimitedStringFromVarArray (const var& varArray) noexcept { diff --git a/extras/Projucer/Source/Application/jucer_Application.cpp b/extras/Projucer/Source/Application/jucer_Application.cpp index 7f11637dcd..be01864d2a 100644 --- a/extras/Projucer/Source/Application/jucer_Application.cpp +++ b/extras/Projucer/Source/Application/jucer_Application.cpp @@ -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(); + LookAndFeel::setDefaultLookAndFeel (&lookAndFeel); + rescanJUCEPathModules(); + rescanUserPathModules(); + + openDocumentManager.registerType (new ProjucerAppClasses::LiveBuildCodeEditorDocument::Type(), 2); + childProcessCache.reset (new ChildProcessCache()); + + initCommandManager(); + menuModel.reset (new MainMenuModel()); + + #if JUCE_MAC + rebuildAppleMenu(); + appleMenuRebuildListener = std::make_unique(); + #endif + + settings->appearance.refreshPresetSchemeList(); + setColourScheme (getGlobalProperties().getIntValue ("COLOUR SCHEME"), false); + setEditorColourScheme (getGlobalProperties().getIntValue ("EDITOR COLOUR SCHEME"), false); + updateEditorColourSchemeIfNeeded(); + ImageCache::setCacheTimeout (30 * 1000); icons = std::make_unique(); - tooltipWindow = std::make_unique (nullptr, 1200); - #if JUCE_MAC - PopupMenu extraAppleMenuItems; - createExtraAppleMenuItems (extraAppleMenuItems); - - // 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 - - if (getGlobalProperties().getValue (Ids::dontQueryForUpdate, {}).isEmpty()) - LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (false); + if (isAutomaticVersionCheckingEnabled()) + LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (true); initialiseWindows (getCommandLineParameters()); - - if (! isRunningCommandLine && settings->shouldAskUserToSetJUCEPath()) - showSetJUCEPathAlert(); } 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; - - 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 ("Light") - .setTicked (selectedColourSchemeIndex == 2) - .setAction ([this] { setColourScheme (2, true); updateEditorColourSchemeIfNeeded(); })); - - menu.addSubMenu ("Colour Scheme", colourSchemeMenu); - - //============================================================================== - PopupMenu editorColourSchemeMenu; - - auto& appearanceSettings = getAppSettings().appearance; - - appearanceSettings.refreshPresetSchemeList(); - auto schemes = appearanceSettings.getPresetSchemes(); - - 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; + PopupMenu colourSchemeMenu; + + 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 ("Light") + .setTicked (selectedColourSchemeIndex == 2) + .setAction ([this] { setColourScheme (2, true); updateEditorColourSchemeIfNeeded(); })); + + menu.addSubMenu ("Colour Scheme", colourSchemeMenu); } - editorColourSchemeMenu.addSeparator(); - editorColourSchemeMenu.addItem (PopupMenu::Item ("Create...") - .setEnabled (editorColourSchemeWindow == nullptr) - .setAction ([this] { showEditorColourSchemeWindow(); })); + { + PopupMenu editorColourSchemeMenu; - menu.addSubMenu ("Editor Colour Scheme", editorColourSchemeMenu); + auto& appearanceSettings = getAppSettings().appearance; + + appearanceSettings.refreshPresetSchemeList(); + auto schemes = appearanceSettings.getPresetSchemes(); + + 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; + } + + editorColourSchemeMenu.addSeparator(); + editorColourSchemeMenu.addItem (PopupMenu::Item ("Create...") + .setEnabled (editorColourSchemeWindow == nullptr) + .setAction ([this] { showEditorColourSchemeWindow(); })); + + 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 & 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& paths, bool async) +void rescanModules (AvailableModulesList& list, const Array& paths, bool async) { if (async) list.scanPathsAsync (paths); @@ -1338,12 +1449,32 @@ void rescanModules (AvailableModuleList& list, const Array& 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)"); diff --git a/extras/Projucer/Source/Application/jucer_Application.h b/extras/Projucer/Source/Application/jucer_Application.h index d992fce682..1863f7c4b7 100644 --- a/extras/Projucer/Source/Application/jucer_Application.h +++ b/extras/Projucer/Source/Application/jucer_Application.h @@ -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&) override; void getCommandInfo (CommandID commandID, ApplicationCommandInfo&) override; bool perform (const InvocationInfo&) override; + bool isLiveBuildEnabled() const; + bool isGUIEditorEnabled() const; + //============================================================================== - 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); - - //============================================================================== - void showUTF8ToolWindow(); - void showSVGPathDataToolWindow(); - - void showAboutWindow(); 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 commandManager; - std::unique_ptr utf8Window, svgPathWindow, aboutWindow, pathsWindow, - editorColourSchemeWindow, pipCreatorWindow; - - std::unique_ptr logger; - - bool isRunningCommandLine; + bool isRunningCommandLine = false; std::unique_ptr 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 getSortedExampleDirectories() noexcept; Array 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(); + //============================================================================== + #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; + #endif + + //============================================================================== + std::unique_ptr licenseController; + void* server = nullptr; - std::unique_ptr tooltipWindow; + AvailableModulesList jucePathModulesList, userPathsModulesList; - AvailableModuleList jucePathModuleList, userPathsModuleList; + std::unique_ptr utf8Window, svgPathWindow, aboutWindow, pathsWindow, + editorColourSchemeWindow, pipCreatorWindow; + + std::unique_ptr logger; int numExamples = 0; std::unique_ptr demoRunnerAlert; - std::unique_ptr pathAlert; bool hasScannedForDemoRunnerExecutable = false, hasScannedForDemoRunnerProject = false; File lastJUCEPath, lastDemoRunnerExectuableFile, lastDemoRunnerProjectFile; #if JUCE_LINUX diff --git a/extras/Projucer/Source/Application/jucer_AutoUpdater.cpp b/extras/Projucer/Source/Application/jucer_AutoUpdater.cpp index c0f5a563c3..8a14d478d1 100644 --- a/extras/Projucer/Source/Application/jucer_AutoUpdater.cpp +++ b/extras/Projucer/Source/Application/jucer_AutoUpdater.cpp @@ -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 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) } }); + } + } } //============================================================================== diff --git a/extras/Projucer/Source/Application/jucer_AutoUpdater.h b/extras/Projucer/Source/Application/jucer_AutoUpdater.h index c7c60badf2..950afead44 100644 --- a/extras/Projucer/Source/Application/jucer_AutoUpdater.h +++ b/extras/Projucer/Source/Application/jucer_AutoUpdater.h @@ -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 installer; std::unique_ptr dialogWindow; diff --git a/extras/Projucer/Source/Application/jucer_CommandIDs.h b/extras/Projucer/Source/Application/jucer_CommandIDs.h index 694731dc0f..3fc1a1c936 100644 --- a/extras/Projucer/Source/Application/jucer_CommandIDs.h +++ b/extras/Projucer/Source/Application/jucer_CommandIDs.h @@ -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 }; diff --git a/extras/Projucer/Source/Application/jucer_CommandLine.cpp b/extras/Projucer/Source/Application/jucer_CommandLine.cpp index 2b282e432c..9089950e30 100644 --- a/extras/Projucer/Source/Application/jucer_CommandLine.cpp +++ b/extras/Projucer/Source/Application/jucer_CommandLine.cpp @@ -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); } } diff --git a/extras/Projucer/Source/Application/jucer_Main.cpp b/extras/Projucer/Source/Application/jucer_Main.cpp index 7a5a215870..875970eca8 100644 --- a/extras/Projucer/Source/Application/jucer_Main.cpp +++ b/extras/Projucer/Source/Application/jucer_Main.cpp @@ -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" diff --git a/extras/Projucer/Source/Application/jucer_MainWindow.cpp b/extras/Projucer/Source/Application/jucer_MainWindow.cpp index 3216350965..d9b2aeab44 100644 --- a/extras/Projucer/Source/Application/jucer_MainWindow.cpp +++ b/extras/Projucer/Source/Application/jucer_MainWindow.cpp @@ -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 comp) + : ComponentMovementWatcher (&window), + mainWindow (window), + componentToShow (std::move (comp)) + { + kernel.createGaussianBlur (1.25f); + + addAndMakeVisible (*componentToShow); + + setAlwaysOnTop (true); + setOpaque (true); + setVisible (true); + + static_cast (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 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 newProject) @@ -174,7 +257,7 @@ void MainWindow::setProject (std::unique_ptr 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 (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 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 (*this, std::make_unique (*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 = [&] diff --git a/extras/Projucer/Source/Application/jucer_MainWindow.h b/extras/Projucer/Source/Application/jucer_MainWindow.h index f8ae1a76a3..ec1c02462d 100644 --- a/extras/Projucer/Source/Application/jucer_MainWindow.h +++ b/extras/Projucer/Source/Application/jucer_MainWindow.h @@ -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 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 currentProject; + Value projectNameValue; + + std::unique_ptr 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(); diff --git a/extras/Projucer/Source/BinaryData/Icons/gpl_logo.svg b/extras/Projucer/Source/BinaryData/Icons/gpl_logo.svg new file mode 100644 index 0000000000..fe205244bc --- /dev/null +++ b/extras/Projucer/Source/BinaryData/Icons/gpl_logo.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extras/Projucer/Source/BinaryData/Icons/huckleberry_icon.svg b/extras/Projucer/Source/BinaryData/Icons/huckleberry_icon.svg deleted file mode 100644 index e0b66e7dc4..0000000000 --- a/extras/Projucer/Source/BinaryData/Icons/huckleberry_icon.svg +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/extras/Projucer/Source/CodeEditor/jucer_DocumentEditorComponent.cpp b/extras/Projucer/Source/CodeEditor/jucer_DocumentEditorComponent.cpp index 6538db9748..cc6cdeae0d 100644 --- a/extras/Projucer/Source/CodeEditor/jucer_DocumentEditorComponent.cpp +++ b/extras/Projucer/Source/CodeEditor/jucer_DocumentEditorComponent.cpp @@ -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) diff --git a/extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.cpp b/extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.cpp index 17cd7089d0..50aac0319b 100644 --- a/extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.cpp +++ b/extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.cpp @@ -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]) diff --git a/extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.h b/extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.h index 953784eecc..ccb7d2fc6c 100644 --- a/extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.h +++ b/extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.h @@ -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); diff --git a/extras/Projucer/Source/ComponentEditor/UI/jucer_JucerDocumentEditor.cpp b/extras/Projucer/Source/ComponentEditor/UI/jucer_JucerDocumentEditor.cpp index d1e29b211c..9419d8ec83 100644 --- a/extras/Projucer/Source/ComponentEditor/UI/jucer_JucerDocumentEditor.cpp +++ b/extras/Projucer/Source/ComponentEditor/UI/jucer_JucerDocumentEditor.cpp @@ -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); diff --git a/extras/Projucer/Source/ComponentEditor/jucer_JucerDocument.cpp b/extras/Projucer/Source/ComponentEditor/jucer_JucerDocument.cpp index e8ab6a17fb..627501e0a1 100644 --- a/extras/Projucer/Source/ComponentEditor/jucer_JucerDocument.cpp +++ b/extras/Projucer/Source/ComponentEditor/jucer_JucerDocument.cpp @@ -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 jucerDoc (JucerDocument::createForCppFile (getProject(), getFile())); + if (ProjucerApplication::getApp().isGUIEditorEnabled()) + { + std::unique_ptr 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); diff --git a/extras/Projucer/Source/LiveBuildEngine/jucer_ClientServerMessages.h b/extras/Projucer/Source/LiveBuildEngine/jucer_ClientServerMessages.h index af5ff53687..630863d96b 100644 --- a/extras/Projucer/Source/LiveBuildEngine/jucer_ClientServerMessages.h +++ b/extras/Projucer/Source/LiveBuildEngine/jucer_ClientServerMessages.h @@ -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; } diff --git a/extras/Projucer/Source/LiveBuildEngine/jucer_CompileEngineClient.cpp b/extras/Projucer/Source/LiveBuildEngine/jucer_CompileEngineClient.cpp index 134a0fcc82..ae3287c448 100644 --- a/extras/Projucer/Source/LiveBuildEngine/jucer_CompileEngineClient.cpp +++ b/extras/Projucer/Source/LiveBuildEngine/jucer_CompileEngineClient.cpp @@ -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() diff --git a/extras/Projucer/Source/LiveBuildEngine/jucer_MessageIDs.h b/extras/Projucer/Source/LiveBuildEngine/jucer_MessageIDs.h index 236667f857..20a5400f13 100644 --- a/extras/Projucer/Source/LiveBuildEngine/jucer_MessageIDs.h +++ b/extras/Projucer/Source/LiveBuildEngine/jucer_MessageIDs.h @@ -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 +} diff --git a/extras/Projucer/Source/Project/Modules/jucer_AvailableModulesList.h b/extras/Projucer/Source/Project/Modules/jucer_AvailableModulesList.h new file mode 100644 index 0000000000..d88be81a75 --- /dev/null +++ b/extras/Projucer/Source/Project/Modules/jucer_AvailableModulesList.h @@ -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; + using ModuleIDAndFolderList = std::vector; + + AvailableModulesList() = default; + + //============================================================================== + void scanPaths (const Array& paths) + { + auto job = createScannerJob (paths); + auto& ref = *job; + + removePendingAndAddJob (std::move (job)); + scanPool.waitForJobToFinish (&ref, -1); + } + + void scanPathsAsync (const Array& 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& paths, + std::function&& 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 pathsToScan; + std::function completionCallback; + }; + + //============================================================================== + std::unique_ptr createScannerJob (const Array& paths) + { + return std::make_unique (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 jobToAdd) + { + scanPool.removeAllJobs (false, 100); + scanPool.addJob (jobToAdd.release(), true); + } + + //============================================================================== + ThreadPool scanPool { 1 }; + + ModuleIDAndFolderList modulesList; + ListenerList listeners; + CriticalSection lock; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AvailableModulesList) +}; diff --git a/extras/Projucer/Source/Project/Modules/jucer_ModuleDescription.h b/extras/Projucer/Source/Project/Modules/jucer_ModuleDescription.h new file mode 100644 index 0000000000..5002f201cc --- /dev/null +++ b/extras/Projucer/Source/Project/Modules/jucer_ModuleDescription.h @@ -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; +}; diff --git a/extras/Projucer/Source/Project/jucer_Module.cpp b/extras/Projucer/Source/Project/Modules/jucer_Modules.cpp similarity index 68% rename from extras/Projucer/Source/Project/jucer_Module.cpp rename to extras/Projucer/Source/Project/Modules/jucer_Modules.cpp index b9f29eb636..a46c482974 100644 --- a/extras/Projucer/Source/Project/jucer_Module.cpp +++ b/extras/Projucer/Source/Project/Modules/jucer_Modules.cpp @@ -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& paths, - std::function&& 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 pathsToScan; - std::function completionCallback; -}; - -ThreadPoolJob* AvailableModuleList::createScannerJob (const Array& 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& paths) -{ - auto* job = createScannerJob (paths); - - removePendingAndAddJob (job); - scanPool.waitForJobToFinish (job, -1); -} - -void AvailableModuleList::scanPathsAsync (const Array& 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& modules) +void EnabledModulesList::createRequiredModules (OwnedArray& 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) diff --git a/extras/Projucer/Source/Project/jucer_Module.h b/extras/Projucer/Source/Project/Modules/jucer_Modules.h similarity index 61% rename from extras/Projucer/Source/Project/jucer_Module.h rename to extras/Projucer/Source/Project/Modules/jucer_Modules.h index d841bceb12..357b225471 100644 --- a/extras/Projucer/Source/Project/jucer_Module.h +++ b/extras/Projucer/Source/Project/Modules/jucer_Modules.h @@ -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; - using ModuleIDAndFolderList = std::vector; - - AvailableModuleList() = default; - - void scanPaths (const Array&); - void scanPathsAsync (const Array&); - - 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&); - void removePendingAndAddJob (ThreadPoolJob*); - - ThreadPool scanPool { 1 }; - - ModuleIDAndFolderList moduleList; - ListenerList 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) }; diff --git a/extras/Projucer/Source/Project/UI/Sidebar/jucer_ExporterTreeItems.h b/extras/Projucer/Source/Project/UI/Sidebar/jucer_ExporterTreeItems.h index 731c9e7146..cc97d2f8df 100644 --- a/extras/Projucer/Source/Project/UI/Sidebar/jucer_ExporterTreeItems.h +++ b/extras/Projucer/Source/Project/UI/Sidebar/jucer_ExporterTreeItems.h @@ -108,18 +108,11 @@ public: void handlePopupMenuResult (int resultCode) override { if (resultCode == 1) - { exporter->addNewConfiguration (false); - } else if (resultCode == 2) - { - const ScopedValueSetter valueSetter (project.specifiedExporterToSave, exporter->getName(), {}); - project.save (true, true); - } + project.saveProject (exporter.get()); else if (resultCode == 3) - { deleteAllSelectedItems(); - } } var getDragSourceDescription() override diff --git a/extras/Projucer/Source/Project/UI/Sidebar/jucer_FileTreeItems.h b/extras/Projucer/Source/Project/UI/Sidebar/jucer_FileTreeItems.h index b853b8805d..460867627f 100644 --- a/extras/Projucer/Source/Project/UI/Sidebar/jucer_FileTreeItems.h +++ b/extras/Projucer/Source/Project/UI/Sidebar/jucer_FileTreeItems.h @@ -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(); } } diff --git a/extras/Projucer/Source/Project/UI/Sidebar/jucer_LiveBuildTab.h b/extras/Projucer/Source/Project/UI/Sidebar/jucer_LiveBuildTab.h index f1f3691da2..1a06f27319 100644 --- a/extras/Projucer/Source/Project/UI/Sidebar/jucer_LiveBuildTab.h +++ b/extras/Projucer/Source/Project/UI/Sidebar/jucer_LiveBuildTab.h @@ -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()) 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 headers; ConcertinaPanel concertinaPanel; - std::unique_ptr settingsButton; + IconButton settingsButton { "Settings", getIcons().settings }; std::unique_ptr downloadButton, enableButton; std::unique_ptr