diff --git a/.github/workflows/build-osx.yml b/.github/workflows/build-osx.yml index eaec902c0..8c97a7d7d 100644 --- a/.github/workflows/build-osx.yml +++ b/.github/workflows/build-osx.yml @@ -267,7 +267,7 @@ jobs: APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_APPSPEC_PASS: ${{ secrets.APPLE_APPSPEC_PASS }} APPLE_DIST_STORE: true - APPLE_UPLOAD_STORE: startsWith(github.ref, 'refs/tags/1.') || startsWith(github.ref, 'refs/tags/2.') + APPLE_UPLOAD_STORE: ${{ startsWith(github.ref, 'refs/tags/1.') || startsWith(github.ref, 'refs/tags/2.') }} run: make dist # Upload diff --git a/.github/workflows/build-win.yml b/.github/workflows/build-win.yml index ea51cdd7a..30535aa54 100644 --- a/.github/workflows/build-win.yml +++ b/.github/workflows/build-win.yml @@ -142,6 +142,10 @@ jobs: env: BRANDING: ${{ matrix.branding }} steps: + + # Install WIX + - name: Install WIX + run: dotnet tool install --global wix --version 4.0.1 # Checkout - name: Checkout @@ -213,13 +217,13 @@ jobs: run: | fsutil volume diskfree d: - # Upload ZIP - - name: Upload ZIP + # Upload MSI + - name: Upload MSI uses: actions/upload-artifact@v3 with: - name: Packages ZIP (${{ matrix.os }}-${{ matrix.branding }}) + name: Packages MSI (${{ matrix.os }}-${{ matrix.branding }}) path: | - ./dist/win-10/*.zip + ./dist/win-10/*.msi # Upload Packages - name: Upload App @@ -236,4 +240,4 @@ jobs: if: startsWith(github.ref, 'refs/tags/') && (matrix.branding == 'neuromore') with: token: ${{ secrets.PAT_GITHUB_ACTIONS }} - files: ./dist/win-10/*.zip + files: ./dist/win-10/*.msi diff --git a/build/make/Engine.mk b/build/make/Engine.mk index d7fcb5bbf..ca19b9e71 100644 --- a/build/make/Engine.mk +++ b/build/make/Engine.mk @@ -410,9 +410,9 @@ build: pch $(OBJS) $(OBJSPRIV) $(AR) $(ARFLAGS) $(LIBDIR)/$(NAME)$(SUFFIX)$(EXTLIB) $(OBJS) $(OBJSPRIV) clean: - $(call deletefiles,$(OBJDIR),*.o) - $(call deletefiles,$(OBJDIR),*.op) - $(call deletefiles,$(OBJDIR),$(PCH).pch) - $(call deletefiles,$(LIBDIR),$(NAME)$(SUFFIX)$(EXTLIB)) + -$(call deletefiles,$(OBJDIR),*.o) + -$(call deletefiles,$(OBJDIR),*.op) + -$(call deletefiles,$(OBJDIR),$(PCH).pch) + -$(call deletefiles,$(LIBDIR),$(NAME)$(SUFFIX)$(EXTLIB)) .DEFAULT_GOAL := build diff --git a/build/make/EngineJNI.mk b/build/make/EngineJNI.mk index c99201075..5b800e53b 100644 --- a/build/make/EngineJNI.mk +++ b/build/make/EngineJNI.mk @@ -192,8 +192,8 @@ build: $(OBJDIR)/neuromoreEngine.o $(OBJS) $(JOBJS) $(JAR) cf $(LIBDIR)/$(NAME)$(SUFFIX).jar -C $(OBJDIR)/Java . clean: - $(call deletefiles,$(OBJDIR),*.o) - $(call deletefiles,$(OBJDIR),*.class) - $(call deletefiles,$(LIBDIR),$(NAME)$(SUFFIX)$(EXTDLL)) - $(call deletefiles,$(LIBDIR),$(NAME)$(SUFFIX)$(EXTPDB)) - $(call deletefiles,$(LIBDIR),$(NAME)$(SUFFIX).jar) + -$(call deletefiles,$(OBJDIR),*.o) + -$(call deletefiles,$(OBJDIR),*.class) + -$(call deletefiles,$(LIBDIR),$(NAME)$(SUFFIX)$(EXTDLL)) + -$(call deletefiles,$(LIBDIR),$(NAME)$(SUFFIX)$(EXTPDB)) + -$(call deletefiles,$(LIBDIR),$(NAME)$(SUFFIX).jar) diff --git a/build/make/EngineLIB.mk b/build/make/EngineLIB.mk index ae799986a..4609ed249 100644 --- a/build/make/EngineLIB.mk +++ b/build/make/EngineLIB.mk @@ -175,6 +175,6 @@ build: $(OBJDIR)/neuromoreEngine.o $(OBJS) $(LINK) $(LINKFLAGS) $(LINKPATH) $(OBJDIR)/neuromoreEngine.o $(OBJS) $(LINKLIBS) -o $(LIBDIR)/$(NAME)$(SUFFIX)$(EXTDLL) clean: - $(call deletefiles,$(OBJDIR),*.o) - $(call deletefiles,$(LIBDIR),$(NAME)$(SUFFIX)$(EXTDLL)) - $(call deletefiles,$(LIBDIR),$(NAME)$(SUFFIX)$(EXTPDB)) + -$(call deletefiles,$(OBJDIR),*.o) + -$(call deletefiles,$(LIBDIR),$(NAME)$(SUFFIX)$(EXTDLL)) + -$(call deletefiles,$(LIBDIR),$(NAME)$(SUFFIX)$(EXTPDB)) diff --git a/build/make/QtBase.mk b/build/make/QtBase.mk index 8ddfef5cd..ef649c20a 100644 --- a/build/make/QtBase.mk +++ b/build/make/QtBase.mk @@ -538,19 +538,19 @@ build: pch $(PRES) $(OBLS) $(AR) $(ARFLAGS) $(LIBDIR)/$(NAME)$(SUFFIX)$(EXTLIB) $(OBLS) clean: - $(call deletefiles,$(MOCDIR),*.cpp) - $(call deletefiles,$(MOCDIR),*.moc) - $(call deletefiles,$(MOCDIR),*.mocmm) - $(call deletefiles,$(RCCDIR),*.cpp) - $(call deletefiles,$(RCCDIR),*.cppp) - $(call deletefiles,$(UICDIR),*.h) - $(call deletefiles,$(OBJDIR),$(PCH).pch) - $(call deletefiles,$(OBJDIR),*.o) - $(call deletefiles,$(OBJDIR),*.op) - $(call deletefiles,$(OBJDIR),*.oc) - $(call deletefiles,$(OBJDIR),*.ocp) - $(call deletefiles,$(OBJDIR),*.omm) - $(call deletefiles,$(OBJDIR),*.ommp) - $(call deletefiles,$(OBJDIR),*.omoc) - $(call deletefiles,$(OBJDIR),*.orcc) - $(call deletefiles,$(LIBDIR),$(NAME)$(SUFFIX)$(EXTLIB)) + -$(call deletefiles,$(MOCDIR),*.cpp) + -$(call deletefiles,$(MOCDIR),*.moc) + -$(call deletefiles,$(MOCDIR),*.mocmm) + -$(call deletefiles,$(RCCDIR),*.cpp) + -$(call deletefiles,$(RCCDIR),*.cppp) + -$(call deletefiles,$(UICDIR),*.h) + -$(call deletefiles,$(OBJDIR),$(PCH).pch) + -$(call deletefiles,$(OBJDIR),*.o) + -$(call deletefiles,$(OBJDIR),*.op) + -$(call deletefiles,$(OBJDIR),*.oc) + -$(call deletefiles,$(OBJDIR),*.ocp) + -$(call deletefiles,$(OBJDIR),*.omm) + -$(call deletefiles,$(OBJDIR),*.ommp) + -$(call deletefiles,$(OBJDIR),*.omoc) + -$(call deletefiles,$(OBJDIR),*.orcc) + -$(call deletefiles,$(LIBDIR),$(NAME)$(SUFFIX)$(EXTLIB)) diff --git a/build/make/Studio.mk b/build/make/Studio.mk index 66095933d..a21bd3eb6 100644 --- a/build/make/Studio.mk +++ b/build/make/Studio.mk @@ -393,6 +393,18 @@ DEFINES := $(DEFINES) \ OBJSPRIV := $(OBJSPRIV) RCCHPRIV := $(RCCHPRIV) RCCO := $(RCCO) +ifeq ($(TARGET_ARCH),x86) +APPGUIDPLAT = ba60729d-dace-4746-9c78-829d9b943f67 +endif +ifeq ($(TARGET_ARCH),x64) +APPGUIDPLAT = 2bcdb126-dc80-42ba-985e-48836ce23193 +endif +ifeq ($(TARGET_ARCH),arm) +APPGUIDPLAT = 9e9ffa9f-f21a-4125-bb87-bff0d2866af7 +endif +ifeq ($(TARGET_ARCH),arm64) +APPGUIDPLAT = badb6403-11c5-4dd4-8964-9fda8a470314 +endif else include ../../priv/build/make/StudioBranding.mk endif @@ -796,27 +808,27 @@ build: pch $(PRES) $(OBLS) $(RESO) @-$(call copyfiles,$(BINDIRDEP)/*$(EXTDLL),$(BINDIR)) clean: - $(call deletefiles,$(MOCDIR),*.cpp) - $(call deletefiles,$(MOCDIR),*.moc) - $(call deletefiles,$(MOCDIR),*.mocmm) - $(call deletefiles,$(RCCDIR),*.cpp) - $(call deletefiles,$(RCCDIR),*.cppp) - $(call deletefiles,$(UICDIR),*.h) - $(call deletefiles,$(OBJDIR),$(PCH).pch) - $(call deletefiles,$(OBJDIR),*.o) - $(call deletefiles,$(OBJDIR),*.op) - $(call deletefiles,$(OBJDIR),*.oc) - $(call deletefiles,$(OBJDIR),*.ocp) - $(call deletefiles,$(OBJDIR),*.omm) - $(call deletefiles,$(OBJDIR),*.ommp) - $(call deletefiles,$(OBJDIR),*.omoc) - $(call deletefiles,$(OBJDIR),*.orcc) - $(call deletefiles,$(OBJDIR),*.res) - $(call deletefiles,$(LIBDIR),$(NAME)$(SUFFIX)$(EXTLIB)) - $(call deletefiles,$(BINDIR),$(NAME)$(SUFFIX)$(EXTLIB)) - $(call deletefiles,$(BINDIR),$(NAME)$(SUFFIX)$(EXTBIN)) - $(call deletefiles,$(BINDIR),$(NAME)$(SUFFIX)$(EXTPDB)) - $(call deletefiles,$(BINDIR),*.$(EXTDLL)) + -$(call deletefiles,$(MOCDIR),*.cpp) + -$(call deletefiles,$(MOCDIR),*.moc) + -$(call deletefiles,$(MOCDIR),*.mocmm) + -$(call deletefiles,$(RCCDIR),*.cpp) + -$(call deletefiles,$(RCCDIR),*.cppp) + -$(call deletefiles,$(UICDIR),*.h) + -$(call deletefiles,$(OBJDIR),$(PCH).pch) + -$(call deletefiles,$(OBJDIR),*.o) + -$(call deletefiles,$(OBJDIR),*.op) + -$(call deletefiles,$(OBJDIR),*.oc) + -$(call deletefiles,$(OBJDIR),*.ocp) + -$(call deletefiles,$(OBJDIR),*.omm) + -$(call deletefiles,$(OBJDIR),*.ommp) + -$(call deletefiles,$(OBJDIR),*.omoc) + -$(call deletefiles,$(OBJDIR),*.orcc) + -$(call deletefiles,$(OBJDIR),*.res) + -$(call deletefiles,$(LIBDIR),$(NAME)$(SUFFIX)$(EXTLIB)) + -$(call deletefiles,$(BINDIR),$(NAME)$(SUFFIX)$(EXTLIB)) + -$(call deletefiles,$(BINDIR),$(NAME)$(SUFFIX)$(EXTBIN)) + -$(call deletefiles,$(BINDIR),$(NAME)$(SUFFIX)$(EXTPDB)) + -$(call deletefiles,$(BINDIR),*.$(EXTDLL)) ################################################################################################ diff --git a/deps/build/make/platforms/dist.mk b/deps/build/make/platforms/dist.mk index 90ff888ce..2344d8a46 100644 --- a/deps/build/make/platforms/dist.mk +++ b/deps/build/make/platforms/dist.mk @@ -65,18 +65,21 @@ dist-prep: chcp 1252 echo [PUB] $(PUBLISHER) echo [PFX] $(SIGN_PFX_FILE) + echo [RMD] $(DISTDIR)/$(NAME) + -$(call rmdir,$(DISTDIR)/$(NAME)) + -$(call deletefiles,$(DISTDIR),$(NAME)*.zip) + -$(call deletefiles,$(DISTDIR),$(NAME)*.msi) + -$(call deletefiles,$(DISTDIR),$(NAME)*.wixpdb) + -$(call deletefiles,$(DISTDIR),$(NAME)*.appx) + -$(call deletefiles,$(DISTDIR),$(NAME)*.appxbundle) + -$(call deletefiles,$(DISTDIR),$(NAME)*.appxupload) echo [MKD] $(DISTDIR)/$(NAME) - $(call rmdir,$(DISTDIR)/$(NAME)) - $(call deletefiles,$(DISTDIR),$(NAME)*.zip) - $(call deletefiles,$(DISTDIR),$(NAME)*.appx) - $(call deletefiles,$(DISTDIR),$(NAME)*.appxbundle) - $(call deletefiles,$(DISTDIR),$(NAME)*.appxupload) - $(call mkdir,$(DISTDIR)/$(NAME)) - $(call mkdir,$(DISTDIR)/$(NAME)/resources) - $(call mkdir,$(DISTDIR)/$(NAME)/upload) - $(call mkdir,$(DISTDIR)/$(NAME)/x64) - $(call mkdir,$(DISTDIR)/$(NAME)/x86) - $(call mkdir,$(DISTDIR)/$(NAME)/arm64) + -$(call mkdir,$(DISTDIR)/$(NAME)) + -$(call mkdir,$(DISTDIR)/$(NAME)/resources) + -$(call mkdir,$(DISTDIR)/$(NAME)/upload) + -$(call mkdir,$(DISTDIR)/$(NAME)/x64) + -$(call mkdir,$(DISTDIR)/$(NAME)/x86) + -$(call mkdir,$(DISTDIR)/$(NAME)/arm64) $(call copyfiles,$(DISTDIR)/$(NAME).appxmanifest,$(DISTDIR)/$(NAME)/AppxManifest.xml) $(call replace,$(DISTDIR)/$(NAME)/AppxManifest.xml,{PUBLISHER},$(PUBLISHER),$(DISTDIR)/$(NAME)/AppxManifest.xml) $(call replace,$(DISTDIR)/$(NAME)/AppxManifest.xml,{PUBLISHERID},$(PUBLISHERID),$(DISTDIR)/$(NAME)/AppxManifest.xml) @@ -91,37 +94,38 @@ dist-prep: $(call copyfiles,$(APPICON)-44x44.png,$(DISTDIR)/$(NAME)/resources/app-44x44.png) $(call copyfiles,$(APPICON)-50x50.png,$(DISTDIR)/$(NAME)/resources/app-50x50.png) $(call copyfiles,$(APPICON)-150x150.png,$(DISTDIR)/$(NAME)/resources/app-150x150.png) + $(call copyfiles,$(APPICON).ico,$(DISTDIR)/$(NAME)/resources/app.ico) dist-vis-%: dist-prep echo [VIS] $* $(call mkdir,$(DISTDIR)/$(NAME)/x64/Visualizations/$*/) $(call copyfiles,$(DISTDIR)/../../visualizations/$*/Info.json,$(DISTDIR)/$(NAME)/x64/Visualizations/$*/) $(call copyfiles,$(DISTDIR)/../../visualizations/$*/Thumbnail.png,$(DISTDIR)/$(NAME)/x64/Visualizations/$*/) - $(call copyfilesrecursive,$(DISTDIR)/../../visualizations/$*/win-x64/*,$(DISTDIR)/$(NAME)/x64/Visualizations/$*/) + -$(call copyfilesrecursive,$(DISTDIR)/../../visualizations/$*/win-x64/*,$(DISTDIR)/$(NAME)/x64/Visualizations/$*/) $(call sleep,3) ifeq ($(SIGN_PFX_PASS),) - $(call sign,$(DISTDIR)/$(NAME)/x64/Visualizations/$*/$*.exe,$(SIGN_PFX_FILE)) + -$(call sign,$(DISTDIR)/$(NAME)/x64/Visualizations/$*/$*.exe,$(SIGN_PFX_FILE)) else - $(call signp,$(DISTDIR)/$(NAME)/x64/Visualizations/$*/$*.exe,$(SIGN_PFX_FILE),$(SIGN_PFX_PASS)) + -$(call signp,$(DISTDIR)/$(NAME)/x64/Visualizations/$*/$*.exe,$(SIGN_PFX_FILE),$(SIGN_PFX_PASS)) endif $(call mkdir,$(DISTDIR)/$(NAME)/x86/Visualizations/$*/) $(call copyfiles,$(DISTDIR)/../../visualizations/$*/Info.json,$(DISTDIR)/$(NAME)/x86/Visualizations/$*/) $(call copyfiles,$(DISTDIR)/../../visualizations/$*/Thumbnail.png,$(DISTDIR)/$(NAME)/x86/Visualizations/$*/) - $(call copyfilesrecursive,$(DISTDIR)/../../visualizations/$*/win-x86/*,$(DISTDIR)/$(NAME)/x86/Visualizations/$*/) + -$(call copyfilesrecursive,$(DISTDIR)/../../visualizations/$*/win-x86/*,$(DISTDIR)/$(NAME)/x86/Visualizations/$*/) $(call sleep,3) ifeq ($(SIGN_PFX_PASS),) - $(call sign,$(DISTDIR)/$(NAME)/x86/Visualizations/$*/$*.exe,$(SIGN_PFX_FILE)) + -$(call sign,$(DISTDIR)/$(NAME)/x86/Visualizations/$*/$*.exe,$(SIGN_PFX_FILE)) else - $(call signp,$(DISTDIR)/$(NAME)/x86/Visualizations/$*/$*.exe,$(SIGN_PFX_FILE),$(SIGN_PFX_PASS)) + -$(call signp,$(DISTDIR)/$(NAME)/x86/Visualizations/$*/$*.exe,$(SIGN_PFX_FILE),$(SIGN_PFX_PASS)) endif $(call mkdir,$(DISTDIR)/$(NAME)/arm64/Visualizations/$*/) $(call copyfiles,$(DISTDIR)/../../visualizations/$*/Info.json,$(DISTDIR)/$(NAME)/arm64/Visualizations/$*/) $(call copyfiles,$(DISTDIR)/../../visualizations/$*/Thumbnail.png,$(DISTDIR)/$(NAME)/arm64/Visualizations/$*/) - $(call copyfilesrecursive,$(DISTDIR)/../../visualizations/$*/win-arm64/*,$(DISTDIR)/$(NAME)/arm64/Visualizations/$*/) + -$(call copyfilesrecursive,$(DISTDIR)/../../visualizations/$*/win-arm64/*,$(DISTDIR)/$(NAME)/arm64/Visualizations/$*/) $(call sleep,3) ifeq ($(SIGN_PFX_PASS),) - $(call sign,$(DISTDIR)/$(NAME)/arm64/Visualizations/$*/$*.exe,$(SIGN_PFX_FILE)) + -$(call sign,$(DISTDIR)/$(NAME)/arm64/Visualizations/$*/$*.exe,$(SIGN_PFX_FILE)) else - $(call signp,$(DISTDIR)/$(NAME)/arm64/Visualizations/$*/$*.exe,$(SIGN_PFX_FILE),$(SIGN_PFX_PASS)) + -$(call signp,$(DISTDIR)/$(NAME)/arm64/Visualizations/$*/$*.exe,$(SIGN_PFX_FILE),$(SIGN_PFX_PASS)) endif dist-dll-x64: dist-prep echo [DLL] Copy X64 DLL @@ -142,7 +146,6 @@ dist-dll-arm64: dist-prep echo [DLL] Copy ARM64 DLL dist-bin-%: dist-prep dist-dll-% echo [BIN] $(DISTDIR)/$(NAME)/$* - $(call mkdir,$(DISTDIR)/$(NAME)/$*) $(call copyfiles,./bin/win-$*/$(NAME)$(EXTBIN),$(DISTDIR)/$(NAME)/$*/$(NAME)$(EXTBIN)) $(call copyfiles,./bin/win-$*/$(NAME)$(EXTPDB),$(DISTDIR)/$(NAME)/$*/$(NAME)$(EXTPDB)) $(call sleep,3) @@ -157,8 +160,26 @@ endif echo [SYM] $(DISTDIR)/$(NAME)/upload/$(NAME)-$*.appxsym $(ZIPPER) $(DISTDIR)/$(NAME)/upload/$(NAME)-$*.appxsym.zip $(DISTDIR)/$(NAME)/$*/$(NAME)$(EXTPDB) $(call move,$(DISTDIR)/$(NAME)/upload/$(NAME)-$*.appxsym.zip,$(DISTDIR)/$(NAME)/upload/$(NAME)-$*.appxsym) - echo [ZIP] $(DISTDIR)/$(NAME)-$(VERSION3)-win-10-$*.zip - $(ZIPPER) $(DISTDIR)/$(NAME)-$(VERSION3)-win-10-$*.zip $(DISTDIR)/$(NAME)/$*/* + echo [MSI] $(DISTDIR)/$(NAME)-$(VERSION3)-win-10-$*.msi + powershell -command "& { \ + Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass; \ + . $(DISTDIR)/Harvest.ps1 -in $(DISTDIR)/$(NAME).wxs -out $(DISTDIR)/$(NAME)/MSI.$*.wxs -path $(DISTDIR)/$(NAME)/$*; }" + wix extension add WixToolset.Firewall.wixext + wix build -arch $* \ + -ext WixToolset.Firewall.wixext \ + -d APPNAME="$(APPNAME)" \ + -d APPSHORTNAME="$(APPSHORTNAME)" \ + -d APPGUIDPLAT="$(APPGUIDPLAT)" \ + -d APPCOMPANY="$(APPCOMPANY)" \ + -d VERSION=$(VERSION4) \ + -b $(DISTDIR)/$(NAME) \ + $(DISTDIR)/$(NAME)/MSI.$*.wxs \ + -out $(DISTDIR)/$(NAME)-$(VERSION3)-win-10-$*.msi +ifeq ($(SIGN_PFX_PASS),) + $(call sign,$(DISTDIR)/$(NAME)-$(VERSION3)-win-10-$*.msi,$(SIGN_PFX_FILE)) +else + $(call signp,$(DISTDIR)/$(NAME)-$(VERSION3)-win-10-$*.msi,$(SIGN_PFX_FILE),$(SIGN_PFX_PASS)) +endif dist: dist-prep dist-vis dist-bin-x64 dist-bin-x86 dist-bin-arm64 echo [BDL] $(DISTDIR)/$(NAME)-$(VERSION3)-win-10.appxbundle $(call makepkg,$(DISTDIR)/$(NAME)/Layout.xml,$(DISTDIR)) diff --git a/deps/build/make/platforms/win-all.mk b/deps/build/make/platforms/win-all.mk index 2c13cb043..f065435bd 100644 --- a/deps/build/make/platforms/win-all.mk +++ b/deps/build/make/platforms/win-all.mk @@ -2,27 +2,27 @@ # Delete Files in Folder by Pattern define deletefiles - cmd.exe /C "del /s /q $(subst /,\,$(1))\$(2) >nul 2>&1" & exit 0 + powershell "Remove-Item $(1)/$(2) -Force" endef # Copy Files between Folders by Pattern define copyfiles - cmd.exe /C "copy /Y $(subst /,\,$(1)) $(subst /,\,$(2)) >nul 2>&1" & exit 0 + powershell "Copy-Item $(1) -Destination $(2) -Force" endef # Copy Files recursively define copyfilesrecursive - cmd.exe /C "xcopy /Y /E /H $(subst /,\,$(1)) $(subst /,\,$(2)) >nul 2>&1" & exit 0 + powershell "Copy-Item $(1) -Destination $(2) -Force -Recurse" endef # Recursively remove folder -define rmdir - cmd.exe /C "if exist $(subst /,\,$(1)) rd /s /q $(subst /,\,$(1)) >nul 2>&1" +define rmdir + powershell "Remove-Item $(1) -Force -Recurse" endef # Create folder and all sub folders define mkdir - cmd.exe /C "if not exist $(subst /,\,$(1)) mkdir $(subst /,\,$(1)) >nul 2>&1" + powershell "New-Item $(1) -ItemType Directory -Force" endef # Replace string occurrences in file @@ -32,40 +32,40 @@ endef # Create APPX unencrypted define makepkg - cmd.exe /C "MakeAppx.exe build /o /h SHA256 /f $(subst /,\,$(1)) /op $(subst /,\,$(2))" + MakeAppx.exe build /o /h SHA256 /f $(subst /,\,$(1)) /op $(subst /,\,$(2)) endef # Create APPXBUNDLE define makebundle - cmd.exe /C "MakeAppx.exe bundle /o /d $(subst /,\,$(1)) /p $(subst /,\,$(2)) >nul 2>&1" + MakeAppx.exe bundle /o /d $(subst /,\,$(1)) /p $(subst /,\,$(2)) endef # Sign File with pfx without password define sign - cmd.exe /C "if exist $(subst /,\,$(1)) SignTool.exe sign /a /fd SHA256 /td SHA256 /tr http://timestamp.digicert.com /f $(subst /,\,$(2)) $(subst /,\,$(1)) >nul 2>&1" + SignTool.exe sign /a /fd SHA256 /td SHA256 /tr http://timestamp.digicert.com /f $(subst /,\,$(2)) $(subst /,\,$(1)) endef # Sign File with pfx with password define signp - cmd.exe /C "if exist $(subst /,\,$(1)) SignTool.exe sign /a /fd SHA256 /td SHA256 /tr http://timestamp.digicert.com /f $(subst /,\,$(2)) /p $(3) $(subst /,\,$(1))" + SignTool.exe sign /a /fd SHA256 /td SHA256 /tr http://timestamp.digicert.com /f $(subst /,\,$(2)) /p $(3) $(subst /,\,$(1)) endef # Create .pri resources define makepri - cmd.exe /C "MakePri.exe new /v /o /cf $(subst /,\,$(1)) /pr $(subst /,\,$(2)) /of $(subst /,\,$(3)) /IndexName $(4)" + MakePri.exe new /v /o /cf $(subst /,\,$(1)) /pr $(subst /,\,$(2)) /of $(subst /,\,$(3)) /IndexName $(4) endef # Create ZIP define makezip - cmd.exe /C "powershell Compress-Archive -Force $(1) $(2)" + powershell Compress-Archive -Force $(1) $(2) endef # Move Folder or File define move - cmd.exe /C "powershell Move-Item -Force -Path $(1) -Destination $(2)" + powershell Move-Item -Force -Path $(1) -Destination $(2) endef # Sleep n seconds define sleep - cmd.exe /C "powershell Start-Sleep -Seconds $(1)" + powershell Start-Sleep -Seconds $(1) endef diff --git a/dist/win-10/.gitignore b/dist/win-10/.gitignore index 4ca5d88c6..17a6b692d 100644 --- a/dist/win-10/.gitignore +++ b/dist/win-10/.gitignore @@ -5,3 +5,5 @@ *.appxbundle *.appxupload *.appx +*.msi +*.wixpdb diff --git a/dist/win-10/Harvest.ps1 b/dist/win-10/Harvest.ps1 new file mode 100644 index 000000000..0fe35672f --- /dev/null +++ b/dist/win-10/Harvest.ps1 @@ -0,0 +1,82 @@ +param ( + [Parameter(Mandatory)] [System.String] $in, + [Parameter(Mandatory)] [System.String] $out, + [Parameter(Mandatory)] [System.String] $path +) + +# Load WXS +$xml = [xml](Get-Content -Path $in) +$featurenode = $xml.Wix.Package.Feature + +############################################################################################### + +function process +{ + [CmdletBinding()] + param( + [Parameter()] + [System.String] $path, + [System.Xml.XmlNode] $dirnode, + [System.Boolean] $rootfolder + ) + + $newguid = [guid]::NewGuid() + $newcompid = ("-" + $newguid).Replace('-', '_') + + $newcomponent = $xml.CreateElement("Component", "http://wixtoolset.org/schemas/v4/wxs") + $newcomponent.SetAttribute("Id", $newcompid) + $newcomponent.SetAttribute("Guid", $newguid) + + $contents = (Get-ChildItem -Path $path) + foreach ($c in $contents) + { + $plainname = (Split-Path $c -leaf) + if ($c -Is [System.IO.DirectoryInfo]) + { + $newfolder = $xml.CreateElement("Directory", "http://wixtoolset.org/schemas/v4/wxs") + $newfolder.SetAttribute("Name", $plainname) + $dirnode.AppendChild($newfolder) | Out-Null + + process $c.FullName $newfolder 0 + } + else + { + $newfile = $xml.CreateElement("File", "http://wixtoolset.org/schemas/v4/wxs") +# $newfile.SetAttribute("Source", (Resolve-Path -Relative $c.FullName)) + $newfile.SetAttribute("Source", $c.FullName) + + $ext = [System.IO.Path]::GetExtension($c) + if ($ext -eq ".exe") { + if ($rootfolder) { + $newfile.SetAttribute("Id", $plainname) + } + $newrule = $xml.CreateElement( + "fire:FirewallException", + "http://wixtoolset.org/schemas/v4/wxs/firewall") + $newrule.SetAttribute("Name", $plainname) + $newrule.SetAttribute("Profile", "private") + $newrule.SetAttribute("Scope", "any") + $newfile.AppendChild($newrule) | Out-Null + } + $newcomponent.AppendChild($newfile) | Out-Null + } + } + if ($newcomponent.ChildNodes.Count) + { + $dirnode.AppendChild($newcomponent) | Out-Null + $newcompref = $xml.CreateElement("ComponentRef", "http://wixtoolset.org/schemas/v4/wxs") + $newcompref.SetAttribute("Id", $newcompid) + $featurenode.AppendChild($newcompref) | Out-Null + } +} + +############################################################################################### + +$node = $xml.Wix.Package.StandardDirectory | where {$_.Id -eq 'ProgramFiles6432Folder'} +$node = $node.Directory | where {$_.Id -eq 'INSTALLFOLDER'} + +# Modify WXS +process $path $node 1 + +# Save WXS +$xml.save($out) diff --git a/dist/win-10/Studio.wxs b/dist/win-10/Studio.wxs new file mode 100644 index 000000000..f3a33ab07 --- /dev/null +++ b/dist/win-10/Studio.wxs @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/priv b/priv index 3ec133a70..b63a15c1a 160000 --- a/priv +++ b/priv @@ -1 +1 @@ -Subproject commit 3ec133a70c9d2f1ab97f4f57debdeb2b0d3af9f5 +Subproject commit b63a15c1ac2e7c6dc29dea08aded573e54102c46 diff --git a/src/Engine/BciDevice.cpp b/src/Engine/BciDevice.cpp index e76ea1f0f..78c403e05 100644 --- a/src/Engine/BciDevice.cpp +++ b/src/Engine/BciDevice.cpp @@ -73,6 +73,8 @@ void BciDevice::CreateSensors() { // create sensor with the neuro headset sample rate Sensor* sensor = new Sensor(mElectrodes[i].GetName(), GetSampleRate()); + sensor->GetInput()->SetBufferSizeInSeconds(DEFAULTBUFFERSIZEINSECONDS); + sensor->GetOutput()->SetBufferSizeInSeconds(DEFAULTBUFFERSIZEINSECONDS); // set unique color for each sensor sensor->GetChannel()->GetColor().SetUniqueColor(i); diff --git a/src/Engine/BciDevice.h b/src/Engine/BciDevice.h index 98d79097f..99db55680 100644 --- a/src/Engine/BciDevice.h +++ b/src/Engine/BciDevice.h @@ -38,6 +38,8 @@ class ENGINE_API BciDevice : public Device public: enum { BASE_TYPE_ID = 0x02 }; + static constexpr const double DEFAULTBUFFERSIZEINSECONDS = 60.0; + // constructor & destructor BciDevice(DeviceDriver* driver = NULL); virtual ~BciDevice(); @@ -57,6 +59,9 @@ class ENGINE_API BciDevice : public Device // true if device provides the contact quality for a sensor virtual bool HasEegContactQualityIndicator() { return false; } + // true if the device has named electrodes with positions + virtual bool HasElectrodePositions() { return true; } + // get/set position of electrodes const Core::Array& GetElectrodes() const { return mElectrodes; } EEGElectrodes::Electrode GetElectrodePosition(uint32 neuroSensorIndex) const; diff --git a/src/Engine/Branding.h b/src/Engine/Branding.h index 05a87fa3a..c5e751433 100644 --- a/src/Engine/Branding.h +++ b/src/Engine/Branding.h @@ -34,7 +34,7 @@ class Branding inline Branding() { } public: static constexpr const char* CompanyName = "neuromore"; // do not put Inc. behind this as this is also used as folder name - static constexpr const char* DeveloperName = "neuromore Inc."; + static constexpr const char* DeveloperName = "neuromore co"; static constexpr const char* Website = "https://www.neuromore.com"; static constexpr const char* DocumentationUrl = "https://doc.neuromore.com"; static constexpr const char* AccountUrl = "https://account.neuromore.com"; @@ -51,10 +51,14 @@ class Branding static constexpr const char* SplashImageName = ":/Images/SplashScreen-neuromore.png"; static constexpr const char* AboutImageName = ":/Images/About-neuromore.png"; static constexpr const bool LoginRemberMePrechecked = true; + static constexpr const bool HasColorCodesForChannels = false; + static constexpr const bool HasMinimalDeviceInfo = false; static constexpr const bool DefaultAutoDetectionEnabled = true; static constexpr const bool DefaultTestDeviceEnabled = true; static constexpr const bool DefaultEemagineEnabled = true; - static constexpr const bool DefaultBrainMasterEnabled = false; + static constexpr const bool DefaultBrainMasterEnabled = true; + static constexpr const bool DefaultOpenBCIEnabled = true; + static constexpr const bool DefaultBrainflowEnabled = true; static constexpr const int DefaultServerPresetIdx = 0; static constexpr const int DefaultAutoSelectType = 4; // = SELECT_FIRST_EIGHT (see ChannelMultiSelectionWidget.h) }; diff --git a/src/Engine/Devices/Neurosity/CrownDevice.h b/src/Engine/Devices/Neurosity/CrownDevice.h index e6398fd93..aeec41764 100644 --- a/src/Engine/Devices/Neurosity/CrownDevice.h +++ b/src/Engine/Devices/Neurosity/CrownDevice.h @@ -52,8 +52,8 @@ class ENGINE_API CrownDevice : public BciDevice const char* GetTypeName() const override { return "crown"; } double GetLatency() const override { return 0.1; } double GetExpectedJitter() const override { return 0.2; } - bool IsWireless() const override { return false; /* we do not know the connection quality of the muse; this removes the connection quality icon in the device manager */ } - bool HasEegContactQualityIndicator() override { return true; } + bool IsWireless() const override { return false; } + bool HasEegContactQualityIndicator() override { return false; } static const char* GetRuleName() { return "DEVICE_NeurosityNotion"; } int32 GetOscPathDeviceId(const Core::String& address) const override; diff --git a/src/Engine/Devices/Neurosity/NotionDevices.h b/src/Engine/Devices/Neurosity/NotionDevices.h index 359e06fc8..7fe8a7e6f 100644 --- a/src/Engine/Devices/Neurosity/NotionDevices.h +++ b/src/Engine/Devices/Neurosity/NotionDevices.h @@ -59,8 +59,8 @@ class ENGINE_API NotionDevice : public BciDevice const char* GetTypeName() const override { return "notion"; } double GetLatency() const override { return 0.1; } double GetExpectedJitter() const override { return 0.2; } - bool IsWireless() const override { return false; /* we do not know the connection quality of the muse; this removes the connection quality icon in the device manager */ } - bool HasEegContactQualityIndicator() override { return true; } + bool IsWireless() const override { return false; } + bool HasEegContactQualityIndicator() override { return false; } static const char* GetRuleName() { return "DEVICE_NeurosityNotion"; } int32 GetOscPathDeviceId(const Core::String& address) const override; diff --git a/src/Engine/Devices/OpenBCI/OpenBCIDevices.cpp b/src/Engine/Devices/OpenBCI/OpenBCIDevices.cpp index fdc344135..23d20e638 100644 --- a/src/Engine/Devices/OpenBCI/OpenBCIDevices.cpp +++ b/src/Engine/Devices/OpenBCI/OpenBCIDevices.cpp @@ -49,37 +49,24 @@ void OpenBCIDeviceBase::CreateSensors() mAccForwardSensor->GetChannel()->SetMinValue(-2000.0); mAccForwardSensor->GetChannel()->SetMaxValue(1996.1); mAccForwardSensor->GetChannel()->SetUnit("mm/s^2"); + mAccForwardSensor->GetChannel()->SetBufferSizeInSeconds(DEFAULTBUFFERSIZEINSECONDS); AddSensor(mAccForwardSensor); mAccUpSensor = new Sensor("Acc (Up)", 250); mAccUpSensor->GetChannel()->SetMinValue(-2000.0); mAccUpSensor->GetChannel()->SetMaxValue(1996.1); mAccUpSensor->GetChannel()->SetUnit("mm/s^2"); + mAccUpSensor->GetChannel()->SetBufferSizeInSeconds(DEFAULTBUFFERSIZEINSECONDS); AddSensor(mAccUpSensor); mAccLeftSensor = new Sensor("Acc (Left)", 250); mAccLeftSensor->GetChannel()->SetMinValue(-2000.0); mAccLeftSensor->GetChannel()->SetMaxValue(1996.1); mAccLeftSensor->GetChannel()->SetUnit("mm/s^2"); + mAccLeftSensor->GetChannel()->SetBufferSizeInSeconds(DEFAULTBUFFERSIZEINSECONDS); AddSensor(mAccLeftSensor); } -void OpenBCIDeviceBase::StartTest() -{ - if (mTesting) - return; - - mTesting = true; -} - -void OpenBCIDeviceBase::StopTest() -{ - if (!mTesting) - return; - - mTesting = false; -} - // // OpenBCI without Daisy module @@ -111,14 +98,14 @@ void OpenBCIDevice::CreateElectrodes() mElectrodes.Clear(); mElectrodes.Reserve(NUMELECTRODESCYTON); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("Fp1")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("Fp2")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("C5")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("C6")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("P7")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("O1")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("O2")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("P8")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("1")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("2")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("3")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("4")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("5")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("6")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("7")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("8")); } diff --git a/src/Engine/Devices/OpenBCI/OpenBCIDevices.h b/src/Engine/Devices/OpenBCI/OpenBCIDevices.h index bddcec9d9..07d7ef090 100644 --- a/src/Engine/Devices/OpenBCI/OpenBCIDevices.h +++ b/src/Engine/Devices/OpenBCI/OpenBCIDevices.h @@ -86,14 +86,13 @@ class ENGINE_API OpenBCIDeviceBase : public BciDevice double GetLatency() const override { return 0.1; } double GetExpectedJitter() const override { return 0.1; } bool IsWireless() const override { return true; } - bool HasTestMode() const override { return true; } + bool HasTestMode() const override { return false; } void CreateSensors() override; - void StartTest() override; - void StopTest() override; - inline bool IsTestRunning() override { return mTesting; } - inline bool HasEegContactQualityIndicator() override { return mTesting; } + inline bool IsTestRunning() override { return true; } + inline bool HasEegContactQualityIndicator() override { return true; } + inline bool HasElectrodePositions() override { return false; } virtual void AddSample(uint32 idx, double v) = 0; diff --git a/src/Engine/EEGElectrodes.cpp b/src/Engine/EEGElectrodes.cpp index 5c56c99f8..d5c874ded 100644 --- a/src/Engine/EEGElectrodes.cpp +++ b/src/Engine/EEGElectrodes.cpp @@ -432,14 +432,14 @@ void EEGElectrodes::Init() // Others // 127 electrodes - //mElectrodes.Add( Electrode("1", 36.000, -22.000) ); - //mElectrodes.Add( Electrode("2", 47.000, -6.000) ); - //mElectrodes.Add( Electrode("3", 56.000, 10.000) ); - //mElectrodes.Add( Electrode("4", 72.000, 26.000) ); - //mElectrodes.Add( Electrode("5", 78.000, 42.000) ); - //mElectrodes.Add( Electrode("6", 90.000, 58.000) ); - //mElectrodes.Add( Electrode("7", 126.000, 74.000) ); - //mElectrodes.Add( Electrode("8", 54.000, -22.000) ); + mElectrodes.Add( Electrode("1", 180.000, 10.000) ); + mElectrodes.Add( Electrode("2", 180.000, 30.000) ); + mElectrodes.Add( Electrode("3", 180.000, 50.000) ); + mElectrodes.Add( Electrode("4", 180.000, 70.000) ); + mElectrodes.Add( Electrode("5", 0.000, 70.000) ); + mElectrodes.Add( Electrode("6", 0.000, 50.000) ); + mElectrodes.Add( Electrode("7", 0.000, 30.000) ); + mElectrodes.Add( Electrode("8", 0.000, 10.000) ); //mElectrodes.Add( Electrode("9", 64.000, -6.000) ); //mElectrodes.Add( Electrode("10", 73.000, 10.000) ); //mElectrodes.Add( Electrode("11", 90.000, 26.000) ); diff --git a/src/Engine/Graph/ChannelSelectorNode.cpp b/src/Engine/Graph/ChannelSelectorNode.cpp index a3a14eea1..7bac7ede6 100644 --- a/src/Engine/Graph/ChannelSelectorNode.cpp +++ b/src/Engine/Graph/ChannelSelectorNode.cpp @@ -59,20 +59,25 @@ void ChannelSelectorNode::Init() InitInputPorts(1); GetInputPort(INPUTPORT_SET).SetupAsChannels("In", "x1", INPUTPORT_SET); - const uint32 numPortsDefault = 0; - InitOutputPorts(numPortsDefault); + // create temporary empty ports + InitOutputPorts(NUMPORTSDEFAULT); + for (uint32 i = 0; i < NUMPORTSDEFAULT; ++i) + { + mTempString.Format("y%i", i + 1); + GetOutputPort(i).SetupAsChannels("-", mTempString.AsChar(), i); + } // ATTRIBUTES // hidden port number attribute Core::AttributeSettings* attribNumPorts = RegisterAttribute("", "numOutputPorts", "", Core::ATTRIBUTE_INTERFACETYPE_INTSPINNER); - attribNumPorts->SetDefaultValue( Core::AttributeInt32::Create(numPortsDefault) ); + attribNumPorts->SetDefaultValue( Core::AttributeInt32::Create(NUMPORTSDEFAULT) ); attribNumPorts->SetMinValue( Core::AttributeInt32::Create(0) ); attribNumPorts->SetMaxValue( Core::AttributeInt32::Create(INT_MAX) ); attribNumPorts->SetVisible(false); // channel names - Core::AttributeSettings* attribChannelNames = RegisterAttribute("Channel Names", "channels", "", Core::ATTRIBUTE_INTERFACETYPE_STRINGARRAY); + Core::AttributeSettings* attribChannelNames = RegisterAttribute("Channels", "channels", "Names or Indexes of Channels to use", Core::ATTRIBUTE_INTERFACETYPE_STRINGARRAY); attribChannelNames->SetDefaultValue( Core::AttributeStringArray::Create("*") ); // single output attribute @@ -109,7 +114,7 @@ void ChannelSelectorNode::ReInit(const Time& elapsed, const Time& delta) { if (inPort.GetChannels() != NULL && inPort.GetChannels()->GetNumChannels() == 0) { - mIsInitialized = false; + //mIsInitialized = false; } } @@ -129,23 +134,29 @@ void ChannelSelectorNode::Update(const Time& elapsed, const Time& delta) if (mIsInitialized == false) return; - CORE_ASSERT(mOutputChannels.Size() == mSelectedInputs.Size()); - // forward all samples to the outputs - const uint32 numChannels = mOutputChannels.Size(); + const uint32 numChannels = mMapping.Size(); for (uint32 i=0; iAddSample(0.0); + + // no input and no output, do nothing + if (!reader || !m.out) + continue; + + // forward samples from input to output const uint32 numSamples = reader->GetNumNewSamples(); for (uint32 s = 0; s < numSamples; ++s) { - double sample = reader->GetSample(s); // Note: don't pop samples here, just read them (as we might forward one channel multiple times to different outputs) - output->AsType()->AddSample(sample); + // Note: don't pop samples here, just read them + // (as we might forward one channel multiple times to different outputs) + double sample = reader->GetSample(s); + m.out->AddSample(sample); } } @@ -157,11 +168,6 @@ void ChannelSelectorNode::Update(const Time& elapsed, const Time& delta) // an attribute has changed void ChannelSelectorNode::OnAttributesChanged() { - // A little hacky? This codepath is only used to create output ports during loading of the classifier - the attribute is the only way we know how - // many ports there should be created before loading the connection. - if (GetNumOutputPorts() == 0) - ReInitOutputPorts(); - // always reinit if a node attribute was changed ResetAsync(); } @@ -173,6 +179,7 @@ void ChannelSelectorNode::Start(const Time& elapsed) // remember number of ports in attribute SetInt32Attribute("numOutputPorts", mInputReader.GetNumChannels()); + DeleteOutputChannels(); CollectSelectedInputChannels(); // collect channels first ReInitOutputPorts(); // init ports (empty, without channels) ReInitOutputChannels(); // init output channels and connect them to the ports; also name the ports @@ -189,8 +196,6 @@ void ChannelSelectorNode::CollectSelectedInputChannels() const Array& names = GetStringArrayAttribute(ATTRIB_CHANNELNAMES, Array()); const bool hasNames = !names.IsEmpty(); const bool useSingleWildCardFeature = (hasNames && names[0].GetLength() == 1 && names[0].GetFirst() == StringCharacter::asterisk); - - mSelectedInputs.Clear(); const uint32 numInputChannels = mInputReader.GetNumChannels(); @@ -202,9 +207,12 @@ void ChannelSelectorNode::CollectSelectedInputChannels() if (useSingleWildCardFeature == true) { // forward all channel - for (uint32 i=0; i* ch = mInputReader.GetChannel(i)->AsType(); + mMapping.Add(Mapping(ch, 0, ch->GetName())); + } + return; } @@ -220,8 +228,6 @@ void ChannelSelectorNode::CollectSelectedInputChannels() name.GetFirst() == StringCharacter::asterisk && name.GetLast() == StringCharacter::asterisk); - bool haveChannel = false; - // normal mode: exact name matching if (useWildCardFeature == false && useSingleWildCardFeature == false) { @@ -231,7 +237,7 @@ void ChannelSelectorNode::CollectSelectedInputChannels() for (uint32 j=0; jGetNameString().IsEqual(name) == true) + if ((name.IsValidInt() && name.ToInt() == (int)(j+1)) || channel->GetNameString().IsEqual(name)) { // found it channelIndex = j; @@ -242,17 +248,17 @@ void ChannelSelectorNode::CollectSelectedInputChannels() // add the channel if (channelIndex != CORE_INVALIDINDEX32) { - mSelectedInputs.Add(channelIndex); - haveChannel = true; + Channel* ch = mInputReader.GetChannel(channelIndex)->AsType(); + mMapping.Add(Mapping(ch, 0, ch->GetName())); } - - // node error when channel was not found (not in wild card mode, only in exact match mode) - if (haveChannel == false) + else { + mMapping.Add(Mapping(0, 0, name)); + + // node error when channel was not found (not in wild card mode, only in exact match mode) String tmp; tmp.Format("Channel '%s' not found", name.AsChar()); SetError(ERROR_CHANNEL_NOT_FOUND, tmp.AsChar()); } - } else // wildcard modes: use substring matching { @@ -266,13 +272,10 @@ void ChannelSelectorNode::CollectSelectedInputChannels() if (channel->GetNameString().Contains(name) == true) { // found one, add it (don't break, keep processing all input channels) - mSelectedInputs.Add(j); - haveChannel = true; + mMapping.Add(Mapping(channel->AsType(), 0, channel->GetNameString())); } } } - - } } @@ -280,12 +283,14 @@ void ChannelSelectorNode::CollectSelectedInputChannels() // use this to check if an input channel is forwarded by the node bool ChannelSelectorNode::IsChannelSelected(const ChannelBase* channel) const { + if (!channel) + return false; + // compare pointer against all selected input channels - const uint32 numSelectedChannels = mSelectedInputs.Size(); + const uint32 numSelectedChannels = mMapping.Size(); for (uint32 i=0; i(this)->mInputReader.GetChannel(index) == channel) + if (channel == mMapping[i].in) return true; } @@ -297,7 +302,6 @@ bool ChannelSelectorNode::IsChannelSelected(const ChannelBase* channel) const void ChannelSelectorNode::ReInitOutputPorts() { const bool singleOutput = GetBoolAttribute(ATTRIB_SINGLE_OUTPUT); - uint32 numPortsAttr = GetInt32Attribute(ATTRIB_NUMOUTPUTPORTS); // previously remembered number of output ports // get number of required output ports if (singleOutput == true) @@ -307,28 +311,15 @@ void ChannelSelectorNode::ReInitOutputPorts() // update channel references GetOutputPort(0).GetChannels()->Clear(); - - // remember number of outputs (if it has changed) - if (numPortsAttr != 1) - SetInt32AttributeByIndex(ATTRIB_NUMOUTPUTPORTS, 1); } else { // get number of output channels we have to output - const uint32 numChannels = mSelectedInputs.Size(); - uint32 numPorts; - - // use last port count only if there are no input channel present - if (mInputReader.GetNumChannels() == 0 && GetNumOutputPorts() == 0) - numPorts = numPortsAttr; - else - numPorts = numChannels; - - SetInt32AttributeByIndex(ATTRIB_NUMOUTPUTPORTS, numPorts); + const uint32 numChannels = mMapping.Size(); // create empty ports - InitOutputPorts(numPorts); - for (uint32 i = 0; i < numPorts; ++i) + InitOutputPorts(numChannels); + for (uint32 i = 0; i < numChannels; ++i) { mTempString.Format("y%i", i + 1); GetOutputPort(i).SetupAsChannels("-", mTempString.AsChar(), i); @@ -340,27 +331,22 @@ void ChannelSelectorNode::ReInitOutputPorts() // create the output channels (one for each selected input channel) and connect them to the outputs void ChannelSelectorNode::ReInitOutputChannels() { - DeleteOutputChannels(); - // create output channel array, for the selected channels given - const uint32 numChannels = mSelectedInputs.Size(); + const uint32 numChannels = mMapping.Size(); for (uint32 i = 0; i < numChannels; ++i) { - CORE_ASSERT(mSelectedInputs[i] < mInputReader.GetNumChannels()); - - Channel* channel = new Channel(); // TODO clone channel here so its independent of type - Channel* inputChannel = mInputReader.GetChannel(mSelectedInputs[i])->AsType(); - // configure output channel to same parameters as input channel - channel->SetBufferSize(10); // set any buffersize > 0 - channel->SetName(inputChannel->GetName()); - channel->SetSampleRate(inputChannel->GetSampleRate()); - channel->SetMinValue(inputChannel->GetMinValue()); - channel->SetMaxValue(inputChannel->GetMaxValue()); - channel->SetColor(inputChannel->GetColor()); - - mOutputChannels.Add(channel); + mMapping[i].out = new Channel(); + mMapping[i].out->SetBufferSize(10); // set any buffersize > 0 + mMapping[i].out->SetName(mMapping[i].name); + if (mMapping[i].in) + { + mMapping[i].out->SetSampleRate(mMapping[i].in->GetSampleRate()); + mMapping[i].out->SetMinValue(mMapping[i].in->GetMinValue()); + mMapping[i].out->SetMaxValue(mMapping[i].in->GetMaxValue()); + mMapping[i].out->SetColor(mMapping[i].in->GetColor()); + } } // now add connections to output ports @@ -374,24 +360,24 @@ void ChannelSelectorNode::ReInitOutputChannels() MultiChannel* multichannel = GetOutputPort(0).GetChannels(); multichannel->Clear(); for (uint32 i = 0; i < numChannels; ++i) - multichannel->AddChannel(mOutputChannels[i]); + multichannel->AddChannel(mMapping[i].out); // set the port name GetOutputPort(0).SetName("Out"); } else { - CORE_ASSERT(GetNumOutputPorts() == numChannels); + CORE_ASSERT(GetNumOutputPorts() >= numChannels); // add each channel to its individual port for (uint32 i = 0; i < numChannels; ++i) { MultiChannel* multichannel = GetOutputPort(i).GetChannels(); multichannel->Clear(); - multichannel->AddChannel(mOutputChannels[i]); + multichannel->AddChannel(mMapping[i].out); // set the port name - GetOutputPort(i).SetName(mOutputChannels[i]->GetName()); + GetOutputPort(i).SetName(mMapping[i].name); } } } @@ -411,5 +397,11 @@ void ChannelSelectorNode::DeleteOutputChannels() } // delete the output channels - DestructArray(mOutputChannels); + const uint32 numMappings = mMapping.Size(); + for (uint32 i = 0; i < numMappings; i++) + { + delete mMapping[i].out; + mMapping[i].out = 0; + } + mMapping.Clear(); } diff --git a/src/Engine/Graph/ChannelSelectorNode.h b/src/Engine/Graph/ChannelSelectorNode.h index 1b2448971..89ff3bad7 100644 --- a/src/Engine/Graph/ChannelSelectorNode.h +++ b/src/Engine/Graph/ChannelSelectorNode.h @@ -33,6 +33,8 @@ class ENGINE_API ChannelSelectorNode : public SPNode { public: + static constexpr const uint32 NUMPORTSDEFAULT = 64; + enum { TYPE_ID = 0x0031 }; static const char* Uuid () { return "fd168f5a-047b-11e5-8418-1697f925ec7b"; } @@ -44,8 +46,9 @@ class ENGINE_API ChannelSelectorNode : public SPNode enum { ATTRIB_NUMOUTPUTPORTS = 0, - ATTRIB_CHANNELNAMES, - ATTRIB_SINGLE_OUTPUT, + ATTRIB_CHANNELNAMES = 1, + ATTRIB_SINGLE_OUTPUT = 2, + ATTRIB_QUICK_CONFIG = 3 }; enum EError @@ -53,7 +56,15 @@ class ENGINE_API ChannelSelectorNode : public SPNode ERROR_CHANNEL_NOT_FOUND = GraphObjectError::ERROR_CONFIGURATION | 0x01, }; - + struct Mapping + { + Channel* in; + Channel* out; + Core::String name; + Mapping(Channel* in = 0, Channel* out = 0, const Core::String& name = "") : + in(in), out(out), name(name) { } + }; + // constructor & destructor ChannelSelectorNode(Graph* graph); ~ChannelSelectorNode(); @@ -77,7 +88,6 @@ class ENGINE_API ChannelSelectorNode : public SPNode // check if input channels are selected (forwarded) to the output bool IsChannelSelected(const ChannelBase* channel) const; - bool IsChannelSelected(uint32 index) const { return mSelectedInputs.Contains(index); } private: void CollectSelectedInputChannels(); // collect all channels that match the provided selection into mSelectedInputChannels array @@ -86,9 +96,7 @@ class ENGINE_API ChannelSelectorNode : public SPNode void ReInitOutputChannels(); // create the output channels (one for each selected input channel) and connect them to the outputs void DeleteOutputChannels(); - Core::Array mSelectedInputs; // indices of the selected channels in mInputChannels; one for each element of mOutputChannels - Core::Array mOutputChannels; // the output channels where all samples are forwarded to (1:1 from mSelectedInputChannels) - + Core::Array mMapping; // mapping between input and output channels }; diff --git a/src/Engine/Graph/Classifier.cpp b/src/Engine/Graph/Classifier.cpp index 50ae6be66..154b2329e 100644 --- a/src/Engine/Graph/Classifier.cpp +++ b/src/Engine/Graph/Classifier.cpp @@ -1092,3 +1092,18 @@ uint32 Classifier::SaveCloudParameters(CloudParameters& parameter) const return numSaved; } + + +// +ChannelSelectorNode* Classifier::FindMainChannelSelector() const +{ + const uint32 numNodes = GetNumNodes(); + for (uint32 i = 0; i < numNodes; i++) { + Node* n = GetNode(i); + if (n->GetType() == ChannelSelectorNode::TYPE_ID && n->GetNumInputPorts() > 0) { + if (n->GetBoolAttribute(ChannelSelectorNode::ATTRIB_QUICK_CONFIG)) + return (ChannelSelectorNode*)n; + } + } + return 0; +} diff --git a/src/Engine/Graph/Classifier.h b/src/Engine/Graph/Classifier.h index c65515747..d4c59fbc6 100644 --- a/src/Engine/Graph/Classifier.h +++ b/src/Engine/Graph/Classifier.h @@ -43,7 +43,7 @@ #include "AnnotationNode.h" #include "DeviceInputNode.h" #include "ToneGeneratorNode.h" - +#include "ChannelSelectorNode.h" class ENGINE_API Classifier : public Graph, public Core::EventHandler { @@ -126,6 +126,9 @@ class ENGINE_API Classifier : public Graph, public Core::EventHandler ToneGeneratorNode* GetToneGeneratorNode(uint32_t index) const { return mToneGeneratorNodes[index]; } uint32_t GetNumToneGeneratorNodes() const { return mToneGeneratorNodes.Size(); } + // find the first ChannelSelector with a DeviceInput at its input + ChannelSelectorNode* FindMainChannelSelector() const; + // // Parameters // @@ -166,6 +169,7 @@ class ENGINE_API Classifier : public Graph, public Core::EventHandler void CollectUsedSensors(); bool IsSensorUsed (Sensor* sensor) const { return mUsedSensors.Contains(sensor); } + uint32 GetNumUsedSensors() const { return mUsedSensors.Size(); } // // Classifier Control diff --git a/src/Engine/Graph/CustomFeedbackNode.cpp b/src/Engine/Graph/CustomFeedbackNode.cpp index 3748d37cf..55e62682e 100644 --- a/src/Engine/Graph/CustomFeedbackNode.cpp +++ b/src/Engine/Graph/CustomFeedbackNode.cpp @@ -191,14 +191,6 @@ void CustomFeedbackNode::Update(const Time& elapsed, const Time& delta) } } - -void CustomFeedbackNode::SetName(const char* name) -{ - FeedbackNode::SetName(name); - mChannels[0]->SetName(name); -} - - // attributes have changed void CustomFeedbackNode::OnAttributesChanged() { diff --git a/src/Engine/Graph/CustomFeedbackNode.h b/src/Engine/Graph/CustomFeedbackNode.h index e5c1fe97d..a569edfee 100644 --- a/src/Engine/Graph/CustomFeedbackNode.h +++ b/src/Engine/Graph/CustomFeedbackNode.h @@ -76,8 +76,6 @@ class ENGINE_API CustomFeedbackNode : public FeedbackNode const char* GetRuleName() const override { return "NODE_Feedback"; } GraphObject* Clone(Graph* graph) override { CustomFeedbackNode* clone = new CustomFeedbackNode(graph); return clone; } - void SetName(const char* name) override; - // accessors bool IsRanged() const { return GetBoolAttribute(ATTRIB_ISRANGED); } double GetRangeMin() const override { return GetFloatAttribute(ATTRIB_RANGEMIN); } diff --git a/src/Engine/Graph/Graph.cpp b/src/Engine/Graph/Graph.cpp index a0815761b..b25391394 100644 --- a/src/Engine/Graph/Graph.cpp +++ b/src/Engine/Graph/Graph.cpp @@ -701,7 +701,7 @@ bool Graph::ApplySettings(const GraphSettings& settings) const bool typeMatched = (settings.HasObjectType(i) == true && object->GetType() == settings.GetObjectType(i)); const bool nameMatched = (settings.HasObjectName(i) == true && object->GetUuidString().IsEqualNoCase(settings.GetObjectName(i))); - if (uuidMatched || typeMatched || nameMatched) + if (typeMatched && (uuidMatched || nameMatched)) { uint32 attributeIndex = object->FindAttributeIndexByInternalName(attributeName); diff --git a/src/Engine/Graph/OutputNode.cpp b/src/Engine/Graph/OutputNode.cpp index c340bf5a0..9bee2d9aa 100644 --- a/src/Engine/Graph/OutputNode.cpp +++ b/src/Engine/Graph/OutputNode.cpp @@ -57,6 +57,13 @@ void OutputNode::Init() // upload checkbox AttributeSettings* attributeUpload = RegisterAttribute("Upload", "upload", "Upload the data stream to neuromore Cloud after a successful session.", ATTRIBUTE_INTERFACETYPE_CHECKBOX); attributeUpload->SetDefaultValue(AttributeBool::Create(false)); + + // output name + AttributeSettings* outputName = RegisterAttribute("Output Name", "outputName", "Select name of output/upload.", ATTRIBUTE_INTERFACETYPE_COMBOBOX); + outputName->ResizeComboValues(2); + outputName->SetComboValue(0, "Use Node Name"); + outputName->SetComboValue(1, "Use Channel Name"); + outputName->SetDefaultValue(AttributeInt32::Create(0)); } @@ -77,6 +84,9 @@ void OutputNode::Start(const Time& elapsed) const Time startTime = mInputReader.FindMinLastSampleTime(); mInputReader.Start(startTime); + // whether to use node or channel name for output + const bool usechannelname = this->GetInt32Attribute(ATTRIB_OUTPUTNAME) == 1; + int readerIndex = 0; const uint32 numChannels = mChannels.Size(); for (uint32 i = 0; i < numChannels; ++i) @@ -103,6 +113,7 @@ void OutputNode::Start(const Time& elapsed) // set start times mResamplers[i].SetStartTime(startTime); mChannels[i]->SetStartTime(startTime); + mChannels[i]->SetName(usechannelname ? inputChannel->GetName() : this->GetName()); // finally reinit resampler mResamplers[i].ReInit(); diff --git a/src/Engine/Graph/OutputNode.h b/src/Engine/Graph/OutputNode.h index dc502ee2f..afa8c158b 100644 --- a/src/Engine/Graph/OutputNode.h +++ b/src/Engine/Graph/OutputNode.h @@ -43,7 +43,8 @@ class ENGINE_API OutputNode : public SPNode { ATTRIB_SIGNALRESOLUTION = 0, ATTRIB_UPLOAD = 1, - NUM_BASEATTRIBUTES = 2 + ATTRIB_OUTPUTNAME = 2, + NUM_BASEATTRIBUTES = 3 }; enum SignalResolution diff --git a/src/Engine/Graph/ParameterNode.cpp b/src/Engine/Graph/ParameterNode.cpp index 3cd39f841..dfc3f4461 100644 --- a/src/Engine/Graph/ParameterNode.cpp +++ b/src/Engine/Graph/ParameterNode.cpp @@ -42,7 +42,7 @@ ParameterNode::ParameterNode(Graph* graph) : InputNode(graph) mShowControls = false; mShowInputs = false; - mSampleRate = 128; + mSampleRate = 0.0; mShowWidget = true; mEnableWidget = true; @@ -85,7 +85,7 @@ void ParameterNode::Init() // sample rate Core::AttributeSettings* attributeSampleRate = RegisterAttribute("Sample Rate", "sampleRate", "Sample rate of the output channel.", Core::ATTRIBUTE_INTERFACETYPE_FLOATSPINNER); - attributeSampleRate->SetDefaultValue( Core::AttributeFloat::Create(mSampleRate) ); + attributeSampleRate->SetDefaultValue( Core::AttributeFloat::Create(0.0) ); attributeSampleRate->SetMinValue( Core::AttributeFloat::Create(0) ); attributeSampleRate->SetMaxValue( Core::AttributeFloat::Create(DBL_MAX) ); @@ -166,6 +166,10 @@ void ParameterNode::ReInit(const Time& elapsed, const Time& delta) void ParameterNode::Start(const Time& elapsed) { + BciDevice* bci = GetEngine()->GetActiveBci(); + mSampleRate = mSampleRate > 0.01 ? mSampleRate : + bci ? bci->GetSampleRate() : DEFAULTSAMPLERATE; + // create sensors before start UpdateSensors(); @@ -173,7 +177,7 @@ void ParameterNode::Start(const Time& elapsed) InputNode::Start(elapsed); UpdateNames(); - + // configure clock and start it at current elapsed time mClock.Reset(); mClock.SetFrequency(mSampleRate); @@ -256,7 +260,9 @@ void ParameterNode::OnAttributesChanged() { ResetAsync(); - mSampleRate = sampleRate; + BciDevice* bci = GetEngine()->GetActiveBci(); + mSampleRate = sampleRate > 0.01 ? sampleRate : + bci ? bci->GetSampleRate() : DEFAULTSAMPLERATE; } // update values if default value was changed @@ -382,7 +388,6 @@ void ParameterNode::GenerateSamples() void ParameterNode::UpdateSensors() { const uint32 numChannels = GetInt32Attribute(ATTRIB_NUMCHANNELS); - const uint32 sampleRate = GetFloatAttribute(ATTRIB_SAMPLERATE); mValues.Resize(numChannels); mSensors.Resize(numChannels); @@ -395,7 +400,7 @@ void ParameterNode::UpdateSensors() mSensors[i].Reset(); mSensors[i].GetChannel()->SetBufferSize(10); mSensors[i].SetDriftCorrectionEnabled(false); - mSensors[i].GetChannel()->SetSampleRate(sampleRate); + mSensors[i].GetChannel()->SetSampleRate(mSampleRate); mTempString.Format("%.2f", mValues[i]); mSensors[i].GetChannel()->SetName(mTempString.AsChar()); GetOutputPort(OUTPUTPORT_VALUE).GetChannels()->AddChannel(mSensors[i].GetChannel()); diff --git a/src/Engine/Graph/ParameterNode.h b/src/Engine/Graph/ParameterNode.h index 04fed47cb..11e33f48c 100644 --- a/src/Engine/Graph/ParameterNode.h +++ b/src/Engine/Graph/ParameterNode.h @@ -34,6 +34,8 @@ class ENGINE_API ParameterNode : public InputNode { public: + static constexpr const double DEFAULTSAMPLERATE = 128.0; + enum { TYPE_ID = 0x0003 }; static const char* Uuid () { return "0a2f1004-bb6b-11e4-8dfc-aa07a5b093db"; } diff --git a/src/Engine/Graph/SPNode.cpp b/src/Engine/Graph/SPNode.cpp index 8e9eca3d6..cb8305ec5 100644 --- a/src/Engine/Graph/SPNode.cpp +++ b/src/Engine/Graph/SPNode.cpp @@ -177,7 +177,7 @@ void SPNode::ReInit(const Time& elapsed, const Time& delta) { if (mInputChannels.GetChannel(i)->GetSampleRate() <= 0) { - mIsInitialized = false; + //mIsInitialized = false; SetError(ERROR_INPUT_CONSTANT_SAMPLERATE, "Input must have a valid sample rate."); return; } @@ -191,7 +191,7 @@ void SPNode::ReInit(const Time& elapsed, const Time& delta) { if (mInputReader.HasUniformSampleRate() == false) { - mIsInitialized = false; + //mIsInitialized = false; SetError(ERROR_INPUT_MATCHING_SAMPLERATES, "Input sample rates are incompatible."); return; } diff --git a/src/Engine/Version.h b/src/Engine/Version.h index 7be6d6864..8604894e2 100644 --- a/src/Engine/Version.h +++ b/src/Engine/Version.h @@ -26,6 +26,6 @@ #define NEUROMORE_ENGINE_VERSION_MAJOR 1 #define NEUROMORE_ENGINE_VERSION_MINOR 7 -#define NEUROMORE_ENGINE_VERSION_PATCH 1 +#define NEUROMORE_ENGINE_VERSION_PATCH 2 #endif diff --git a/src/QtBase/AttributeWidgets/AttributeWidgets.cpp b/src/QtBase/AttributeWidgets/AttributeWidgets.cpp index 13fed3245..ffc77438c 100644 --- a/src/QtBase/AttributeWidgets/AttributeWidgets.cpp +++ b/src/QtBase/AttributeWidgets/AttributeWidgets.cpp @@ -632,6 +632,7 @@ void StringArrayAttributeWidget::OnStringChange() assert( sender()->inherits("QLineEdit") == true ); QLineEdit* widget = qobject_cast( sender() ); String widgetText; + String attribText; // get the number of attributes and iterate through them const uint32 numAttributes = mAttributes.Size(); @@ -639,7 +640,7 @@ void StringArrayAttributeWidget::OnStringChange() for (uint32 i=0; i(mAttributes[i]); - const char* attribText = attribute->AsString().AsChar(); + attribText = attribute->AsString(); widgetText = FromQtString(widget->text()); if (widgetText.Compare(attribText) != 0) diff --git a/src/QtBase/Networking/OscServer.cpp b/src/QtBase/Networking/OscServer.cpp index ceffcb1d2..4e1b1abf2 100644 --- a/src/QtBase/Networking/OscServer.cpp +++ b/src/QtBase/Networking/OscServer.cpp @@ -84,16 +84,14 @@ void OscServer::Init() // initialize the socket bind mode, reuse UDP adresses QAbstractSocket::BindMode bindMode = QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint; - // bind the udp sockets - //if (mLocalEndpoint.isNull() == true) - mUdpSocket->bind(mUdpPort, bindMode); - //else - // mUdpSocket->bind(mLocalEndpoint, mUdpPort, bindMode); - - - connect(mUdpSocket, SIGNAL(readyRead()), this, SLOT(OnReceiveUdpDatagram())); - - mUdpOutSocket->bind(mLocalEndpoint, mRemoteUdpPort, bindMode); + // setup listen udp port + if (mUdpSocket->bind(mUdpPort, bindMode)) + connect(mUdpSocket, SIGNAL(readyRead()), this, SLOT(OnReceiveUdpDatagram())); + else + LogError("Failed to bind UDP socket."); + + // setup output udp port (no need to bind!) + //mUdpOutSocket->bind(mLocalEndpoint, mRemoteUdpPort, bindMode); const uint32 FPS = 100; mTimer = new QTimer(this); diff --git a/src/QtBase/Version.h b/src/QtBase/Version.h index 39051e9e9..9d085dd5e 100644 --- a/src/QtBase/Version.h +++ b/src/QtBase/Version.h @@ -9,6 +9,6 @@ #define NEUROMORE_QTBASE_VERSION_MAJOR 1 #define NEUROMORE_QTBASE_VERSION_MINOR 7 -#define NEUROMORE_QTBASE_VERSION_PATCH 1 +#define NEUROMORE_QTBASE_VERSION_PATCH 2 #endif \ No newline at end of file diff --git a/src/Studio/Devices/BrainFlow/BrainFlowDriver.cpp b/src/Studio/Devices/BrainFlow/BrainFlowDriver.cpp index 25bdf86e1..cf962d961 100644 --- a/src/Studio/Devices/BrainFlow/BrainFlowDriver.cpp +++ b/src/Studio/Devices/BrainFlow/BrainFlowDriver.cpp @@ -33,7 +33,7 @@ using namespace Core; -BrainFlowDriver::BrainFlowDriver() : DeviceDriver(true) +BrainFlowDriver::BrainFlowDriver() : DeviceDriver(Branding::DefaultBrainflowEnabled) { CORE_EVENTMANAGER.AddEventHandler(this); AddSupportedDevice(BrainFlowDevice::TYPE_ID); diff --git a/src/Studio/Devices/OpenBCI/OpenBCIDriver.cpp b/src/Studio/Devices/OpenBCI/OpenBCIDriver.cpp index b3ed01a15..f69d61d0c 100644 --- a/src/Studio/Devices/OpenBCI/OpenBCIDriver.cpp +++ b/src/Studio/Devices/OpenBCI/OpenBCIDriver.cpp @@ -34,7 +34,7 @@ using namespace Core; // constructor -OpenBCIDriver::OpenBCIDriver() : DeviceDriver(false), EventHandler() +OpenBCIDriver::OpenBCIDriver() : DeviceDriver(Branding::DefaultOpenBCIEnabled), EventHandler() { LogInfo("Constructing OpenBCI device driver ..."); diff --git a/src/Studio/Plugins/Devices/BciDeviceWidget.cpp b/src/Studio/Plugins/Devices/BciDeviceWidget.cpp index 03b81eef2..1a3abc45a 100644 --- a/src/Studio/Plugins/Devices/BciDeviceWidget.cpp +++ b/src/Studio/Plugins/Devices/BciDeviceWidget.cpp @@ -29,12 +29,6 @@ using namespace Core; -#if defined(NEUROMORE_BRANDING_ANT) && defined(PRODUCTION_BUILD) -#define NEUROMORE_MINIMAL_DEVICE_STATS true -#else -#define NEUROMORE_MINIMAL_DEVICE_STATS false -#endif - // constructor BciDeviceWidget::BciDeviceWidget(BciDevice* device, QWidget* parent) : DeviceWidget(device, parent) { @@ -56,9 +50,13 @@ void BciDeviceWidget::Init() // init base DeviceWidget::Init(); + // layouts from base class + QHBoxLayout* primaryLayout = GetPrimaryDeviceInfoLayout(); + QHBoxLayout* secondaryLayout = GetSecondaryDeviceInfoLayout(); + // add signal quality widget mSignalQualityWidget = new SignalQualityWidget(this); - GetSecondaryDeviceInfoLayout()->addWidget(mSignalQualityWidget); + secondaryLayout->addWidget(mSignalQualityWidget); // hide/show signal quality widget (if device is not wireless) if (mBciDevice->HasWirelessIndicator()) @@ -66,19 +64,131 @@ void BciDeviceWidget::Init() else mSignalQualityWidget->hide(); + // impedance grid widget (container) + mImpedanceGridWidget = new QWidget(this); + + // impedance grid + mImpedanceGrid = new QGridLayout(mImpedanceGridWidget); + mImpedanceGrid->setAlignment(Qt::AlignCenter); + mImpedanceGrid->setSizeConstraint(QLayout::SetMinimumSize); + mImpedanceGrid->setContentsMargins(QMargins(16, 0, 0, 0)); + + const QSize lblsize(30, 20); + const QSize valsize(50, 20); + + QLabel* headlbl = new QLabel(); + headlbl->setFixedSize(lblsize); + headlbl->setText("CH"); + headlbl->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + headlbl->setStyleSheet("background-color: black;"); + headlbl->setAlignment(Qt::AlignCenter); + + QLabel* headval = new QLabel(); + headval->setFixedSize(valsize); + headval->setText("kOhm"); + headval->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + headval->setStyleSheet("background-color: black;"); + headval->setAlignment(Qt::AlignCenter); + + mImpedanceGrid->addWidget(headlbl, 0, 0); + mImpedanceGrid->addWidget(headval, 0, 1); + + if (mBciDevice) + { + const uint32 numSensors = std::min(8U, mBciDevice->GetNumNeuroSensors()); + for (uint32_t i = 0; i < numSensors; i++) + { + Sensor* sensor = mBciDevice->GetNeuroSensor(i); + + // name label + QLabel* lbl = new QLabel(); + lbl->setFixedSize(lblsize); + lbl->setText(sensor->GetName()); + lbl->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + lbl->setAlignment(Qt::AlignCenter); + + if (Branding::HasColorCodesForChannels) + { + switch (i) + { + case 0: lbl->setStyleSheet("color: brown; background-color: black;"); break; + case 1: lbl->setStyleSheet("color: red; background-color: black;"); break; + case 2: lbl->setStyleSheet("color: orange; background-color: black;"); break; + case 3: lbl->setStyleSheet("color: yellow; background-color: black;"); break; + case 4: lbl->setStyleSheet("color: green; background-color: black;"); break; + case 5: lbl->setStyleSheet("color: blue; background-color: black;"); break; + case 6: lbl->setStyleSheet("color: purple; background-color: black;"); break; + case 7: lbl->setStyleSheet("color: grey; background-color: black;"); break; + default: lbl->setStyleSheet("color: white; background-color: black;"); break; + } + } + else + lbl->setStyleSheet("color: white; background-color: black;"); + + // value label + QLabel* val = new QLabel(); + val->setFixedSize(valsize); + val->setText(QString().sprintf("%5.1f", mBciDevice->GetImpedance(i))); + val->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + val->setAlignment(Qt::AlignCenter); + + mImpedanceGrid->addWidget(lbl, i+1, 0); + mImpedanceGrid->addWidget(val, i+1, 1); + } + + for (uint32_t i = 0; i < 3; i++) + { + // name label + QLabel* lbl = new QLabel(); + lbl->setFixedSize(lblsize); + lbl->setText("AVG"); + lbl->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + lbl->setAlignment(Qt::AlignCenter); + lbl->setStyleSheet("color: white; background-color: black;"); + + // name + switch (i) + { + case 0: lbl->setText("MIN"); break; + case 1: lbl->setText("AVG"); break; + case 2: lbl->setText("MAX"); break; + default: break; + } + + // value label + QLabel* val = new QLabel(); + val->setFixedSize(valsize); + val->setText(QString().sprintf("%5.1f", 0.0)); + val->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + val->setAlignment(Qt::AlignCenter); + val->setStyleSheet("background-color: black;"); + + mImpedanceGrid->addWidget(lbl, i+numSensors+1, 0); + mImpedanceGrid->addWidget(val, i+numSensors+1, 1); + } + } + + primaryLayout->addWidget(mImpedanceGridWidget); + + // electrode widget container + mEEGElectrodeWidgetContainer = new QWidget(this); + mEEGElectrodeWidgetContainer->setMinimumSize(256, 256); + mEEGElectrodeWidgetContainer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + // electrode widget - mEEGElectrodeWidget = new EEGElectrodesWidget(this); + mEEGElectrodeWidget = new EEGElectrodesWidget(mEEGElectrodeWidgetContainer); // set minimuim size and expanding - mEEGElectrodeWidget->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Fixed); + mEEGElectrodeWidget->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed); mEEGElectrodeWidget->setFixedSize(256,256); - // add it to primary layout - QVBoxLayout* primaryLayout = GetPrimaryDeviceInfoLayout(); - primaryLayout ->addWidget(mEEGElectrodeWidget); + primaryLayout ->addWidget(mEEGElectrodeWidgetContainer); mBciDevice = static_cast(mDevice); mEEGElectrodeWidget->SetDevice(mBciDevice); + + mImpedanceGridWidget->hide(); + mEEGElectrodeWidgetContainer->hide(); } @@ -97,6 +207,67 @@ void BciDeviceWidget::UpdateInterface() // update impedance test widget if (mDeviceTestWidget != NULL && mDeviceTestWidget->isVisible()) static_cast(mDeviceTestWidget)->UpdateInterface(); + + // update impedance grid + if (mBciDevice && mImpedanceGridWidget && mImpedanceGrid && mImpedanceGrid->rowCount() > 0) + { + Classifier* classifier = GetEngine()->GetActiveClassifier(); + mImpedanceGridWidget->setVisible(mBciDevice->HasEegContactQualityIndicator()); + mEEGElectrodeWidgetContainer->setVisible(mBciDevice->HasElectrodePositions()); + const uint32 numSensors = std::min( + (uint32)mImpedanceGrid->rowCount()-4U, + std::min(8U, mBciDevice->GetNumNeuroSensors())); + const uint32 numUsedSensors = classifier ? classifier->GetNumUsedSensors() : 0U; + double max = DBL_MIN; + double avg = 0.0; + double min = DBL_MAX; + uint32 cnt = 0; + for (uint32_t i = 0; i < numSensors; i++) + { + Sensor* sensor = mBciDevice->GetNeuroSensor(i); + double impedance = mBciDevice->GetImpedance(i); + Color color = sensor->GetContactQualityColor(); + const bool isused = classifier && classifier->IsSensorUsed(sensor); + + // value label in grid + QLabel* item = (QLabel*)mImpedanceGrid->itemAtPosition(i + 1, 1)->widget(); + + if (impedance >= 0.1) + item->setText(QString().sprintf("%5.1f", impedance)); + else + item->setText(""); + + if (numUsedSensors == 0 || isused) + { + if (impedance > 0.1) + { + max = impedance > max ? impedance : max; + min = impedance < min ? impedance : min; + avg += impedance; + cnt++; + } + } + else + color *= 0.4; // same factor as in EEGElectrodesWidget.cpp + + item->setStyleSheet(QString("color: black; background-color: %1;").arg( + color.ToHexString().AsChar())); + } + + if (cnt) avg /= cnt; + + QLabel* lblmin = (QLabel*)mImpedanceGrid->itemAtPosition(numSensors + 1, 1)->widget(); + if (min >= 0.1 && min < DBL_MAX) lblmin->setText(QString().sprintf("%5.1f", min)); + else lblmin->setText(""); + + QLabel* lblavg = (QLabel*)mImpedanceGrid->itemAtPosition(numSensors + 2, 1)->widget(); + if (avg >= 0.1) lblavg->setText(QString().sprintf("%5.1f", avg)); + else lblavg->setText(""); + + QLabel* lblmax = (QLabel*)mImpedanceGrid->itemAtPosition(numSensors + 3, 1)->widget(); + if (max >= 0.1) lblmax->setText(QString().sprintf("%5.1f", max)); + else lblmax->setText(""); + } } @@ -137,12 +308,12 @@ void BciDeviceWidget::AddSensorItems(QTreeWidgetItem* parent) // add signal quality subitem item = new QTreeWidgetItem(sensorItem); item->setText(0, "Signal Quality"); - item->setHidden(NEUROMORE_MINIMAL_DEVICE_STATS); + item->setHidden(Branding::HasMinimalDeviceInfo); // add current value subitem item = new QTreeWidgetItem(sensorItem); item->setText(0, "Current Value"); - item->setHidden(NEUROMORE_MINIMAL_DEVICE_STATS); + item->setHidden(Branding::HasMinimalDeviceInfo); // add sample rate subitem item = new QTreeWidgetItem(sensorItem); @@ -151,17 +322,16 @@ void BciDeviceWidget::AddSensorItems(QTreeWidgetItem* parent) // add drift correction subitem item = new QTreeWidgetItem(sensorItem); item->setText(0, "Drift (Corrected)"); - item->setHidden(NEUROMORE_MINIMAL_DEVICE_STATS); + item->setHidden(Branding::HasMinimalDeviceInfo); // add burst size subitem item = new QTreeWidgetItem(sensorItem); item->setText(0, "Max/Avg Burst Size"); - item->setHidden(NEUROMORE_MINIMAL_DEVICE_STATS); // add burst size subitem item = new QTreeWidgetItem(sensorItem); item->setText(0, "Max Latency"); - item->setHidden(NEUROMORE_MINIMAL_DEVICE_STATS); + item->setHidden(Branding::HasMinimalDeviceInfo); // add sample counter subitem item = new QTreeWidgetItem(sensorItem); @@ -174,7 +344,7 @@ void BciDeviceWidget::AddSensorItems(QTreeWidgetItem* parent) // add total memory subitem item = new QTreeWidgetItem(sensorItem); item->setText(0, "Memory Used"); - item->setHidden(NEUROMORE_MINIMAL_DEVICE_STATS); + item->setHidden(Branding::HasMinimalDeviceInfo); } } diff --git a/src/Studio/Plugins/Devices/BciDeviceWidget.h b/src/Studio/Plugins/Devices/BciDeviceWidget.h index bbe1daf56..092280f56 100644 --- a/src/Studio/Plugins/Devices/BciDeviceWidget.h +++ b/src/Studio/Plugins/Devices/BciDeviceWidget.h @@ -53,8 +53,11 @@ class BciDeviceWidget : public DeviceWidget private: BciDevice* mBciDevice; // reference to BciDevice + QWidget* mEEGElectrodeWidgetContainer; EEGElectrodesWidget* mEEGElectrodeWidget; // eeg sensor status - SignalQualityWidget* mSignalQualityWidget; + SignalQualityWidget* mSignalQualityWidget; + QWidget* mImpedanceGridWidget; + QGridLayout* mImpedanceGrid; }; #endif diff --git a/src/Studio/Plugins/Devices/DeviceWidget.cpp b/src/Studio/Plugins/Devices/DeviceWidget.cpp index cd0bcedbb..0df621f98 100644 --- a/src/Studio/Plugins/Devices/DeviceWidget.cpp +++ b/src/Studio/Plugins/Devices/DeviceWidget.cpp @@ -42,8 +42,7 @@ DeviceWidget::DeviceWidget(Device* device, QWidget* parent) : QWidget(parent) mDeviceTestWidget = NULL; mDevice = device; - - setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Maximum ); + setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding); } @@ -60,6 +59,7 @@ void DeviceWidget::Init() mLayout = new QGridLayout(); mLayout->setMargin(0); //mLayout->setSpacing(0); + mLayout->setAlignment(Qt::AlignTop); //mLayout->addWidget(QLabel( mDevice->GetName().AsChar() )); @@ -72,29 +72,34 @@ void DeviceWidget::Init() if (existsFileTest.exists() == true) { mDeviceIcon = new QLabel(); - mDeviceIcon->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + mDeviceIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); mLayout->addWidget(mDeviceIcon, 0, 0); // scale and apply icon QPixmap icon(iconFilename.AsChar()); - icon = icon.scaledToWidth(130, Qt::TransformationMode::SmoothTransformation); + icon = icon.scaledToWidth(160, Qt::TransformationMode::SmoothTransformation); mDeviceIcon->setPixmap(icon); } else mDeviceIcon = NULL; // middle: secondary device info widgets (battery, signal etc) - mSecondaryDeviceInfoLayout = new QVBoxLayout(); - mSecondaryDeviceInfoLayout->setMargin(0); + mSecondaryDeviceInfoLayout = new QHBoxLayout(); + mSecondaryDeviceInfoLayout->setContentsMargins(0, 8, 0, 8); + mSecondaryDeviceInfoLayout->setAlignment(Qt::AlignLeft); + mSecondaryDeviceInfoLayout->setSizeConstraint(QLayout::SetMinimumSize); mBatteryStatusWidget = new BatteryStatusWidget(this); mBatteryStatusWidget->setVisible(mDevice->HasBatteryIndicator()); + mSecondaryDeviceInfoLayout->addWidget(mBatteryStatusWidget); - mLayout->addLayout(mSecondaryDeviceInfoLayout, 1, 0); + mLayout->addLayout(mSecondaryDeviceInfoLayout, 1, 0, Qt::AlignLeft | Qt::AlignVCenter); // bottom: info/test buttons QSize buttonSize = QSize(65, 25); QHBoxLayout* buttonLayout = new QHBoxLayout(); + buttonLayout->setAlignment(Qt::AlignLeft); + buttonLayout->setSizeConstraint(QLayout::SetMinimumSize); mDeviceInfoButton = new QPushButton("Info"); mDeviceInfoButton->setCheckable(true); mDeviceInfoButton->setIcon(GetQtBaseManager()->FindIcon("/Images/Icons/Info.png")); @@ -115,29 +120,26 @@ void DeviceWidget::Init() mDeviceTestButton->setFixedSize(buttonSize); connect(mDeviceTestButton, SIGNAL(clicked()), this, SLOT(OnDeviceTestButtonPressed())); buttonLayout->addWidget(mDeviceTestButton); - InitDeviceTestWidget(); } else { mDeviceTestButton = NULL; } - - // add spacer widget - QWidget* spacerWidget = new QWidget(); - spacerWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Ignored); - buttonLayout->addWidget(spacerWidget); + + InitDeviceTestWidget(); // add hor. button layout to main layout - mLayout->addLayout(buttonLayout, 2, 0); + mLayout->addLayout(buttonLayout, 2, 0, Qt::AlignLeft); // 2) right half of widget area (primary device info) - mPrimaryDeviceInfoLayout = new QVBoxLayout(); + mPrimaryDeviceInfoLayout = new QHBoxLayout(); mPrimaryDeviceInfoLayout->setMargin(0); - mLayout->addLayout(mPrimaryDeviceInfoLayout, 0, 1, 3, 1); + mLayout->addLayout(mPrimaryDeviceInfoLayout, 0, 1, 4, 1, Qt::AlignTop); // 3) device information tree (lower half of main layout) mDeviceInfoTree = new QTreeWidget(); - mDeviceInfoTree->setFixedHeight(200); + mDeviceInfoTree->setMinimumHeight(70); + mDeviceInfoTree->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); mDeviceInfoTree->setSelectionMode( QAbstractItemView::SingleSelection); mDeviceInfoTree->setSortingEnabled(false); mDeviceInfoTree->setAlternatingRowColors(true); @@ -156,7 +158,7 @@ void DeviceWidget::Init() mDeviceInfoTree->hide(); // add tree - mLayout->addWidget(mDeviceInfoTree, 3, 0, 1, 2); + mLayout->addWidget(mDeviceInfoTree, 4, 0, 1, 2); setLayout(mLayout); } @@ -220,21 +222,24 @@ void DeviceWidget::InitDeviceInfoWidget() void DeviceWidget::InitDeviceTestWidget() { - if (mDevice->HasTestMode() == false || mDeviceTestWidget != NULL) + if ((!mDevice->HasTestMode() && !mDevice->IsTestRunning()) || mDeviceTestWidget != NULL) return; mDeviceTestWidget = CreateDeviceTestWidget(); - mLayout->addWidget(mDeviceTestWidget, 4, 0, 1, 2); + mLayout->addWidget(mDeviceTestWidget, 3, 0, 1, 1); - if (mDevice->IsTestRunning()) - { - mDeviceTestWidget->setVisible(true); - mDeviceTestButton->setText("Close"); - } - else + if (mDevice->HasTestMode()) { - mDeviceTestWidget->setVisible(false); - mDeviceTestButton->setText("Test"); + if (mDevice->IsTestRunning()) + { + mDeviceTestWidget->setVisible(true); + mDeviceTestButton->setText("Stop"); + } + else + { + mDeviceTestWidget->setVisible(false); + mDeviceTestButton->setText("Test"); + } } } @@ -246,12 +251,14 @@ void DeviceWidget::AddDeviceItems() item->setText(0, "Device Name"); item->setText(1, mDevice->GetName().AsChar()); mDeviceInfoTree->addTopLevelItem(item); + item->setHidden(Branding::HasMinimalDeviceInfo); // Memory usage item = new QTreeWidgetItem(); item->setText(0, "Memory Used"); item->setText(1, "0 KB"); mDeviceInfoTree->addTopLevelItem(item); + item->setHidden(Branding::HasMinimalDeviceInfo); } @@ -442,8 +449,10 @@ void DeviceWidget::OnDeviceInfoButtonPressed() mDeviceInfoTree->setVisible(mShowInfoWidget); // update Button text - if (mShowInfoWidget == true) + if (mShowInfoWidget == true) + { mDeviceInfoButton->setText("Close"); + } else mDeviceInfoButton->setText("Info"); @@ -487,7 +496,7 @@ void DeviceWidget::OnDeviceTestButtonPressed() if (mDevice->IsTestRunning()) { mDeviceTestWidget->setVisible(true); - mDeviceTestButton->setText("Close"); + mDeviceTestButton->setText("Stop"); } else { diff --git a/src/Studio/Plugins/Devices/DeviceWidget.h b/src/Studio/Plugins/Devices/DeviceWidget.h index e46cd86b0..ce4e740e6 100644 --- a/src/Studio/Plugins/Devices/DeviceWidget.h +++ b/src/Studio/Plugins/Devices/DeviceWidget.h @@ -73,8 +73,8 @@ class DeviceWidget : public QWidget // UI elements QGridLayout* mLayout; // main layout - QVBoxLayout* mPrimaryDeviceInfoLayout; // the right half of the device status panel - QVBoxLayout* mSecondaryDeviceInfoLayout; // the area on the left under the device icon + QHBoxLayout* mPrimaryDeviceInfoLayout; // the right half of the device status panel + QHBoxLayout* mSecondaryDeviceInfoLayout; // the area on the left under the device icon QLabel* mDeviceIcon; // photographic icon (large size) QPushButton* mDeviceInfoButton; // show/hide button for device information tree QPushButton* mDeviceTestButton; // show/hide button for device information test @@ -93,8 +93,8 @@ class DeviceWidget : public QWidget // shared ui elements QWidget* mDeviceTestWidget; - inline QVBoxLayout* GetPrimaryDeviceInfoLayout() { return mPrimaryDeviceInfoLayout; } - inline QVBoxLayout* GetSecondaryDeviceInfoLayout() { return mSecondaryDeviceInfoLayout; } + inline QHBoxLayout* GetPrimaryDeviceInfoLayout() { return mPrimaryDeviceInfoLayout; } + inline QHBoxLayout* GetSecondaryDeviceInfoLayout() { return mSecondaryDeviceInfoLayout; } void AddDeviceInfo (const char* name, QWidget* value); // helpers diff --git a/src/Studio/Plugins/Experience/ExperienceWidget.cpp b/src/Studio/Plugins/Experience/ExperienceWidget.cpp index 2a87511f7..60ed672d1 100644 --- a/src/Studio/Plugins/Experience/ExperienceWidget.cpp +++ b/src/Studio/Plugins/Experience/ExperienceWidget.cpp @@ -502,7 +502,7 @@ void ExperienceWidget::paintEvent(QPaintEvent* event) font.setPixelSize( textScaler ); painter.setFont( font ); - const int usedheight = MAINLAYOUTPADDING + mTextEdit->isVisible() ? mTextEdit->height() : 0; + const int usedheight = MAINLAYOUTPADDING + (mTextEdit->isVisible() ? mTextEdit->height() : 0); painter.drawText( QRect( 0, 0, width(), height()-usedheight), Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextWordWrap, mText ); painter.end(); diff --git a/src/Studio/Plugins/ExperienceSelection/ExperienceSelectionWidget.cpp b/src/Studio/Plugins/ExperienceSelection/ExperienceSelectionWidget.cpp index 349b756a8..247c2f76c 100644 --- a/src/Studio/Plugins/ExperienceSelection/ExperienceSelectionWidget.cpp +++ b/src/Studio/Plugins/ExperienceSelection/ExperienceSelectionWidget.cpp @@ -221,16 +221,19 @@ void ExperienceSelectionWidget::CreateWidgetsForFolders(bool downloadAssets) } } } else { - button->setText(item.GetName()); + + QLabel* lbl = new QLabel(button); + lbl->setWordWrap(true); + lbl->setAlignment(Qt::AlignCenter); + lbl->setGeometry(QRect(24, 30, tileSize-48, tileSize)); + lbl->setStyleSheet("QLabel { color: black; font-size: 18px; }"); + lbl->setText(item.GetName()); button->setStyleSheet(QString("QPushButton {" "width: %1px;" "height: %1px;" "border-image: url(:/Images/Icons/ExperienceFolder.png);" - "text-align: center bottom;" - "font-size: 18px;" - "color: #FFFFFF;" - "}").arg(tileSize - 18)); + "}").arg(tileSize)); } connect(button, &ImageButton::clicked, this, &ExperienceSelectionWidget::OnItemButtonClicked); diff --git a/src/Studio/Plugins/SessionControl/ClientInfoWidget.cpp b/src/Studio/Plugins/SessionControl/ClientInfoWidget.cpp index 7545c5d13..12a428212 100644 --- a/src/Studio/Plugins/SessionControl/ClientInfoWidget.cpp +++ b/src/Studio/Plugins/SessionControl/ClientInfoWidget.cpp @@ -47,9 +47,9 @@ bool ClientInfoWidget::Init() // clients table mClientTable = new QTableWidget(); - //mClientTable->setMaximumHeight(50); + mClientTable->setFixedHeight(100); //gridLayout->addWidget(new QLabel("Clients"), row, 0); - gridLayout->addWidget(mClientTable, row, 1); + gridLayout->addWidget(mClientTable, row, 1, Qt::AlignTop); row++; setLayout(gridLayout); diff --git a/src/Studio/Plugins/SessionControl/PreSessionWidget.cpp b/src/Studio/Plugins/SessionControl/PreSessionWidget.cpp index d925d2e77..cb5185789 100644 --- a/src/Studio/Plugins/SessionControl/PreSessionWidget.cpp +++ b/src/Studio/Plugins/SessionControl/PreSessionWidget.cpp @@ -43,14 +43,18 @@ void PreSessionWidget::Init() { setObjectName("TransparentWidget"); setMinimumHeight(mStartButtonSize); - setSizePolicy(QSizePolicy::Ignored, QSizePolicy::MinimumExpanding); + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->setMargin(0); setLayout(hLayout); + constexpr int labelwidth = 64; + // create the back to selection button - mBackToSelectionButton = new QPushButton("Back to selection"); + mBackToSelectionButton = new QPushButton("Back"); + mBackToSelectionButton->setFixedSize(QSize(mStartButtonSize, mStartButtonSize)); + hLayout->addWidget(mBackToSelectionButton); connect( mBackToSelectionButton, &QPushButton::clicked, this, [=] { @@ -69,21 +73,19 @@ void PreSessionWidget::Init() // ------------------------------------------ - QVBoxLayout* vLayout = new QVBoxLayout(); - vLayout->setMargin(0); - hLayout->addLayout(vLayout); - // right side of hLayout: grid layout - QGridLayout* gLayout = new QGridLayout(); + gLayout = new QGridLayout(); gLayout->setMargin(0); - vLayout->addLayout(gLayout); + + hLayout->addLayout(gLayout); // // first row of gridlayout // // add the level selection label - mVisSelectionLabel = new QLabel(" Visualization:"); + mVisSelectionLabel = new QLabel("Visualization:"); + mVisSelectionLabel->setFixedWidth(labelwidth); gLayout->addWidget( mVisSelectionLabel, 0, 0 ); // add the level selection button (for local ones) @@ -92,7 +94,7 @@ void PreSessionWidget::Init() if (GetManager()->GetVisualizationManager()->GetNumVisualizations() == 0) mVisSelectionButton->setEnabled(false); - mVisSelectionButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); + mVisSelectionButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); mVisSelectionButton->setIcon( GetQtBaseManager()->FindIcon("Images/Icons/Eye.png") ); connect( mVisSelectionButton, &QPushButton::clicked, this, &PreSessionWidget::OnSelectVisualizationClicked); gLayout->addWidget(mVisSelectionButton, 0, 1); @@ -100,68 +102,55 @@ void PreSessionWidget::Init() // // second row of gridlayout // -/* - // add the total time label - mTotalTimeLabel = new QLabel(" Duration:"); - gLayout->addWidget( mTotalTimeLabel, 1, 0 ); - - // row 1, col 1: horizontal layout: spinner+label - QHBoxLayout* hLayoutBottom = new QHBoxLayout(); - hLayoutBottom->setMargin(0); - gLayout->addLayout( hLayoutBottom, 1, 1 ); - - // add the total time spinbox - mTotalTimeSpinbox = new IntSpinBox(); - mTotalTimeSpinbox->setRange(0, INT_MAX); - const int defaultTotalTime = (int)GetEngine()->GetSession()->GetTotalTime().InSeconds(); - mTotalTimeSpinbox->setValue(defaultTotalTime); - hLayoutBottom->addWidget(mTotalTimeSpinbox); - connect( mTotalTimeSpinbox, SIGNAL(valueChanged(double)), this, SLOT(OnTotalTimeChanged(double)) ); - - // NOTE disabled it for now - mTotalTimeSpinbox->setEnabled(false); - - // add seconds label - mTotalTimeSecondsLabel = new QLabel(" seconds"); - hLayoutBottom->addWidget(mTotalTimeSecondsLabel); - - // add toolbar spacer widget - QWidget* spacerWidget = new QWidget(); - spacerWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); - hLayoutBottom->addWidget(spacerWidget); - */ + // add the level selection label + mElectrodeSelectionLabel = new QLabel("Channels:"); + mElectrodeSelectionLabel->setFixedWidth(labelwidth); + gLayout->addWidget( mElectrodeSelectionLabel, 1, 0 ); + + QHBoxLayout* chLayout = new QHBoxLayout(); + chLayout->setAlignment(Qt::AlignLeft); + + for (uint32_t i = 0; i < NUMELECTRODESELECT; i++) + { + mElectrodeSelections[i] = new QComboBox(); + mElectrodeSelections[i]->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + mElectrodeSelections[i]->setEditable(false); + mElectrodeSelections[i]->setAutoCompletion(false); + + connect(mElectrodeSelections[i], &QComboBox::currentTextChanged, this, &PreSessionWidget::OnChannelSelected); + chLayout->addWidget(mElectrodeSelections[i]); + } + gLayout->addLayout(chLayout, 1, 1, Qt::AlignLeft); + + if (Classifier* c = GetEngine()->GetActiveClassifier()) + UpdateChannels(c->FindMainChannelSelector()); + else + UpdateChannels(0); // // third row of gridlayout // // add the user label - mSelectUserLabel = new QLabel(" User:"); - gLayout->addWidget( mSelectUserLabel, 1, 0 ); + mSelectUserLabel = new QLabel("User:"); + mSelectUserLabel->setFixedWidth(labelwidth); + gLayout->addWidget( mSelectUserLabel, 2, 0 ); // add select user button - mSelectUserButton = new QPushButton(GetUser()->CreateDisplayableName().AsChar()); - mSelectUserButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); + mSelectUserButton = new QPushButton(GetSessionUser()->CreateDisplayableName().AsChar()); + mSelectUserButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); mSelectUserButton->setIcon( GetQtBaseManager()->FindIcon("Images/Icons/Users.png") ); connect( mSelectUserButton, &QPushButton::clicked, this, &PreSessionWidget::OnSelectUserClicked); - gLayout->addWidget( mSelectUserButton, 1, 1 ); + gLayout->addWidget( mSelectUserButton, 2, 1); // show report button mShowReportButton = new QPushButton("Show Report"); + mShowReportButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); mShowReportButton->setIcon(GetQtBaseManager()->FindIcon("/Images/Icons/Report.png")); mShowReportButton->setVisible(false); - vLayout->addWidget(mShowReportButton); + gLayout->addWidget(mShowReportButton, 3, 0, 1, 2); connect( mShowReportButton, SIGNAL(clicked()), mPlugin, SLOT(ShowReport()) ); - - if (GetUser()->FindRule("STUDIO_SETTING_EasyWorkflow") != NULL) - { - mVisSelectionLabel->hide(); - mVisSelectionButton->hide(); - - mSelectUserLabel->hide(); - mSelectUserButton->hide(); - } } @@ -257,12 +246,98 @@ void PreSessionWidget::ReInit() } -void PreSessionWidget::ShowSelectUserButton(bool show) +void PreSessionWidget::UpdateChannels(ChannelSelectorNode* chs) { - mSelectUserLabel->setVisible(show); - mSelectUserButton->setVisible(show); + // update comboboxes values + for (uint32_t i = 0; i < NUMELECTRODESELECT; i++) + { + QComboBox* box = mElectrodeSelections[i]; + + // block signals + const bool oldState = box->blockSignals(true); + + // clear first + box->clear(); + + // enable/disable and try set value from channelselector output + if (chs && chs->GetNumInputPorts() && chs->GetNumOutputPorts()) + { + const bool singleoutput = chs->GetBoolAttribute(ChannelSelectorNode::ATTRIB_SINGLE_OUTPUT); + + InputPort& firstportin = chs->GetInputPort(0); + OutputPort& firstportout = chs->GetOutputPort(0); + + MultiChannel* firstmchin = firstportin.GetChannels(); + MultiChannel* firstmchout = firstportout.GetChannels(); + + // fill choices from first single input multichannel + if (firstmchin) + { + uint32 numchs = firstmchin->GetNumChannels(); + QStringList qs; + for (uint32_t j = 0; j < numchs; j++) + qs.push_back(firstmchin->GetChannel(j)->GetName()); + box->addItems(qs); + } + + // set selected text from channel name in multichannel + if (singleoutput && firstmchout && i < firstmchout->GetNumChannels()) + { + box->setCurrentText(firstmchout->GetChannel(i)->GetName()); + box->setEnabled(true); + } + // set selected text from port name + else if (!singleoutput && i < chs->GetNumOutputPorts()) + { + box->setCurrentText(chs->GetOutputPort(i).GetName()); + box->setEnabled(true); + } + else + { + box->setCurrentText(""); + box->setEnabled(false); + } + } + else + { + box->setCurrentText(""); + box->setEnabled(false); + } + + // restore signals + box->blockSignals(oldState); + } } +Core::String PreSessionWidget::GetSelectedChannels() +{ + Core::String s; + for (uint32_t i = 0; i < NUMELECTRODESELECT; i++) + { + if (!mElectrodeSelections[i]->isEnabled()) + continue; + + if (!s.IsEmpty()) + s += ','; + + const QString& cur = mElectrodeSelections[i]->currentText(); + + if (cur.isEmpty()) + return ""; + + s += cur.toLatin1().data(); + } + return s; +} + +void PreSessionWidget::OnChannelSelected(const QString& text) +{ + QComboBox* box = (QComboBox*)QObject::sender(); + Core::String chs = GetSelectedChannels(); + if (chs.IsEmpty()) + return; + emit SelectedChannelsChanged(); +} // called when the total session time got changed void PreSessionWidget::OnTotalTimeChanged(double value) diff --git a/src/Studio/Plugins/SessionControl/PreSessionWidget.h b/src/Studio/Plugins/SessionControl/PreSessionWidget.h index 65d5fb9a0..e9fdae56a 100644 --- a/src/Studio/Plugins/SessionControl/PreSessionWidget.h +++ b/src/Studio/Plugins/SessionControl/PreSessionWidget.h @@ -45,6 +45,8 @@ class PreSessionWidget : public QWidget { Q_OBJECT public: + static constexpr const uint32_t NUMELECTRODESELECT = 4; + PreSessionWidget(SessionControlPlugin* plugin, QWidget* parent, int buttonSize); virtual ~PreSessionWidget() {} @@ -55,20 +57,31 @@ class PreSessionWidget : public QWidget QPushButton* GetShowReportButton() { return mShowReportButton; } QPushButton* GetSelectUserButton() { return mSelectUserButton; } - void ShowSelectUserButton(bool show = true); + void UpdateChannels(ChannelSelectorNode* chs = 0); + Core::String GetSelectedChannels(); + inline QString GetSelectedChannel(uint32 idx) { return idx < NUMELECTRODESELECT ? mElectrodeSelections[idx]->currentText() : ""; } private slots: void OnTotalTimeChanged(double value); void OnSelectVisualizationClicked(); void OnSelectUserClicked() { GetMainWindow()->SelectSessionUser(); } + void OnChannelSelected(const QString& text); + + signals: + void SelectedChannelsChanged(); private: SessionControlPlugin* mPlugin; ImageButton* mStartButton; uint32 mStartButtonSize; + QGridLayout* gLayout; + QPushButton* mBackToSelectionButton; + QLabel* mElectrodeSelectionLabel; + QComboBox* mElectrodeSelections[NUMELECTRODESELECT]; + QLabel* mVisSelectionLabel; QPushButton* mVisSelectionButton; diff --git a/src/Studio/Plugins/SessionControl/SessionControlPlugin.cpp b/src/Studio/Plugins/SessionControl/SessionControlPlugin.cpp index 39e9243c6..e6fe48f25 100644 --- a/src/Studio/Plugins/SessionControl/SessionControlPlugin.cpp +++ b/src/Studio/Plugins/SessionControl/SessionControlPlugin.cpp @@ -66,63 +66,65 @@ bool SessionControlPlugin::Init() QWidget* mainWidget = new QWidget(); QVBoxLayout* mainLayout = new QVBoxLayout(); + mainLayout->setAlignment(Qt::AlignTop); mainWidget->setLayout( mainLayout ); + mainWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); const int32 buttonSize = IMAGEBUTTONSIZE_BIG; const int32 startButtonSize = IMAGEBUTTONSIZE_BIG; - // create the dummy widget and a layout - QWidget* dummyWidget = new QWidget(); - QVBoxLayout* vLayout = new QVBoxLayout(); QHBoxLayout* hLayout = new QHBoxLayout(); - vLayout->setMargin(0); + hLayout->setAlignment(Qt::AlignTop); hLayout->setMargin(0); - vLayout->addLayout(hLayout); - dummyWidget->setLayout(vLayout); // pre-session widget mPreSessionWidget = new PreSessionWidget(this, NULL, startButtonSize); + mPreSessionWidget->setFixedHeight(128); + mPreSessionWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); mPreSessionWidget->ReInit(); + hLayout->addWidget(mPreSessionWidget); connect(mPreSessionWidget->GetStartButton(), &QPushButton::clicked, this, &SessionControlPlugin::OnStart); - - // show user button only if user is allowed to select a client - if (GetUser()->FindRule("STUDIO_SETTING_SelectUser") == NULL) - mPreSessionWidget->ShowSelectUserButton(false); + connect(mPreSessionWidget, &PreSessionWidget::SelectedChannelsChanged, this, &SessionControlPlugin::OnSelectedChannelsChanged); // while-session widget mWhileSessionWidget = new WhileSessionWidget(NULL, buttonSize); + mWhileSessionWidget->setFixedHeight(128); + mWhileSessionWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); hLayout->addWidget(mWhileSessionWidget); connect( mWhileSessionWidget->GetStopButton(), SIGNAL(clicked()), this, SLOT(OnStop()) ); connect( mWhileSessionWidget->GetPauseButton(), SIGNAL(clicked()), this, SLOT(OnPause()) ); connect( mWhileSessionWidget->GetContinueButton(), SIGNAL(clicked()), this, SLOT(OnContinue()) ); + mainLayout->addLayout(hLayout); + + hLayout = new QHBoxLayout(); + hLayout->setAlignment(Qt::AlignTop); + // session-info widget mSessionInfoWidget = new SessionInfoWidget(); - mSessionInfoWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); - vLayout->addWidget(mSessionInfoWidget); + mSessionInfoWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); + hLayout->addWidget(mSessionInfoWidget); + + mainLayout->addLayout(hLayout); // add session widget - dummyWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); - mainLayout->addWidget(dummyWidget); + hLayout = new QHBoxLayout(); + hLayout->setAlignment(Qt::AlignTop); // add stage widget - mStageControlWidget = new StageControlWidget(mainWidget); - mStageControlWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); - mainLayout->addWidget(mStageControlWidget); + mStageControlWidget = new StageControlWidget(0); + mStageControlWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); + hLayout->addWidget(mStageControlWidget); + mainLayout->addLayout(hLayout); // add client info widget #ifndef PRODUCTION_BUILD - mClientInfoWidget = new ClientInfoWidget(mainWidget); - mClientInfoWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); + mClientInfoWidget = new ClientInfoWidget(); + mClientInfoWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); mainLayout->addWidget(mClientInfoWidget); #endif - // add spacer widget - QWidget* spacerWidget = new QWidget(mainWidget); - spacerWidget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::MinimumExpanding); - mainLayout->addWidget(spacerWidget); - // connect to server signals so the interface gets updated NetworkServer* networkServer = GetNetworkServer(); connect( networkServer, SIGNAL( ClientAdded ( NetworkServerClient* ) ), this, SLOT( OnClientChanged( NetworkServerClient* ) ) ); @@ -227,8 +229,8 @@ void SessionControlPlugin::UpdateWidgets() const bool isRunning = GetEngine()->GetSession()->IsRunning(); if (isRunning || isPreparing) { - mWhileSessionWidget->show(); mPreSessionWidget->hide(); + mWhileSessionWidget->show(); } else { @@ -263,6 +265,44 @@ void SessionControlPlugin::OnSessionUserChanged(const User& user) userButton->setText(name.AsChar()); } +void SessionControlPlugin::OnActiveBciChanged(BciDevice* device) +{ + +} + +void SessionControlPlugin::OnActiveClassifierChanged(Classifier* classifier) +{ + UpdateStartButton(); + mPreSessionWidget->UpdateChannels(0); +} + +void SessionControlPlugin::OnNodeStarted(Graph* graph, SPNode* node) +{ + if (mPreSessionWidget) + if (node->GetType() == ChannelSelectorNode::TYPE_ID) + if (node->GetBoolAttribute(ChannelSelectorNode::ATTRIB_QUICK_CONFIG)) + mPreSessionWidget->UpdateChannels((ChannelSelectorNode*)node); +} + +void SessionControlPlugin::OnRemoveNode(Graph* graph, Node* node) +{ + if (mPreSessionWidget) + if (node->GetType() == ChannelSelectorNode::TYPE_ID) + if (node->GetBoolAttribute(ChannelSelectorNode::ATTRIB_QUICK_CONFIG)) + mPreSessionWidget->UpdateChannels(0); +} + +void SessionControlPlugin::OnAttributeUpdated(Graph* graph, GraphObject* object, Core::Attribute* attribute) +{ + if (mPreSessionWidget) + if (object->GetType() == ChannelSelectorNode::TYPE_ID) + if (attribute == object->GetAttributeValue(ChannelSelectorNode::ATTRIB_QUICK_CONFIG)) + if (object->GetBoolAttribute(ChannelSelectorNode::ATTRIB_QUICK_CONFIG)) + mPreSessionWidget->UpdateChannels((ChannelSelectorNode*)object); + else + mPreSessionWidget->UpdateChannels(0); +} + void SessionControlPlugin::OnRemoveDevice(Device* device) { if (GetSession()->IsRunning() == true) @@ -337,6 +377,10 @@ void SessionControlPlugin::OnMessageReceived( NetworkServerClient* client, Netwo delete message; } +void SessionControlPlugin::OnSelectedChannelsChanged() +{ + ApplySettings(); +} void SessionControlPlugin::CheckStartRequirements() { @@ -360,6 +404,14 @@ void SessionControlPlugin::CheckStartRequirements() const bool deviceBatteryLow = (GetDeviceManager()->IsDevicePowerOk() == false); const bool deviceMissing = (classifier != NULL && classifier->HasError (DeviceInputNode::ERROR_DEVICE_NOT_FOUND) == true); + + // not runninng session as patient + const char* noPatientInfo = "No patient selected."; + if (GetUser()->GetIdString() == GetSessionUser()->GetIdString() && (GetUser()->FindRule("ROLE_ClinicClinician") || GetUser()->FindRule("ROLE_ClinicOperator"))) + mSessionInfoWidget->ShowInfo(SessionInfoWidget::TYPE_WARNING, noPatientInfo, "Please select a patient before starting a session."); + else + mSessionInfoWidget->RemoveInfo(noPatientInfo); + // classifier or statemachine has error const bool designError = (classifier != NULL && classifier->HasError() == true) || (stateMachine != NULL && stateMachine->HasError() == true); @@ -381,11 +433,11 @@ void SessionControlPlugin::CheckStartRequirements() mSessionInfoWidget->RemoveInfo( noPermissionInfo ); // device test - const char* testModeInfo = "Device is in test mode"; + /*const char* testModeInfo = "Device is in test mode"; if (deviceTestRunning == true) mSessionInfoWidget->ShowInfo( SessionInfoWidget::TYPE_INFO, testModeInfo, "Please stop the device test first."); else - mSessionInfoWidget->RemoveInfo( testModeInfo ); + mSessionInfoWidget->RemoveInfo( testModeInfo );*/ // device not connected const char* noDevicePower = "Device battery is almost empty"; @@ -456,6 +508,9 @@ void SessionControlPlugin::OnStart() StateMachine* activeStateMachine = GetEngine()->GetActiveStateMachine(); Experience* activeExperience = GetEngine()->GetActiveExperience(); + if (!activeClassifier) + return; + // gather all errors an compose them into a string /* if (activeClassifier != NULL && activeClassifier->HasError() == true) { @@ -483,15 +538,18 @@ void SessionControlPlugin::OnStart() // BEGIN + // make sure no session is running + CORE_ASSERT(GetSession()->IsRunning() == false); + // switch to first stage EMIT_EVENT( OnSwitchStage(0) ); + // + ApplySettings(); + // TODO do checks on statemachine / experience? mPreSessionWidget->setEnabled(false); - // make sure no session is running - CORE_ASSERT( GetSession()->IsRunning() == false ); - GetEngine()->Reset(); // reset engine (also resets session) GetEngine()->SoftPause(); // but keep it paused @@ -530,6 +588,41 @@ void SessionControlPlugin::OnParametersLoaded(bool success) } +void SessionControlPlugin::ApplySettings() +{ + Classifier* activeClassifier = GetEngine()->GetActiveClassifier(); + StateMachine* activeStateMachine = GetEngine()->GetActiveStateMachine(); + Experience* activeExperience = GetEngine()->GetActiveExperience(); + + if (!activeClassifier) + return; + + // get selected channels for session before disabling ui + const Core::String selectedChannels = mPreSessionWidget->GetSelectedChannels(); + + // apply channel selection on main channel selector if found + if (ChannelSelectorNode* chselector = activeClassifier->FindMainChannelSelector()) + { + // remember + const bool dirty = activeClassifier->IsDirty(); + const bool settingsmode = activeClassifier->GetUseSettings(); + + // clone classifier settings from experience or create new + GraphSettings set = activeExperience ? activeExperience->GetClassifierSettings() : GraphSettings(); + + // set the channels on the main selector node + AttributeSettings* attrib = chselector->GetAttribute(ChannelSelectorNode::ATTRIB_CHANNELNAMES); + set.Add(chselector, attrib->GetInternalName(), selectedChannels); + + + // apply modified settings and restore state + activeClassifier->ApplySettings(set); + activeClassifier->SetIsDirty(dirty); + activeClassifier->SetUseSettings(settingsmode); + } +} + + void SessionControlPlugin::Start() { GetSession()->Reset(); @@ -751,7 +844,7 @@ void SessionControlPlugin::ShowReport() // called after switching layout void SessionControlPlugin::OnAfterLoadLayout() { - if (GetUser()->FindRule("STUDIO_SETTING_EasyWorkflow") != NULL) + /*if (GetUser()->FindRule("STUDIO_SETTING_EasyWorkflow") != NULL) { // open visualization select window if one is available and none is running VisualizationManager* vizManager = GetManager()->GetVisualizationManager(); @@ -760,5 +853,5 @@ void SessionControlPlugin::OnAfterLoadLayout() VisualizationSelectWindow selectVizWindow(GetMainWindow()); selectVizWindow.exec(); } - } + }*/ } diff --git a/src/Studio/Plugins/SessionControl/SessionControlPlugin.h b/src/Studio/Plugins/SessionControl/SessionControlPlugin.h index cf16b0942..570e90a96 100644 --- a/src/Studio/Plugins/SessionControl/SessionControlPlugin.h +++ b/src/Studio/Plugins/SessionControl/SessionControlPlugin.h @@ -72,14 +72,20 @@ class SessionControlPlugin : public Plugin, private Core::EventSource, public Co // update widget visibility void UpdateWidgets(); + // apply quick settings from ui on nodes + void ApplySettings(); // EVENTS void OnPreparedSession() override final; void OnSessionUserChanged(const User& user) override final; + void OnActiveBciChanged(BciDevice* device) override final; void OnActiveExperienceChanged(Experience* experience) override final { UpdateStartButton(); } - void OnActiveClassifierChanged(Classifier* experience) override final { UpdateStartButton(); } + void OnActiveClassifierChanged(Classifier* classifier) override final; void OnActiveStateMachineChanged(StateMachine* experience) override final { UpdateStartButton(); } + void OnNodeStarted(Graph* graph, SPNode* node) override final; + void OnRemoveNode(Graph* graph, Node* node) override final; void OnRemoveDevice(Device* device) override final; + void OnAttributeUpdated(Graph* graph, GraphObject* object, Core::Attribute* attribute) override final; public slots: void ShowReport(); @@ -89,6 +95,7 @@ class SessionControlPlugin : public Plugin, private Core::EventSource, public Co void OnStop(); void OnPause(); void OnContinue(); + void OnSelectedChannelsChanged(); // client and network message callabacks void OnClientChanged ( NetworkServerClient* client ); diff --git a/src/Studio/Plugins/SessionControl/SessionInfoWidget.cpp b/src/Studio/Plugins/SessionControl/SessionInfoWidget.cpp index d9b6b12be..575af105c 100644 --- a/src/Studio/Plugins/SessionControl/SessionInfoWidget.cpp +++ b/src/Studio/Plugins/SessionControl/SessionInfoWidget.cpp @@ -40,6 +40,7 @@ SessionInfoWidget::SessionInfoWidget(QWidget* parent) : QWidget(parent) mGridLayout = new QGridLayout(); mGridLayout->setColumnStretch(1,100); + mGridLayout->setAlignment(Qt::AlignTop); setLayout(mGridLayout); // load icons @@ -252,7 +253,7 @@ void SessionInfoWidget::UpdateLayout() // add to row i mGridLayout->addWidget(info.mIcon, i, 0); - mGridLayout->addWidget(info.mLabel, i, 1, Qt::AlignLeft); + mGridLayout->addWidget(info.mLabel, i, 1); } if (numInfos > 0) diff --git a/src/Studio/Plugins/SessionControl/StageControlWidget.cpp b/src/Studio/Plugins/SessionControl/StageControlWidget.cpp index bde51a5b0..d1364e64e 100644 --- a/src/Studio/Plugins/SessionControl/StageControlWidget.cpp +++ b/src/Studio/Plugins/SessionControl/StageControlWidget.cpp @@ -63,10 +63,11 @@ bool StageControlWidget::Init() mStageTable->setSelectionBehavior( QAbstractItemView::SelectRows ); mStageTable->setSelectionMode( QAbstractItemView::NoSelection ); //mStageTable->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Minimum); + mStageTable->setFixedHeight(80); connect( mStageTable, SIGNAL( cellDoubleClicked ( int, int ) ), this, SLOT( OnStageTableDoubleClick( int, int ) ) ); - gridLayout->addWidget(mStageTable, row, 0); + gridLayout->addWidget(mStageTable, row, 0, Qt::AlignTop); row++; setLayout(gridLayout); diff --git a/src/Studio/Plugins/SessionControl/WhileSessionWidget.cpp b/src/Studio/Plugins/SessionControl/WhileSessionWidget.cpp index 9fa7a549c..dd4fd6e92 100644 --- a/src/Studio/Plugins/SessionControl/WhileSessionWidget.cpp +++ b/src/Studio/Plugins/SessionControl/WhileSessionWidget.cpp @@ -35,7 +35,7 @@ WhileSessionWidget::WhileSessionWidget(QWidget* parent, int buttonSize) : QWidge setObjectName("TransparentWidget"); setMinimumHeight(buttonSize); - setSizePolicy(QSizePolicy::Ignored, QSizePolicy::MinimumExpanding); + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); QVBoxLayout* mainLayout = new QVBoxLayout(); mainLayout->setMargin(0); diff --git a/src/Studio/Version.h b/src/Studio/Version.h index c33a8c95b..5e8f50e50 100644 --- a/src/Studio/Version.h +++ b/src/Studio/Version.h @@ -26,7 +26,7 @@ #define NEUROMORE_STUDIO_VERSION_MAJOR 1 #define NEUROMORE_STUDIO_VERSION_MINOR 7 -#define NEUROMORE_STUDIO_VERSION_PATCH 1 +#define NEUROMORE_STUDIO_VERSION_PATCH 2 // Macros diff --git a/src/Studio/Widgets/BatteryStatusWidget.cpp b/src/Studio/Widgets/BatteryStatusWidget.cpp index a14a8f8a0..9b0b70e96 100644 --- a/src/Studio/Widgets/BatteryStatusWidget.cpp +++ b/src/Studio/Widgets/BatteryStatusWidget.cpp @@ -32,7 +32,7 @@ using namespace Core; // constructor BatteryStatusWidget::BatteryStatusWidget(QWidget* parent) : QWidget(parent) { - mPixmapHeight = 24; + mPixmapHeight = 18; setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum ); @@ -57,7 +57,7 @@ BatteryStatusWidget::BatteryStatusWidget(QWidget* parent) : QWidget(parent) mTextLabel = new QLabel(); mTextLabel->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); QFont timeFont = mTextLabel->font(); - timeFont.setPixelSize(20); + timeFont.setPixelSize(14); mTextLabel->setFont(timeFont); mainLayout->addWidget( mTextLabel, 0, Qt::AlignLeft | Qt::AlignHCenter ); @@ -153,7 +153,7 @@ void BatteryStatusWidget::SetStatus(double normalizedLevel, EStatus status) if (mBatteryStatus == STATUS_UNKNOWN) mTempString = ""; else - mTempString.Format( "%.0f %%", mBatteryLevel * 100.0 ); + mTempString.Format( "%.0f%%", mBatteryLevel * 100.0 ); mTextLabel->setText( mTempString.AsChar() ); diff --git a/src/Studio/Widgets/ImpedanceTestWidget.cpp b/src/Studio/Widgets/ImpedanceTestWidget.cpp index a2efdef67..8b857de53 100644 --- a/src/Studio/Widgets/ImpedanceTestWidget.cpp +++ b/src/Studio/Widgets/ImpedanceTestWidget.cpp @@ -38,12 +38,14 @@ ImpedanceTestWidget::Threshold ImpedanceTestWidget::Thresholds[] = { // constructor ImpedanceTestWidget::ImpedanceTestWidget(BciDevice* device, QWidget* parent) : QWidget(parent), - mImpedanceThreshold(&ImpedanceTestWidget::Thresholds[DEFAULTPROFILE]) + mImpedanceThreshold(&ImpedanceTestWidget::Thresholds[DEFAULTPROFILE]), + mPixmapPass(GetQtBaseManager()->FindIcon("Images/Icons/SuccessCircled.png").pixmap(20, 20)), + mPixmapFail(GetQtBaseManager()->FindIcon("Images/Icons/FailCircled.png").pixmap(20, 20)) { mDevice = device; // expand width but not height - setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); + setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum); // add the main widget @@ -53,7 +55,7 @@ ImpedanceTestWidget::ImpedanceTestWidget(BciDevice* device, QWidget* parent) : Q vMainLayout->setMargin(4); vMainLayout->addWidget(mainWidget); setLayout(vMainLayout); - vMainLayout->setAlignment(Qt::AlignLeft); + vMainLayout->setAlignment(Qt::AlignLeft | Qt::AlignTop); // Main Grid Layout // add the main widget w/ grid layout @@ -61,65 +63,38 @@ ImpedanceTestWidget::ImpedanceTestWidget(BciDevice* device, QWidget* parent) : Q mainLayout->setMargin(2); mainWidget->setLayout(mainLayout); - // Column 1 + // Row 1 QLabel* label = new QLabel("Impedance Test"); mainLayout->addWidget(label, 0, 0, 1, 2); + // Row 2 label = new QLabel("Threshold:"); - mainLayout->addWidget(label, 1, 0, 2, 1); + mainLayout->addWidget(label, 1, 0, 1, 1); mThresholdComboBox = new QComboBox(); - mThresholdComboBox->setEditable(true); + mThresholdComboBox->setEditable(false); mThresholdComboBox->setMaxCount(NUMPROFILES); for (size_t i = 0; i < NUMPROFILES; i++) mThresholdComboBox->addItem(Thresholds[i].name); connect(mThresholdComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(OnThresholdSelected(int))); connect(mThresholdComboBox, SIGNAL(editTextChanged(const QString&)), this, SLOT(OnThresholdEdited(const QString&))); - mThresholdComboBox->setFixedWidth(75); - mainLayout->addWidget(mThresholdComboBox, 1, 1, 2, 1); + mThresholdComboBox->setFixedWidth(100); + mainLayout->addWidget(mThresholdComboBox, 1, 1, 1, 1); // select default threshold mThresholdComboBox->setCurrentIndex(DEFAULTPROFILE); OnThresholdSelected(DEFAULTPROFILE); - // Column 2 - // expanding spacer widget - QWidget* spacerWidget = new QWidget(); - spacerWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); - mainLayout->addWidget(spacerWidget, 0, 2, 3, 1); - - // Column 3 - // min/max/average - label = new QLabel("Min:"); mainLayout->addWidget(label, 0, 3); - label = new QLabel("Max:"); mainLayout->addWidget(label, 1, 3); - label = new QLabel("Avg:"); mainLayout->addWidget(label, 2, 3); - - mMinImpedanceLabel = new QLabel("-"); mainLayout->addWidget(mMinImpedanceLabel, 0, 4); - mMaxImpedanceLabel = new QLabel("-"); mainLayout->addWidget(mMaxImpedanceLabel, 1, 4); - mAvgImpedanceLabel = new QLabel("-"); mainLayout->addWidget(mAvgImpedanceLabel, 2, 4); - - // Column 4 - // fixed size spacer widget - spacerWidget = new QWidget(); - spacerWidget->setFixedWidth(15); - mainLayout->addWidget(spacerWidget, 0, 5, 3, 1); - - // Column 5 + // Row 3 + QLabel* passedlbl = new QLabel("Passed:"); + mainLayout->addWidget(passedlbl, 2, 0, 1, 1); + // Pass/Fail icon - mPassIconLabel = new QLabel(); - mFailIconLabel = new QLabel(); - const int pixmapSize = 50; - mPassIconLabel->setPixmap(GetQtBaseManager()->FindIcon("Images/Icons/SuccessCircled.png").pixmap(pixmapSize, pixmapSize)); - mFailIconLabel->setPixmap(GetQtBaseManager()->FindIcon("Images/Icons/FailCircled.png").pixmap(pixmapSize, pixmapSize)); - mPassIconLabel->setVisible(false); - mFailIconLabel->setVisible(false); - mPassIconLabel->setToolTip("Passed"); - mFailIconLabel->setVisible("Failed"); - - QHBoxLayout* hLayout = new QHBoxLayout(); - hLayout->addWidget(mPassIconLabel); - hLayout->addWidget(mFailIconLabel); - mainLayout->addLayout(hLayout, 0, 6, 3, 1); + mTestLabel = new QLabel(); + mTestLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + //mTestLabel->setAlignment(Qt::AlignCenter); + //mTestLabel->setStyleSheet("color: white; background-color: black;"); + mainLayout->addWidget(mTestLabel, 2, 1, 1, 1); } @@ -131,94 +106,47 @@ ImpedanceTestWidget::~ImpedanceTestWidget() void ImpedanceTestWidget::UpdateInterface() { - Classifier* activeClassifier = GetEngine()->GetActiveClassifier(); - - // calc min/max/avg impedance accross sensors - - // max impedance, if the values is larger we assume the lead is unconnected and ignore the electrode - const double electrodeActiveDetectionThreshold = 200; // 200 kiloohms - - // note: impedance values are transported via the EEG sensors - double minImpedance = DBL_MAX; - double maxImpedance = -DBL_MAX; - double avgImpedance = 0.0; - uint32 avgCount = 0; - - bool testPassed = true; - - const uint32 numSensors = mDevice->GetNumNeuroSensors(); - for (uint32 i = 0; i < numSensors; ++i) - { - const double impedance = mDevice->GetImpedance(i); - - // skip invalid values (Eccept exactly 0.0), dont skip used electrodes - if (activeClassifier == NULL || (activeClassifier != NULL && activeClassifier->IsSensorUsed(mDevice->GetSensor(i)) == false)) - if (impedance > electrodeActiveDetectionThreshold || impedance <= 0.0) - continue; - - minImpedance = Min(minImpedance, impedance); - maxImpedance = Max(maxImpedance, impedance); - avgImpedance += impedance; - avgCount++; - } - - - // no active electrode: test failed - if (avgCount == 0) - { - mMinImpedanceLabel->setText("-"); - mMaxImpedanceLabel->setText("-"); - mAvgImpedanceLabel->setText("-"); - - testPassed = false; - - // set sensor contact quality to no-signal - for (uint32 i = 0; i < numSensors; ++i) - mDevice->GetNeuroSensor(i)->SetContactQuality(Sensor::CONTACTQUALITY_NO_SIGNAL); - } - else - { - avgImpedance /= (double)avgCount; - - // update min/max/avg labels - mTempString.Format("%.2f k", minImpedance); - mMinImpedanceLabel->setText(mTempString.AsChar()); - mTempString.Format("%.2f k", maxImpedance); - mMaxImpedanceLabel->setText(mTempString.AsChar()); - mTempString.Format("%.2f k", avgImpedance); - mAvgImpedanceLabel->setText(mTempString.AsChar()); - - // test passed? - testPassed = (maxImpedance <= mImpedanceThreshold->yellow); - - // set sensor contact quality (reuse electrode widget) to indicate pass/fail of individual electrodes - for (uint32 i = 0; i < numSensors; ++i) - { - const double impedance = mDevice->GetImpedance(i); - - if (impedance < 0.1) // == 0.0 case - mDevice->GetNeuroSensor(i)->SetContactQuality(Sensor::CONTACTQUALITY_NO_SIGNAL); - - else if (impedance <= mImpedanceThreshold->green) - mDevice->GetNeuroSensor(i)->SetContactQuality(Sensor::CONTACTQUALITY_GOOD); - - else if (impedance <= mImpedanceThreshold->yellow) - mDevice->GetNeuroSensor(i)->SetContactQuality(Sensor::CONTACTQUALITY_FAIR); - - else if (impedance <= mImpedanceThreshold->orange) - mDevice->GetNeuroSensor(i)->SetContactQuality(Sensor::CONTACTQUALITY_POOR); - - else if (impedance <= mImpedanceThreshold->red) - mDevice->GetNeuroSensor(i)->SetContactQuality(Sensor::CONTACTQUALITY_VERY_BAD); - - else - mDevice->GetNeuroSensor(i)->SetContactQuality(Sensor::CONTACTQUALITY_NO_SIGNAL); - } - } - - // update fail/passed icon - mPassIconLabel->setVisible(testPassed); - mFailIconLabel->setVisible(!testPassed); + constexpr double minthreshold = 0.1; + + Classifier* classifier = GetEngine()->GetActiveClassifier(); + const uint32 numSensors = mDevice->GetNumNeuroSensors(); + const uint32 numUsedSensors = classifier ? classifier->GetNumUsedSensors() : 0U; + + bool testok = true; + + for (uint32 i = 0; i < numSensors; ++i) + { + Sensor* sensor = mDevice->GetNeuroSensor(i); + double impedance = mDevice->GetImpedance(i); + bool isused = classifier && classifier->IsSensorUsed(sensor); + + // update contact quality on sensor based on selected thresholds + if (impedance < minthreshold) sensor->SetContactQuality(Sensor::CONTACTQUALITY_NO_SIGNAL); + else if (impedance <= mImpedanceThreshold->green) sensor->SetContactQuality(Sensor::CONTACTQUALITY_GOOD); + else if (impedance <= mImpedanceThreshold->yellow) sensor->SetContactQuality(Sensor::CONTACTQUALITY_FAIR); + else if (impedance <= mImpedanceThreshold->orange) sensor->SetContactQuality(Sensor::CONTACTQUALITY_POOR); + else if (impedance <= mImpedanceThreshold->red) sensor->SetContactQuality(Sensor::CONTACTQUALITY_VERY_BAD); + else sensor->SetContactQuality(Sensor::CONTACTQUALITY_NO_SIGNAL); + + // test fails if either a used electrode has poor/bad/no signal + // or (if none is used), any electrode has poor/bad/no signal + if (isused || numUsedSensors == 0) + { + switch (sensor->GetContactQuality()) + { + case Sensor::CONTACTQUALITY_POOR: + case Sensor::CONTACTQUALITY_VERY_BAD: + case Sensor::CONTACTQUALITY_NO_SIGNAL: + testok = false; + break; + default: + break; + } + } + } + + // update fail/passed icon + mTestLabel->setPixmap(testok ? mPixmapPass : mPixmapFail); } diff --git a/src/Studio/Widgets/ImpedanceTestWidget.h b/src/Studio/Widgets/ImpedanceTestWidget.h index 71b576608..cf1d2821e 100644 --- a/src/Studio/Widgets/ImpedanceTestWidget.h +++ b/src/Studio/Widgets/ImpedanceTestWidget.h @@ -74,13 +74,10 @@ class ImpedanceTestWidget : public QWidget Threshold* mImpedanceThreshold; QComboBox* mThresholdComboBox; - QLabel* mMinImpedanceLabel; - QLabel* mMaxImpedanceLabel; - QLabel* mAvgImpedanceLabel; - QLabel* mPassIconLabel; - QLabel* mFailIconLabel; - + QLabel* mTestLabel; + QPixmap mPixmapPass; + QPixmap mPixmapFail; }; diff --git a/src/Studio/Widgets/SignalQualityWidget.cpp b/src/Studio/Widgets/SignalQualityWidget.cpp index 8deb142d2..a12e4e1e4 100644 --- a/src/Studio/Widgets/SignalQualityWidget.cpp +++ b/src/Studio/Widgets/SignalQualityWidget.cpp @@ -32,7 +32,7 @@ using namespace Core; // constructor SignalQualityWidget::SignalQualityWidget(QWidget* parent) : QWidget(parent) { - mPixmapHeight = 24; + mPixmapHeight = 18; setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum ); @@ -57,7 +57,7 @@ SignalQualityWidget::SignalQualityWidget(QWidget* parent) : QWidget(parent) mTextLabel = new QLabel(); mTextLabel->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); QFont timeFont = mTextLabel->font(); - timeFont.setPixelSize(20); + timeFont.setPixelSize(14); mTextLabel->setFont(timeFont); mainLayout->addWidget( mTextLabel, 0, Qt::AlignLeft | Qt::AlignHCenter ); @@ -103,7 +103,7 @@ void SignalQualityWidget::SetSignalQuality(double normalizedSignalQuality) // update the signal quality mSignalQuality = normalizedSignalQuality; - mTempString.Format( "%.0f %%", normalizedSignalQuality * 100.0 ); + mTempString.Format( "%.0f%%", normalizedSignalQuality * 100.0 ); mTextLabel->setText( mTempString.AsChar() ); const int32 pixmapIndex = normalizedSignalQuality * mPixmaps.Size() - Math::epsilon; diff --git a/src/Studio/Windows/ReportWindow.cpp b/src/Studio/Windows/ReportWindow.cpp index f2453fdb3..a9f900d28 100644 --- a/src/Studio/Windows/ReportWindow.cpp +++ b/src/Studio/Windows/ReportWindow.cpp @@ -44,6 +44,7 @@ ReportWindow::ReportWindow(const Core::String& dataChunkId, QWidget* parent) : Q // create the back to selection button QPushButton* backButton = new QPushButton("Back to selection"); + backButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); mainLayout->addWidget(backButton); connect( backButton, &QPushButton::clicked, this, [=] { @@ -58,6 +59,7 @@ ReportWindow::ReportWindow(const Core::String& dataChunkId, QWidget* parent) : Q // create show report button mShowReportButton = new QPushButton(""); mShowReportButton->setEnabled(false); + mShowReportButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); mShowReportButton->setIcon(GetQtBaseManager()->FindIcon("Images/Icons/Report.png")); mainLayout->addWidget(mShowReportButton); connect( mShowReportButton, &QPushButton::clicked, this, [dataChunkId] @@ -74,7 +76,7 @@ ReportWindow::ReportWindow(const Core::String& dataChunkId, QWidget* parent) : Q // avoid resizing setSizeGripEnabled(false); setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); - setFixedSize( QSize(500, sizeHint().height()) ); + setFixedSize( QSize(512, 64) ); // position window in the screen center GetQtBaseManager()->CenterToScreen(this); diff --git a/src/Studio/Windows/SelectUserWindow.cpp b/src/Studio/Windows/SelectUserWindow.cpp index a26f20302..7703a19f3 100644 --- a/src/Studio/Windows/SelectUserWindow.cpp +++ b/src/Studio/Windows/SelectUserWindow.cpp @@ -90,6 +90,7 @@ SelectUserWindow::SelectUserWindow(const User& user, QWidget* parent, bool showS connect(mSearchEdit, SIGNAL(TextChanged(const QString&)), this, SLOT(OnSearchEdited(const QString&))); connect(mSearchEdit, SIGNAL(TextCleared()), this, SLOT(OnSearchCleared())); topHLayout->addWidget(mSearchEdit); + mSearchEdit->setFocus(); // search timer mSearchTimer = new QTimer(this);