diff --git a/.github/workflows/Create_installer.yml b/.github/workflows/Create_installer.yml new file mode 100644 index 0000000..6414f02 --- /dev/null +++ b/.github/workflows/Create_installer.yml @@ -0,0 +1,134 @@ +name: Build installer + +on: + workflow_dispatch: + push: + branches: + - 'master' + +jobs: + build_pyinstaller: + runs-on: ${{ matrix.os }} + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: false + + # Build on the following 4 configurations: + # 1. + # 2. + # 3. + # 4. > $GITHUB_ENV + else + echo "ICON_PATH=EasyImagingApp/Gui/Resources/Logos/App.icns" >> $GITHUB_ENV + fi + + - name: Build with pyinstaller + working-directory: imaging-app + shell: bash + run: | + pyinstaller EasyImagingApp/main.py --onedir --windowed --name EasyImaging --icon ${{ env.ICON_PATH }} \ + --clean --noconfirm \ + --add-data=EasyImagingApp:. --collect-submodules PySide6 --collect-all scitiff --collect-all easyscience --collect-all EasyApp + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + path: imaging-app/dist/EasyImaging + name: EasyImaging-${{ matrix.os }} + + - name: Prepare creation of installer + working-directory: imaging-app/Installer + shell: bash + run: | + if [ "${{ runner.os}}" == "macOS" ]; then + cp -r ../dist/EasyImaging.app packages/core/data/ + else + cp -r ../dist/EasyImaging/* packages/core/data/ + fi + + - name: Create installers (Windows) + if: runner.os == 'Windows' + working-directory: imaging-app\Installer + run: | + ${{env.IQTA_TOOLS}}\QtInstallerFramework\4.7\bin\binarycreator.exe -c config\config.xml -p packages ${{runner.os}}-${{runner.arch}}_EasyImaging_Installer.exe + + - name: Create installers (Linux & MacOS) + if: runner.os != 'Windows' + working-directory: imaging-app/Installer + run: | + ${{env.IQTA_TOOLS}}/QtInstallerFramework/4.7/bin/binarycreator -c config/config.xml -p packages ${{runner.os}}-${{runner.arch}}_EasyImaging_Installer + + - name: Install DigiCert Client tools from Github Custom Actions marketplace + if: runner.os == 'windows' + uses: digicert/ssm-code-signing@v1.1.0 + + - name: Set up P12 certificate + if: runner.os == 'windows' + shell: bash + run: echo "${{ secrets.WINDOWS_CERT_DATA }}" | base64 --decode > /d/Certificate_pkcs12.p12 + + - name: Set keylocker variables + if: runner.os == 'windows' + shell: bash + run: | + echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT + echo "SM_HOST=${{ secrets.KEYLOCKER_HOST }}" >> "$GITHUB_ENV" + echo "SM_API_KEY=${{ secrets.KEYLOCKER_API_KEY }}" >> "$GITHUB_ENV" + echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV" + echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.WINDOWS_CERT_PASSWORD }}" >> "$GITHUB_ENV" + + - name: Sign the binary using keypair alias + if: runner.os == 'windows' + shell: cmd + run: smctl sign --keypair-alias key_911959544 --input imaging-app\Installer\${{runner.os}}-${{runner.arch}}_EasyImaging_Installer.exe + + - name: Upload installers + uses: actions/upload-artifact@master + with: + name: EasyImaging-installer-${{runner.os}}-${{runner.arch}} + path: imaging-app/Installer/*EasyImaging_Installer* \ No newline at end of file diff --git a/EasyImagingApp/Gui/Resources/Logos/App.icns b/EasyImagingApp/Gui/Resources/Logos/App.icns index 70975cb..0630fbf 100644 Binary files a/EasyImagingApp/Gui/Resources/Logos/App.icns and b/EasyImagingApp/Gui/Resources/Logos/App.icns differ diff --git a/EasyImagingApp/Gui/Resources/Logos/App.ico b/EasyImagingApp/Gui/Resources/Logos/App.ico index 23c0866..692c5db 100644 Binary files a/EasyImagingApp/Gui/Resources/Logos/App.ico and b/EasyImagingApp/Gui/Resources/Logos/App.ico differ diff --git a/EasyImagingApp/Gui/Resources/Logos/App.png b/EasyImagingApp/Gui/Resources/Logos/App.png index cf5d431..2376a8c 100644 Binary files a/EasyImagingApp/Gui/Resources/Logos/App.png and b/EasyImagingApp/Gui/Resources/Logos/App.png differ diff --git a/EasyImagingApp/Gui/Resources/Logos/App.svg b/EasyImagingApp/Gui/Resources/Logos/App.svg index 5aab6b1..4c61581 100644 --- a/EasyImagingApp/Gui/Resources/Logos/App.svg +++ b/EasyImagingApp/Gui/Resources/Logos/App.svg @@ -1,27 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/EasyImagingApp/main.py b/EasyImagingApp/main.py index 822be18..a20a50e 100644 --- a/EasyImagingApp/main.py +++ b/EasyImagingApp/main.py @@ -9,6 +9,7 @@ # from PySide6.QtGui import QGuiApplication from PySide6.QtQml import QQmlApplicationEngine, qmlRegisterSingletonType from PySide6.QtCore import qInstallMessageHandler +from PySide6.QtGui import QIcon # It is usually assumed that the EasyApp package is already installed in the desired python environment. # If this is not the case, and if the example is run from the EasyApp repository, one need to add the path to the @@ -37,6 +38,7 @@ app = QApplication(sys.argv) console.debug(f'Qt Application created {app}') + app.setWindowIcon(QIcon(str(CURRENT_DIR / 'Gui' / 'Resources' / 'Logos' / 'App.svg'))) engine = QQmlApplicationEngine() console.debug(f'QML application engine created {engine}') diff --git a/Installer/config/config.xml b/Installer/config/config.xml new file mode 100644 index 0000000..f41c9d8 --- /dev/null +++ b/Installer/config/config.xml @@ -0,0 +1,14 @@ + + + EasyImaging + 0.0.1 + EasyImaging Installer + ESS DMSC + ../../EasyImagingApp/Gui/Resources/Logos/App.png + ../../EasyImagingApp/Gui/Resources/Logos/App + EasyImaging + @ApplicationsDirUser@/EasyImaging + Modern + controllerscript.js + EasyImaging Maintenance Tool + \ No newline at end of file diff --git a/Installer/config/controllerscript.js b/Installer/config/controllerscript.js new file mode 100644 index 0000000..3412731 --- /dev/null +++ b/Installer/config/controllerscript.js @@ -0,0 +1,16 @@ +/// This is the script which handles the parts of the installer not related to any of the packages. + + +function Controller() +{ + if (systemInfo.productType === "windows") { + installer.setDefaultPageVisible(QInstaller.StartMenuSelection, false); + } + +} + +Controller.prototype.IntroductionPageCallback = function() +{ + gui.clickButton(buttons.NextButton); +} + diff --git a/Installer/packages/core/data/.gitignore b/Installer/packages/core/data/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/Installer/packages/core/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/Installer/packages/core/meta/LICENSE b/Installer/packages/core/meta/LICENSE new file mode 100644 index 0000000..0cb01f7 --- /dev/null +++ b/Installer/packages/core/meta/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2024, EasyImaging contributors (https://github.com/easyscience/imaging-app). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Installer/packages/core/meta/customintroductionpage.ui b/Installer/packages/core/meta/customintroductionpage.ui new file mode 100644 index 0000000..c3da7aa --- /dev/null +++ b/Installer/packages/core/meta/customintroductionpage.ui @@ -0,0 +1,28 @@ + + + Page + + + + 0 + 0 + 400 + 300 + + + + Welcome + + + + + + Qt::AlignCenter + + + + + + + + diff --git a/Installer/packages/core/meta/installscript.js b/Installer/packages/core/meta/installscript.js new file mode 100644 index 0000000..ed14c90 --- /dev/null +++ b/Installer/packages/core/meta/installscript.js @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the FOO module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +var Dir = new function () { + this.toNativeSparator = function (path) { + if (systemInfo.kernelType === "winnt") + return path.replace(/\//g, '\\'); + return path; + } +}; + +function Component() { + // constructor + if (installer.isInstaller()) { + component.loaded.connect(this, Component.prototype.loaded); + } +} + +Component.prototype.loaded = function () { + if (installer.addWizardPage(component, "CustomIntroductionPage", QInstaller.TargetDirectory)) { + var page = gui.pageByObjectName("DynamicCustomIntroductionPage"); + if (page != null) { + page.entered.connect(Component.prototype.dynamicCustromIntroductionPageEntered); + } + } + console.log("component loaded"); + if (systemInfo.kernelType === "winnt") { + console.log("System is windows") + if (installer.addWizardPage(component, "ShortcutWidget", QInstaller.StartMenuSelection)) { + var widget = gui.pageWidgetByObjectName("DynamicShortcutWidget"); + if (widget != null) { + widget.createDesktopShortcut.checked = true; + widget.createStartMenuShortcut.checked = true; + + widget.windowTitle = "Create shortcuts"; + } + } + } else if (systemInfo.kernelType == "linux") { + console.log("System is linux"); + if (installer.addWizardPage(component, "SymlinkWidget", QInstaller.StartMenuSelection)) { + var widget = gui.pageWidgetByObjectName("DynamicSymlinkWidget"); + if (widget != null) { + widget.createDesktopShortcut.checked = true; + widget.createUserLink.checked = true; + widget.createSystemLink.checked = false; + + widget.windowTitle = "Create shortcuts"; + } + } + } +} + +Component.prototype.dynamicCustromIntroductionPageEntered = function () +{ + var pageWidget = gui.pageWidgetByObjectName("DynamicCustomIntroductionPage"); + if (pageWidget != null) { + pageWidget.m_pageLabel.text = "Welcome to the EasyImaging setup."; + installer.setDefaultPageVisible(QInstaller.Introduction, false); + } +} + +Component.prototype.createOperations = function() +{ + try { + // call the base create operations function + component.createOperations(); + } catch (e) { + console.log(e); + } + + if (systemInfo.kernelType === "winnt") { + var shortcutpage = component.userInterface("ShortcutWidget"); + if (shortcutpage && shortcutpage.createDesktopShortcut.checked) { + component.addElevatedOperation("CreateShortcut", "@TargetDir@/EasyImaging.exe", "@DesktopDir@/EasyImaging.lnk"); + } + if (shortcutpage && shortcutpage.createStartMenuShortcut.checked) { + component.addElevatedOperation("CreateShortcut", "@TargetDir@/EasyImaging.exe", "@UserStartMenuProgramsPath@/@StartMenuDir@/EasyImaging.lnk", + "workingDirectory=@TargetDir@"); + } + } else if (systemInfo.kernelType === "linux") { + var shortcutpage = component.userInterface("SymlinkWidget"); + if (shortcutpage && shortcutpage.createDesktopShortcut.checked) { + component.addOperation("CreateDesktopEntry", "@HomeDir@/.local/share/applications/EasyImaging.desktop", + "Type=Application\n"+ + "Name=EasyImaging\n"+ + "Comment=Neutron imaging Bragg-edge analysis software\n"+ + "Exec=@TargetDir@/EasyImaging\n"+ + "Icon=@TargetDir@/_internal/Gui/Resources/Logos/App.svg\n"+ + "Categories=Science" + ); + + } + if (shortcutpage && shortcutpage.createUserLink.checked) { + console.log("Creating Symlinks in @HomeDir@/.local/bin"); + component.addOperation("CreateLink", "@HomeDir@/.local/bin/easyimaging", "@TargetDir@/EasyImaging"); + } + if (shortcutpage && shortcutpage.createSystemLink.checked) { + console.log("Creating Symlinks in @RootDir@usr/local/bin"); + component.addElevatedOperation("CreateLink", "@RootDir@usr/local/bin/easyimaging", "@TargetDir@/EasyImaging"); + } + } +} diff --git a/Installer/packages/core/meta/package.xml b/Installer/packages/core/meta/package.xml new file mode 100644 index 0000000..955d992 --- /dev/null +++ b/Installer/packages/core/meta/package.xml @@ -0,0 +1,19 @@ + + + EasyImaging core components + core + The core components critical to running EasyImaging + 0.0.1 + 2025-10-16 + + + + false + true + + + customintroductionpage.ui + shortcutwidget.ui + symlinkwidget.ui + + \ No newline at end of file diff --git a/Installer/packages/core/meta/shortcutwidget.ui b/Installer/packages/core/meta/shortcutwidget.ui new file mode 100644 index 0000000..cca017f --- /dev/null +++ b/Installer/packages/core/meta/shortcutwidget.ui @@ -0,0 +1,90 @@ + + + ShortcutWidget + + + + 0 + 0 + 400 + 300 + + + + + 0 + 0 + + + + + 400 + 300 + + + + Shortcut Creation + + + + 0 + + + 4 + + + + + + 0 + 0 + + + + Create desktop shortcut + + + true + + + + + + + + 0 + 0 + + + + Create Start menu shortcut + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 100 + + + + + + + + createDesktopShortcut + createStartMenuShortcut + + + diff --git a/Installer/packages/core/meta/symlinkwidget.ui b/Installer/packages/core/meta/symlinkwidget.ui new file mode 100644 index 0000000..8cd7801 --- /dev/null +++ b/Installer/packages/core/meta/symlinkwidget.ui @@ -0,0 +1,133 @@ + + + SymlinkWidget + + + + 0 + 0 + 500 + 300 + + + + + 0 + 0 + + + + + 500 + 300 + + + + Shortcut Creation + + + + 0 + + + 4 + + + + + + + 0 + 0 + + + + Create desktop shortcut + + + true + + + + + + + + + + + 0 + 0 + + + + Create symlinks to applications on user path (~.local/bin) + + + true + + + + + + + OBS! May require logging in again to take effect + + + + + + + + + + + + + 0 + 0 + + + + Create symlinks to applications on system path (/usr/local/bin) + + + false + + + + + + + OBS! Requires root access + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 100 + + + + + + + + createDesktopShortcut + createUserLink + createSystemLink + + + diff --git a/pyproject.toml b/pyproject.toml index c1161ce..1bec8ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = 'hatchling.build' [project] name = 'EasyImagingApp' version = '1.0.0' -description = 'Example of a desktop application of advanced complexity with Python backend and EasyApp-based GUI' +description = 'The graphical user-interface of EasyImaging' authors = [ {name = 'Christian Dam Vedel', email = 'christian.vedel@ess.eu'} ] @@ -19,16 +19,19 @@ classifiers = [ ] requires-python = '>=3.11' dependencies = [ - 'EasyApp @ git+https://github.com/EasyScience/EasyApp.git@master', + 'EasyApp @ git+https://github.com/EasyScience/EasyApp.git@Fix_wrong_application_types', 'toml', 'scitiff', + 'PySide6>=6.8', + 'easyscience', ] [project.optional-dependencies] ci = [ 'validate-pyproject[all]', 'build', - 'wheel' + 'wheel', + 'pyinstaller', ] [project.urls]