Sync edgetx to Gitea

This commit is contained in:
2026-08-03 16:37:20 +08:00
commit 14eb5a3fb9
5364 changed files with 2835009 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
---
Language: Cpp
BasedOnStyle: Google
BreakBeforeBraces: Linux
BreakConstructorInitializers: AfterColon
...
+19
View File
@@ -0,0 +1,19 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.187.0/containers/cpp
{
"name": "edgetx-dev",
"image": "ghcr.io/edgetx/edgetx-dev:latest",
"runArgs": [
"--ipc=host"
],
// Add the IDs of extensions you want installed when the container is created.
"customizations": {
"vscode": {
"extensions": [
"ms-vscode.cpptools"
]
}
},
// Post-create command to set up the environment
"postCreateCommand": "git submodule update --init --recursive && mkdir -p build && echo 'EdgeTX development environment ready!'"
}
+112
View File
@@ -0,0 +1,112 @@
# Handle line endings automatically for files detected as text
# and leave all files detected as binary untouched.
* text=auto
#
# The above will handle all files NOT found below
#
# These files are text and should be normalized (Convert crlf => lf)
# Source files
*.c text
*.cc text
*.cxx text
*.cpp text
*.c++ text
*.hpp text
*.h text
*.h++ text
*.hh text
*.lua text
*.pxd text
*.py text
*.py3 text
*.pyw text
*.pyx text
*.pl text
*.pm text
*.sln text eol=crlf
*.csproj text eol=crlf
*.vbproj text eol=crlf
*.vcxproj text eol=crlf
*.vcproj text eol=crlf
*.dbproj text eol=crlf
*.fsproj text eol=crlf
*.lsproj text eol=crlf
*.wixproj text eol=crlf
*.modelproj text eol=crlf
*.sqlproj text eol=crlf
*.wmaproj text eol=crlf
*.xproj text eol=crlf
*.props text eol=crlf
*.filters text eol=crlf
*.vcxitems text eol=crlf
*.sh eol=lf
# Compiled Object files
*.slo binary
*.lo binary
*.o binary
*.obj binary
# Precompiled Headers
*.gch binary
*.pch binary
# Compiled Dynamic libraries
*.so binary
*.dylib binary
*.dll binary
# Compiled Static libraries
*.lai binary
*.la binary
*.a binary
*.lib binary
# Executables
*.exe binary
*.out binary
*.app binary
# Binary files
*.db binary
*.p binary
*.pkl binary
*.pyc binary
*.pyd binary
*.pyo binary
# Documents
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
*.md text
*.adoc text
*.textile text
*.mustache text
*.csv text
*.tab text
*.tsv text
*.sql text
# Graphics
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.tif binary
*.tiff binary
*.ico binary
# SVG treated as an asset (binary) by default. If you want to treat it as text,
# comment-out the following line and uncomment the line after.
*.svg binary
#*.svg text
*.eps binary
+168
View File
@@ -0,0 +1,168 @@
name: 🐞 Bug
description: File a bug report
labels: ["bug 🪲", "triage"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
Please note that this is not the place to make feature requests or ask for
help. For this, please use the [Discord server](https://discord.gg/wF9wUKnZ6H) or [Discussion forum](https://github.com/EdgeTX/edgetx/discussions).
- type: checkboxes
attributes:
label: Is there an existing issue for this problem?
description: Please search to see if an issue already exists for the bug you encountered.
options:
- label: I have searched the existing issues
required: true
- type: dropdown
id: component-affected
attributes:
label: What part of EdgeTX is the focus of this bug?
description: Is it the radio firmware, companion software or something else?
options:
- Transmitter firmware
- Companion
- Simulator
- Build system
- Other (Please specify below)
validations:
required: true
- type: textarea
attributes:
label: Current Behavior
description: A clean and concise description of what you're experiencing.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: What is it that you expected to happen?
validations:
required: true
- type: textarea
attributes:
label: Steps To Reproduce
description: Steps to reproduce the behavior.
placeholder: |
1. Push throttle up.
2. Push throttle down.
3. Flick SWC on and off three times.
4. See error.
validations:
required: true
- type: dropdown
id: version
attributes:
label: Version
description: What version of EdgeTX software are you running?
options:
- 2.12.1
- 2.12.0
- 2.11.5
- 2.11.4
- 2.11.3
- 2.11.2
- 2.11.1
- 2.11.0
- 2.10.6
- 2.10.5
- 2.10.4
- 2.10.3
- 2.10.2
- 2.10.1
- 2.10.0
- 2.9.4
- 2.8.5
- Nightly (Please give date/commit below)
- Other (Please specify below)
validations:
required: true
- type: dropdown
id: transmitter
attributes:
label: Transmitter
description: |
What transmitter are you using? You can pick more than one if this is
affecting more than one transmitter.
multiple: true
options:
- BetaFPV LiteRadio3 Pro
- iFlight Commando8
- Fatfish F16
- Flysky EL18
- Flysky NB4+
- Flysky NV14
- Flysky PA01
- Flysky PL18/PL18EV
- Flysky ST16
- FrSky Q X7 / Q X7S (ACCST)
- FrSky Q X7 / Q X7S (ACCESS)
- FrSky X9D
- FrSky X9D+
- FrSky X9D+2019
- FrSky X9E / X9E Hall
- FrSky X9 Lite / Lite S
- FrSky X10 / X10S (ACCST)
- FrSky X10 Express / X10S Express (ACCESS)
- FrSky X12
- FrSky X-Lite / S / Pro
- HelloRadioSky V12
- HelloRadioSky V14
- HelloRadioSky V16
- Jumper T12
- Jumper T14
- Jumper T15
- Jumper T15 Pro
- Jumper T16
- Jumper T18
- Jumper T20/T20S
- Jumper T20 V2
- Jumper T-Lite
- Jumper T-Pro
- Jumper T-Pro V2
- RadioMaster Boxer
- RadioMaster GX12
- RadioMaster MT12
- RadioMaster Pocket
- RadioMaster T8
- RadioMaster TX12 / TX12MK2
- RadioMaster TX15
- RadioMaster TX16S / TX16SMK2
- RadioMaster TX16S MK3
- RadioMaster Zorro
- Other (Please specify below)
validations:
required: true
- type: dropdown
id: os-type
attributes:
label: "Operating System (OS)"
description: "If this relates to Companion or Simulator, what OSes are you are experiencing issues on?"
multiple: true
options:
- Linux
- macOS
- Windows
validations:
required: false
- type: input
id: os-version
attributes:
label: "OS Version"
description: "If this relates to Companion or Simulator, what OS version does this relate to?"
placeholder: "i.e. 10, Ubuntu 20.04, Big Sur"
validations:
required: false
- type: textarea
attributes:
label: Anything else?
description: |
Screenshots? References? Anything that will give us more context about the issue you are encountering!
This includes if you are running customised firmware or have made hardware modifications to the transmitter
which could have some bearing on the issue.
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: false
+14
View File
@@ -0,0 +1,14 @@
blank_issues_enabled: false
contact_links:
- name: SD Card Pack - e.g. included Templates, Lua Widgets, Themes
url: https://github.com/EdgeTX/edgetx-sdcard/issues/new
about: If you have a request or issue with a Lua widget or something else that is part of the SD Card pack, please head over there to open an issue.
- name: SD Card Voice Pack
url: https://github.com/EdgeTX/edgetx-sdcard-sounds/issues/new/choose
about: If you have a request or issue with the sound card packs, please head over there to open an issue.
- name: Community Support
url: https://discord.gg/wF9wUKnZ6H
about: Please ask and answer support "how do I?" questions here.
- name: Discussion Forum
url: https://github.com/EdgeTX/edgetx/discussions
about: Please raise ideas, ask questions, discuss feature suggestions and showcase stuff here.
@@ -0,0 +1,54 @@
name: 🚀 Feature Request / Enhancement
description: Suggest an improvemnt to make EdgeTX even better!
labels: ["enhancement ✨"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request!
If your idea is still at the formulation stage, or you're not sure it would
be useful to many users, you can raise it as a discussion topic under [Ideas](https://github.com/EdgeTX/edgetx/discussions/categories/ideas)
or you can raise it on the [Discord server](https://discord.gg/wF9wUKnZ6H).
- type: checkboxes
attributes:
label: Is there an existing issue for this feature request?
description: |
Please search to see if an issue already exists for a feature, or perhaps one similar.
You can then comment and react so that we know know much interest there is in the feature request
options:
- label: I have searched the existing issues
required: true
- type: textarea
attributes:
label: Is your feature request related to a problem?
description: A clear and concise description of what the problem is.
placeholder: I'm always frustrated when [...]
validations:
required: true
- type: textarea
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
placeholder: It should do [...]
validations:
required: true
- type: textarea
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
placeholder: |
1. [...]
2. [...]
3. [...]
validations:
required: false
- type: textarea
attributes:
label: Additional context
description: |
Add any other context, diagrams, illustations or screenshots about the feature request here.
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: false
@@ -0,0 +1,64 @@
name: 'Build Companion'
inputs:
os:
description: 'OS / Architecture'
required: true
qt-version:
description: 'Qt version'
default: '6.9.3'
qt-arch:
description: 'Qt architecture'
required: runner.os != 'Linux'
sdl2-version:
description: 'SDL2 version'
default: '2.32.8'
runs:
using: "composite"
steps:
- name: Install Qt
if: runner.os != 'Linux'
uses: jdpurcell/install-qt-action@v5
env:
AQT_CONFIG: ${{ github.workspace }}/tools/aqt-settings.ini
with:
version: ${{ inputs.qt-version }}
modules: 'qtmultimedia qtserialport'
arch: ${{ inputs.qt-arch }}
cache: true
cache-key-prefix: 'install-qt-action-${{ inputs.qt-arch }}'
- name: Setup SDL2
if: runner.os != 'Linux'
shell: bash
env:
ACTION_PATH: ${{ github.action_path }}
SDL2_VERSION: ${{ inputs.sdl2-version }}
run: |
"${ACTION_PATH}/setup-sdl2.sh" ${SDL2_VERSION}
- name: Setup MSVC
if: runner.os == 'Windows'
shell: pwsh
run: ${{ github.action_path }}/setup-msvc.ps1
- name: Build
shell: bash
env:
CMAKE_BUILD_TYPE: 'Release'
run: |
mkdir output && \
CMAKE_PREFIX_PATH="$QT_ROOT_DIR;$SDL2_ROOT" \
tools/build-companion.sh "$(pwd)" "$(pwd)/output/"
- name: Compose release filename
run: echo "artifact_name=edgetx-cpn-${{ inputs.os }}-${GITHUB_REF##*/}" >> $GITHUB_ENV
shell: bash
- name: Archive production artifacts
uses: actions/upload-artifact@v7
with:
name: "${{ env.artifact_name }}"
path: ${{github.workspace}}/output
retention-days: 15
@@ -0,0 +1,39 @@
# setup-msvc.ps1
# Runs vcvarsall.bat and exports only the new/changed environment
# variables to GitHub Actions, avoiding clobbering existing ones.
param(
[string]$Arch = "x64"
)
# Find VS installation
$vsWhere = "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"
$vsPath = & $vsWhere -latest -property installationPath
$vcvars = Join-Path $vsPath "VC\Auxiliary\Build\vcvarsall.bat"
if (-not (Test-Path $vcvars)) {
Write-Error "vcvarsall.bat not found at: $vcvars"
exit 1
}
# Snapshot environment before
$before = @{}
Get-ChildItem env: | ForEach-Object { $before[$_.Name] = $_.Value }
# Run vcvarsall.bat and import its environment into this process
cmd /c "`"$vcvars`" $Arch && set" | ForEach-Object {
if ($_ -match '^([^=]+)=(.*)$') {
[System.Environment]::SetEnvironmentVariable($Matches[1], $Matches[2])
}
}
# Export only changed/new variables to GitHub Actions
$count = 0
Get-ChildItem env: | ForEach-Object {
if ($before[$_.Name] -ne $_.Value) {
"$($_.Name)=$($_.Value)" | Out-File -Append -Encoding utf8 $env:GITHUB_ENV
$count++
}
}
Write-Host "Exported $count environment variables to GITHUB_ENV"
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# setup-sdl2.sh
# Downloads/installs SDL2 for Windows and macOS.
# Linux is handled separately via a Docker container.
set -euo pipefail
VERSION="${1:?Usage: setup-sdl2.sh <version>}"
case "$RUNNER_OS" in
Windows)
ARCHIVE="SDL2-devel-${VERSION}-VC.zip"
URL="https://github.com/libsdl-org/SDL/releases/download/release-${VERSION}/${ARCHIVE}"
DEST="${RUNNER_TEMP}/SDL2"
echo "Downloading SDL2 ${VERSION} (Windows/MSVC)..."
curl -fsSL -o "${RUNNER_TEMP}/${ARCHIVE}" "${URL}"
unzip -q "${RUNNER_TEMP}/${ARCHIVE}" -d "${DEST}"
SDL2_ROOT="${DEST}/SDL2-${VERSION}"
echo "SDL2_ROOT=${SDL2_ROOT}" >> "${GITHUB_ENV}"
;;
macOS)
ARCHIVE="SDL2-${VERSION}.dmg"
URL="https://github.com/libsdl-org/SDL/releases/download/release-${VERSION}/${ARCHIVE}"
echo "Downloading SDL2 ${VERSION} (macOS framework)..."
curl -fsSL -o "${RUNNER_TEMP}/${ARCHIVE}" "${URL}"
hdiutil attach "${RUNNER_TEMP}/${ARCHIVE}" -quiet -mountpoint "${RUNNER_TEMP}/sdl2-mnt"
sudo cp -R "${RUNNER_TEMP}/sdl2-mnt/SDL2.framework" /Library/Frameworks/
hdiutil detach "${RUNNER_TEMP}/sdl2-mnt" -quiet
;;
Linux)
echo "Skipping — SDL2 is provided by the Docker container"
;;
*)
echo "::error::Unsupported RUNNER_OS: ${RUNNER_OS}"
exit 1
;;
esac
echo "SDL2 ${VERSION} setup complete for ${RUNNER_OS}"
@@ -0,0 +1,17 @@
name: 'Setup WASI SDK'
inputs:
python-version:
description: 'Python version'
default: '3.13'
runs:
using: "composite"
steps:
- uses: actions/setup-python@v6
with:
python-version: ${{ inputs.python-version }}
cache: 'pip' # caching pip dependencies
- run: python3 -m pip install -r requirements.txt
shell: bash
+20
View File
@@ -0,0 +1,20 @@
name: 'Setup WASI SDK'
inputs:
arch:
description: 'OS / Architecture (ex: arm64-macos)'
default: 'arm64-macos'
version:
description: 'SDK version'
default: '25'
runs:
using: "composite"
steps:
- name: Install WASI SDK
if: steps.cache-wasi-sdk.outputs.cache-hit != 'true'
shell: bash
run: |
mkdir /tmp/wasi-sdk
curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${{ inputs.version }}/wasi-sdk-${{ inputs.version }}.0-${{ inputs.arch }}.tar.gz | tar xz --strip-components 1 -C /tmp/wasi-sdk
sudo mv /tmp/wasi-sdk /opt/wasi-sdk
+13
View File
@@ -0,0 +1,13 @@
<!--
Please note that pull requests are NOT an appropriate way to
ask questions or for support, and will be closed.
For feature requests or bug reports, please use the Issues tab.
For support, please use the Discussion tab or join us on Discord.
Feel free to delete any of the below which does not apply.
-->
Fixes #
Summary of changes:
+25
View File
@@ -0,0 +1,25 @@
name: 'Auto-close marked issues'
on:
schedule:
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10
with:
# Issue closure message
close-issue-message: 'This issue has been closed due to no activity in over four weeks after having been marked as closure pending.'
# Issue close reason
close-issue-reason: not_planned
# Do not mark issues or PRs as stale automaticaly
days-before-stale: -1
# Close issue/PR if no activity for four weeks after closure pending warning
days-before-close: 28
# Do not auto close PRs
days-before-pr-close: -1
# Do not auto-close an issue if it is assigned to a milestone
exempt-all-issue-milestones: true
# Stale issue label
stale-issue-label: 'closing 🚪'
+138
View File
@@ -0,0 +1,138 @@
---
name: Run tests and package firmware
on:
push:
branches:
- 'main'
- '[0-9]+.[0-9]+'
tags:
- v*
paths: &trigger-paths
- '.github/workflows/build_fw.yml'
- 'cmake/**'
- 'radio/**'
- 'tools/build-gh.sh'
- '.gitmodules'
- 'CMakeLists.txt'
pull_request:
paths: *trigger-paths
jobs:
test:
name: Run tests
runs-on: ubuntu-latest
if: |
github.event_name != 'pull_request' ||
!contains(github.event.pull_request.labels.*.name, 'ci: skip-fw')
strategy:
matrix:
target:
- x9dp2019;x9e
- tx15
- tx16s
- tx16smk3
- nv14
- el18
- pl18
- pl18ev
- t12max;t14
- t15
- t16
- t18
- t20;t20v2
- x10
- x12s
- mt12;gx12
- nb4p
- st16
- t15pro
- pa01
container:
image: ghcr.io/edgetx/edgetx-dev:latest
volumes:
- ${{ github.workspace }}:/src
steps:
- name: Check out the repo
uses: actions/checkout@v6
with:
submodules: recursive
- name: Test ${{ matrix.target }}
env:
FLAVOR: ${{ matrix.target }}
CMAKE_BUILD_PARALLEL_LEVEL: 4
run: |
echo "Running commit tests"
./tools/commit-tests.sh
build:
name: Run builds
needs: test
runs-on: ubuntu-latest
if: |
github.event_name != 'pull_request' ||
!contains(github.event.pull_request.labels.*.name, 'ci: skip-fw')
strategy:
matrix:
target:
- nv14;el18
- pl18;pl18ev
- pl18u;pa01
- t15;t16
- t14;t18;mt12
- zorro;pocket;commando8
- tprov2;tpros;bumblebee
- mt12;gx12;t20;t20v2
- t12max;tx12mk2;boxer
- tx15;tx16s
- x12s;f16
- v12;v14;v16
- x10;x10express
- x7access;x9dp2019;x9e;x9e-hall
- nb4p;st16
- tx16smk3;t14;t15pro
container:
image: ghcr.io/edgetx/edgetx-dev:latest
volumes:
- ${{ github.workspace }}:/src
steps:
- name: Check out the repo
uses: actions/checkout@v6
with:
submodules: recursive
- name: Build ${{ matrix.target }}
env:
FLAVOR: ${{ matrix.target }}
CMAKE_BUILD_PARALLEL_LEVEL: 4
run: |
echo "Running firmware build"
./tools/build-gh.sh
- name: Upload ${{ matrix.target }}
uses: actions/upload-artifact@v7
with:
name: firmware-${{ matrix.target }}
path: |
fw.json
LICENSE
*.bin
*.uf2
retention-days: 15
if-no-files-found: error
package:
name: Package firmwares
needs: build
runs-on: ubuntu-latest
steps:
- name: Compose release filename
run: echo "artifact_name=edgetx-firmware-${GITHUB_REF##*/}" >> $GITHUB_ENV
- name: Merge firmware artifact packages
uses: actions/upload-artifact/merge@v7
with:
name: "${{ env.artifact_name }}"
pattern: firmware-*
delete-merged: true
retention-days: 15
+183
View File
@@ -0,0 +1,183 @@
name: Companion
on:
push:
branches:
- 'main'
tags:
- v*
paths:
- '.github/workflows/companion.yml'
- 'companion/**'
- 'tools/build-companion.sh'
- 'tools/build-wasm-modules.sh'
- 'cmake/wasm/CMakeLists.txt'
pull_request:
paths:
- '.github/workflows/companion.yml'
- 'companion/**'
- 'tools/build-companion.sh'
- 'tools/build-wasm-modules.sh'
- 'cmake/wasm/CMakeLists.txt'
workflow_dispatch:
inputs:
target:
description: 'Build target'
required: true
default: 'all'
type: choice
options:
- all
- linux
- macos
- windows
- wasm
jobs:
modules:
name: WASM Modules (${{ matrix.group.id }})
runs-on: macos-15
strategy:
matrix:
group:
- id: color-1
target: [nv14, el18, pl18, pl18ev]
- id: color-2
target: [pl18u, t15, t16, t18]
- id: bw-1
target: [zorro, pocket, mt12, commando8, tprov2, tpros, bumblebee, t14]
- id: bw-2
target: [mt12, gx12, t20, t20v2, t12max, tx12mk2, boxer]
- id: color-3
target: [tx15, tx16s, x12s, f16]
- id: mixed-1
target: [v12, v14, v16, x10, x10express]
- id: bw-3
target: [x7access, x9dp2019, x9e, x9e-hall]
- id: color-4
target: [nb4p, st16, pa01]
- id: color-5
target: [t15pro, tx16smk3]
steps:
- name: Check out the repo
uses: actions/checkout@v5
with:
submodules: true
- name: Setup Python Dependencies
uses: ./.github/actions/python_dependencies
- name: Setup WASI SDK
uses: ./.github/actions/wasi_sdk
- name: Build modules
shell: bash
env:
FLAVOR: ${{ join(matrix.group.target, ';') }}
CMAKE_BUILD_TYPE: 'Release'
run: |
mkdir output && \
tools/build-wasm-modules.sh "$(pwd)" "$(pwd)/output/"
- name: Archive production artifacts
uses: actions/upload-artifact@v7
with:
name: wasm-modules-${{ matrix.group.id }}
path: ${{github.workspace}}/output
if-no-files-found: error
retention-days: 15
build-linux:
name: Linux Companion
runs-on: ubuntu-latest
needs: modules
if: inputs.target == 'all' || inputs.target == 'linux' || inputs.target == ''
container:
image: ghcr.io/edgetx/edgetx-dev:latest
volumes:
- ${{ github.workspace }}:/src
steps:
- name: Check out the repo
uses: actions/checkout@v5
with:
submodules: recursive
- name: Download WASM modules
uses: actions/download-artifact@v7
with:
pattern: wasm-modules-*
path: wasm-modules
merge-multiple: true
- name: Setup and Build
uses: ./.github/actions/build_companion
with:
os: 'linux'
build-macos:
name: macOS Companion
runs-on: macos-15
needs: modules
if: inputs.target == 'all' || inputs.target == 'macos' || inputs.target == ''
env:
CMAKE_OSX_ARCHITECTURES: 'x86_64;arm64'
CMAKE_OSX_DEPLOYMENT_TARGET: '11.0'
steps:
- name: Check out the repo
uses: actions/checkout@v5
with:
submodules: recursive
- name: Download WASM modules
uses: actions/download-artifact@v7
with:
pattern: wasm-modules-*
path: wasm-modules
merge-multiple: true
- name: Setup and Build
uses: ./.github/actions/build_companion
with:
os: 'macos'
qt-arch: 'clang_64'
build-win64:
name: Windows Companion
runs-on: windows-2022
needs: modules
if: inputs.target == 'all' || inputs.target == 'windows' || inputs.target == ''
env:
CMAKE_GENERATOR: 'Ninja'
CXX: 'clang++'
CC: 'clang'
defaults:
run:
shell: bash
steps:
- name: Check out the repo
uses: actions/checkout@v5
with:
submodules: recursive
- name: Download WASM modules
uses: actions/download-artifact@v7
with:
pattern: wasm-modules-*
path: wasm-modules
merge-multiple: true
- name: Setup and Build
uses: ./.github/actions/build_companion
with:
os: 'win64'
qt-arch: 'win64_msvc2022_64'
+55
View File
@@ -0,0 +1,55 @@
name: Deploy Documentation
on:
push:
branches:
- main
paths:
- 'docs/**'
- 'mkdocs.yml'
- 'docs-requirements.txt'
- '.github/workflows/docs.yml'
tags:
- 'v[0-9]+.[0-9]+.[0-9]+' # release tags only, not -rc
pull_request:
paths:
- 'docs/**'
- 'mkdocs.yml'
- 'docs-requirements.txt'
- '.github/workflows/docs.yml'
workflow_dispatch:
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Configure git for mike
if: github.event_name != 'pull_request'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Build docs (PR check)
if: github.event_name == 'pull_request'
run: uv run --with-requirements docs-requirements.txt mkdocs build --strict
- name: Deploy as latest (main branch)
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: uv run --with-requirements docs-requirements.txt mike deploy --push --update-aliases latest
- name: Deploy release version
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
run: |
# Extract minor version from tag, e.g. v2.11.2 -> v2.11
VERSION=$(echo "${GITHUB_REF_NAME}" | sed 's/\.[0-9]*$//')
uv run --with-requirements docs-requirements.txt mike deploy --push --update-aliases "${VERSION}" stable
+148
View File
@@ -0,0 +1,148 @@
---
name: Build and deploy nightly release
on:
schedule:
- cron: '0 2 * * *' # run at 2 AM UTC
workflow_dispatch:
concurrency:
group: 'nightly'
cancel-in-progress: true
jobs:
build:
name: Build firmware
runs-on: ubuntu-latest
strategy:
matrix:
target:
- nv14;el18
- pl18;pl18ev;pl18u
- pa01
- t12max
- t15;t16;t18
- zorro;pocket;mt12;commando8
- tprov2;tpros;bumblebee
- t20;t20v2;t14
- tx12mk2;boxer;gx12
- tx16s
- tx16smk3
- f16
- v16
- x10;x10express
- x12s
- x7access
- x9dp2019
- x9e;x9e-hall
- mt12
- nb4p
- st16
- tx15
- t15pro
container:
image: ghcr.io/edgetx/edgetx-dev:latest
volumes:
- ${{ github.workspace }}:/src
steps:
- name: Check out the repo
uses: actions/checkout@v6
with:
submodules: recursive
- name: Build firmware ${{ matrix.target }}
env:
FLAVOR: ${{ matrix.target }}
EDGETX_VERSION_SUFFIX: nightly
CMAKE_BUILD_PARALLEL_LEVEL: 4
run: ./tools/build-gh.sh
- name: Package firmware ${{ matrix.target }}
uses: actions/upload-artifact@v7
with:
name: edgetx-firmware-nightly-${{ matrix.target }}
path: |
fw.json
LICENSE
*.bin
*.uf2
retention-days: 15
if-no-files-found: error
deploy:
name: Deploy release
runs-on: ubuntu-latest
needs: build
permissions:
contents: write
steps:
- name: Check out the repo
uses: actions/checkout@v6
with:
fetch-depth: 0 # Need full history for changelog
- name: Download artifacts
uses: actions/download-artifact@v8
with:
pattern: edgetx-firmware-nightly-*
path: edgetx-firmware-nightly
merge-multiple: true
- name: Compose release filename
run: |
echo "release_filename=edgetx-firmware-nightly-${GITHUB_SHA::8}.zip" >> $GITHUB_ENV
echo "build_date=$(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_ENV
echo "old_nightly_sha=$(git rev-parse nightly)" >> $GITHUB_ENV
- name: Zip release file
uses: montudor/action-zip@v1
with:
args: zip -qq -j -r ${{ env.release_filename }} ./edgetx-firmware-nightly
- name: Generate changelog
id: changelog
uses: orhun/git-cliff-action@v4
with:
config: cliff.toml
args: --verbose --strip all nightly..HEAD
env:
OUTPUT: CHANGELOG.md
- name: Update nightly tag
run: |
git config user.name github-actions
git config user.email github-actions@github.com
git tag -f nightly
git push -f origin nightly
- name: Delete previous nightly release
run: |
# Delete the existing nightly release to avoid accumulating assets
gh release delete nightly --yes || true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy nightly release
uses: softprops/action-gh-release@v2 #softprops/action-gh-release/pull/670 or v3 to clear nodejs log warning
with:
tag_name: nightly
prerelease: true
name: "Nightly Build"
body: |
## Automated Nightly Build
**Commit:** ${{ github.sha }}
**Built:** ${{ env.build_date }}
This is an automated nightly build of EdgeTX firmware. These builds are for testing purposes and may contain bugs. Please be sure to report any issues you encounter!
### Installation
Download `${{ env.release_filename }}` and extract the firmware file for your radio model, or use [EdgeTX Buddy](https://buddy.edgetx.org/#/flash?version=nightly&source=releases&filters=includePrereleases) with the "Include pre-releases" Filter option enabled so you can select the "Nightly" version.
## Changes since last nightly build
${{ steps.changelog.outputs.content }}
---
**Full Diff:** https://github.com/${{ github.repository }}/compare/${{ env.old_nightly_sha }}...${{ github.sha }}
files: |
${{ env.release_filename }}
+214
View File
@@ -0,0 +1,214 @@
name: Create Release Draft
on:
workflow_dispatch:
inputs:
tag:
description: 'Tag to create release for'
required: true
type: string
permissions:
contents: write
jobs:
release:
name: Create draft release
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Check out the repo
uses: actions/checkout@v6
with:
ref: ${{ inputs.tag }}
fetch-depth: 0
- name: Clean up any previous drafts for this version
run: |
TAG="${{ inputs.tag }}"
if gh release view "$TAG" --json isDraft --jq '.isDraft' 2>/dev/null | grep -q true; then
echo "Deleting existing draft release for tag: $TAG"
gh release delete "$TAG" --yes
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version info
id: version
continue-on-error: false
run: |
VERSION="${{ inputs.tag }}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
# Extract codename from CMakeLists.txt
if [ -f "CMakeLists.txt" ]; then
CODENAME=$(grep -oP 'set\(CODENAME\s+"\K[^"]+' CMakeLists.txt || echo "")
if [ -n "$CODENAME" ]; then
if [ "$CODENAME" = "dev" ]; then
echo "::error::CODENAME is set to 'dev' for a tagged release! Please update the CODENAME in CMakeLists.txt before creating a release."
exit 1
fi
echo "codename=$CODENAME" >> $GITHUB_OUTPUT
echo "has_codename=true" >> $GITHUB_OUTPUT
echo "Found codename: $CODENAME"
else
echo "has_codename=false" >> $GITHUB_OUTPUT
echo "No codename found"
fi
else
echo "has_codename=false" >> $GITHUB_OUTPUT
echo "CMakeLists.txt not found"
fi
- name: Install git-cliff
run: |
VERSION=$(wget -qO- https://api.github.com/repos/orhun/git-cliff/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' | sed 's/^v//')
FILENAME="git-cliff-${VERSION}-x86_64-unknown-linux-gnu.tar.gz"
wget -O git-cliff.tar.gz "https://github.com/orhun/git-cliff/releases/download/v${VERSION}/${FILENAME}"
tar -xzvf git-cliff.tar.gz
mv git-cliff-${VERSION}/git-cliff /usr/local/bin/
- name: Generate Changelog and Contributors List
id: changelog
run: |
# Find the previous tag relative to the input tag
PREV_TAG=$(git describe --tags --abbrev=0 "${{ inputs.tag }}^" 2>/dev/null || echo "")
if [ -z "$PREV_TAG" ]; then
echo "No previous tag found. Generating full history for ${{ inputs.tag }}"
RANGE="${{ inputs.tag }}"
CONTRIB_RANGE="${{ inputs.tag }}"
else
echo "Found previous tag: $PREV_TAG"
RANGE="$PREV_TAG..${{ inputs.tag }}"
CONTRIB_RANGE="$RANGE"
fi
# Generate the changelog using the explicit range
git-cliff $RANGE --strip all --output CHANGELOG.md
# Get unique contributors from the same commit range
CONTRIBUTORS=$(git log --format='%aN' $CONTRIB_RANGE 2>/dev/null | sort -u | sed 's/^/- /' || echo "")
# Combine
CHANGELOG_CONTENT=$(cat CHANGELOG.md)
{
echo "changelog<<EOF"
echo "$CHANGELOG_CONTENT"
if [ -n "$CONTRIBUTORS" ]; then
echo ""
echo "## Thank you to the following contributors who made this release possible!"
echo "$CONTRIBUTORS"
fi
echo "EOF"
} >> $GITHUB_OUTPUT
- name: Download all release artifacts
run: |
TAG="${{ inputs.tag }}"
COMMIT_SHA=$(git rev-parse "${TAG}^{commit}")
echo "Tag $TAG points to commit: $COMMIT_SHA"
# Get the latest successful run ID for each unique workflow
WORKFLOW_RUNS=$(gh run list --commit "$COMMIT_SHA" --status success --json workflowName,databaseId,createdAt --jq 'group_by(.workflowName) | map(sort_by(.createdAt) | last) | .[].databaseId')
mkdir -p release-assets
for run_id in $WORKFLOW_RUNS; do
echo "Processing Run ID: $run_id"
gh run download "$run_id" --dir release-assets 2>/dev/null || true
done
cd release-assets
# Clean up: Delete any folders ending in "-logs" BEFORE we start zipping
echo "Cleaning up log artifacts..."
rm -rf *-logs/
# Package remaining folders into zips
for dir in */; do
# Ensure we are actually looking at a directory
[ -d "$dir" ] || continue
dir_name="${dir%/}"
echo "Processing folder: $dir_name"
# Check for existing zip inside (e.g. if firmware was already zipped)
INNER_ZIP=$(find "$dir" -maxdepth 1 -name "*.zip" | head -n 1)
if [ -n "$INNER_ZIP" ]; then
echo " Found existing zip, pulling it out..."
mv "$INNER_ZIP" "${dir_name}.zip"
else
echo " No zip found, creating ${dir_name}.zip..."
(cd "$dir" && zip -r "../${dir_name}.zip" .)
fi
# Remove the folder after zipping/moving
rm -rf "$dir"
done
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: List release assets
run: |
echo "Release assets to upload:"
ls -lh release-assets/*.zip
- name: Create draft release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ inputs.tag }}
draft: true
prerelease: ${{ contains(inputs.tag, '-rc') || contains(inputs.tag, '-beta') || contains(inputs.tag, '-alpha') }}
name: EdgeTX "${{ steps.version.outputs.codename }}" ${{ steps.version.outputs.version }}
body: |
We are pleased to offer EdgeTX "${{ steps.version.outputs.codename }}" ${{ steps.version.outputs.version }}.
${{ (contains(inputs.tag, '-rc') || contains(inputs.tag, '-beta') || contains(inputs.tag, '-alpha')) && '> [!WARNING]
> As this is a pre-release, it is virtually guaranteed there will still be some minor issues that need resolving before final release - some may be documented already under "known issues" section of these release notes or the associated tracking issue. During pre-releases, the matching SD card pack and voice pack versions to use are those marked/tagged ''Latest''.
>
> We **need** _your_ help in testing this release to ensure there are no major bugs or faults that will cause problems during flight. Please ensure you back up your model and radio settings before updating, fully bench-test your models after updating, and report any issues you encounter. It is only with your feedback and testing by you, our community (and the assistance provided by partner manufacturers) which will allow us to identify and squash both new and old bugs, and progress onto a stable release version!! We simply cannot do this without you! :hugs: :beers:' || '' }}
> [!WARNING]
> If you are using a STM32 F2 based radio, *stop right now*, this release is not for you! EdgeTX v2.11 is the last release series to support these radios. Check [this list](https://hackmd.io/@edgetx/B12oQyQKye) if you are unsure if this affects you. Because of this, EdgeTX v2.11 will continue to be supported alongside 2.12, 3.0, etc., with a focus on bug fixes.
> [!WARNING]
> Radios based on STM32H7 MCUs update differently to older radios. Do **not** use the "Flash via USB" option in EdgeTX Buddy with them (yet). To update radios such as the Flysky PA01 & ST16, Jumper T15 Pro, RM TX15 and RM TX16S MK3, please follow the instructions in the [EdgeTX Manual](https://manual.edgetx.org/installing-and-updating-edgetx/updating-your-stm32h7-radio).
> [!NOTE]
> For MacOS users, Companion is now only compiled for MacOS 12 and above. Stay with v2.10.5 or earlier if you need support for an older version of MacOS.
> [!NOTE]
> **Migration Information**
> - If you have an old OpenTX Companion (`.otx`) file you need to convert or open, you will need to use EdgeTX Companion v2.10 or earlier.
> - If you wish to upgrade from OpenTX 2.3 or EdgeTX 2.4/2.5, you must use either EdgeTX v2.8.x firmware or EdgeTX Companion v2.10.x.
>
> See the [EdgeTX Manual](https://manual.edgetx.org/installing-and-updating-edgetx) for more information.
## Changes
${{ steps.changelog.outputs.changelog }}
## Supported radios
The full list of supported radios and their support status can be viewed [here on the EdgeTX website](https://edgetx.org/supportedradios).
## Installation Guide
https://manual.edgetx.org/installing-and-updating-edgetx
## Flash firmware via Chrome based browser
https://buddy.edgetx.org/#/flash?version=${{ steps.version.outputs.version }}
## Language and Custom builds
[CloudBuild option in EdgeTX Buddy](https://buddy.edgetx.org/) will allow you build your own (supported) language firmware, with just a few clicks. Additionally, EdgeTX Companion also has some support for CloudBuild, and will automatically fetch firmware for a supported language when you use the "Update components" option. But you can also build your own firmware online [following this guide](https://github.com/EdgeTX/edgetx/wiki/Building-radio-firmware-in-a-web-browser-with-GitHub-CodeSpaces), request a specific build at TODO or ask on Discord for someone to build one for you.
## Known Limitations and Issues
${{ (contains(inputs.tag, '-rc') || contains(inputs.tag, '-beta') || contains(inputs.tag, '-alpha')) && '> [!WARNING]
- Please check TODO during the release candidate phase for any other release candidate-specific issues and status of fixes.' || '' }}
## UI/UX behavioral changes and/or new capabilities:
files: release-assets/*.zip
fail_on_unmatched_files: true
+50
View File
@@ -0,0 +1,50 @@
name: Sync from EdgeTX upstream
on:
schedule:
- cron: '0 2 * * 1' # Weekly on Monday at 2 AM UTC
workflow_dispatch: # Manual trigger option
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.SYNC_PAT }}
- name: Configure git
run: |
git config user.name "EdgeTX Sync Bot"
git config user.email "sync-bot@edgetx.org"
- name: Sync branches from upstream (hard reset)
env:
BRANCHES: "main 2.11" # Add/modify branches here as needed
run: |
git remote add upstream https://github.com/EdgeTX/edgetx.git
git fetch upstream
for branch in $BRANCHES; do
echo "=== Syncing branch: $branch ==="
# Check if upstream has this branch
if ! git ls-remote --heads upstream | grep -q "refs/heads/$branch"; then
echo "⚠️ Branch $branch does not exist in upstream, skipping..."
continue
fi
# Hard reset to upstream (creates exact copy with same SHA)
git checkout -B $branch upstream/$branch
# Force push to origin
git push origin $branch --force
echo "✓ Branch $branch synced (SHA: $(git rev-parse HEAD))"
done
echo ""
echo "=== Sync Complete ==="
echo "All specified branches are now exact copies of upstream"
+31
View File
@@ -0,0 +1,31 @@
---
name: Validate fw.json
on:
push:
branches:
- 'main'
- '[0-9]+.[0-9]+'
tags:
- v*
paths:
- 'fw.json'
pull_request:
branches:
- 'main'
- '[0-9]+.[0-9]+'
paths:
- 'fw.json'
workflow_dispatch:
jobs:
json-validate:
name: Validate fw.json file
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Validate fw.json syntax and schema
run: python3 tools/validate-json.py --syntax-only
- name: Validate fw.json alphabetical order
run: python3 tools/validate-json.py --order-only
+41
View File
@@ -0,0 +1,41 @@
name: Validate hw_defs
on:
push:
branches:
- 'main'
- '[0-9]+.[0-9]+'
paths:
- 'radio/util/hw_defs/**'
- 'radio/src/boards/hw_defs/**'
pull_request:
paths:
- 'radio/util/hw_defs/**'
- 'radio/src/boards/hw_defs/**'
workflow_dispatch:
jobs:
validate:
name: Validate hw_defs
runs-on: ubuntu-latest
defaults:
run:
working-directory: radio/util/hw_defs
steps:
- name: Check out the repo
uses: actions/checkout@v6
- name: Set up uv
uses: astral-sh/setup-uv@v7
- name: Install Python dependencies
working-directory: .
run: uv venv && uv pip install -r requirements.txt
- name: Check Python script syntax
run: uv run python3 -m py_compile *.py
- name: Validate JSON schemas
run: uv run python3 json_validator.py ../../src/boards/hw_defs
- name: Test template generation (all boards x all templates)
run: uv run python3 test_templates.py ../../src/boards/hw_defs
+29
View File
@@ -0,0 +1,29 @@
/.*project
/.settings
/.autotools
/*.user
/*.user.*
/doc/
/build*/
/cmake-build-*/
/debian/
/*.vscode
radio/src/tests/googletest*
radio/src/tests/gtest*.tar.gz
.editorconfig
.cache
compile_commands.json
/ys_Doc
/.idea
/.vs
CMakeUserPresets.json
.DS_Store
_deps
_wasi_sdk_fetch
*.pyc
# MkDocs
/site/
output/
.devcontainer/devcontainer-lock.json
+18
View File
@@ -0,0 +1,18 @@
[submodule "radio/src/thirdparty/AccessDenied"]
path = radio/src/thirdparty/AccessDenied
url = https://github.com/raphaelcoeffic/AccessDenied.git
[submodule "radio/src/thirdparty/FreeRTOS"]
path = radio/src/thirdparty/FreeRTOS
url = https://github.com/FreeRTOS/FreeRTOS-Kernel.git
shallow = true
[submodule "radio/src/thirdparty/stb"]
path = radio/src/thirdparty/stb
url = https://github.com/nothings/stb.git
[submodule "radio/src/thirdparty/lvgl"]
path = radio/src/thirdparty/lvgl
url = https://github.com/EdgeTX/lvgl.git
branch = release/v8.2
[submodule "radio/src/thirdparty/uf2"]
path = radio/src/thirdparty/uf2
url = https://github.com/microsoft/uf2.git
shallow = true
+149
View File
@@ -0,0 +1,149 @@
cmake_minimum_required(VERSION 3.21)
project(EdgeTX)
set(VERSION_MAJOR "3")
set(VERSION_MINOR "0")
set(VERSION_REVISION "0")
set(CODENAME "dev")
string(TIMESTAMP BUILD_YEAR "%Y")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
if(MINGW OR WIN32)
set(CMAKE_OBJECT_PATH_MAX 200)
endif()
if(DEFINED ENV{EDGETX_VERSION_TAG})
set(VERSION_TAG "$ENV{EDGETX_VERSION_TAG}")
else()
if(DEFINED ENV{EDGETX_VERSION_PREFIX})
set(VERSION_PREFIX "$ENV{EDGETX_VERSION_PREFIX}-")
else()
set(VERSION_PREFIX "pre-") # Default prefix for when not tagged
endif()
if(DEFINED ENV{EDGETX_VERSION_SUFFIX})
set(VERSION_SUFFIX "-$ENV{EDGETX_VERSION_SUFFIX}")
else()
set(VERSION_SUFFIX "-selfbuild") # Default suffix for when not in automated build environments
endif()
endif()
set(VERSION_FAMILY ${VERSION_MAJOR}.${VERSION_MINOR})
set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REVISION})
option(EdgeTX_SUPERBUILD "Cross-compile EdgeTX for each toolchain" ON)
include(ExternalProject)
if(EdgeTX_SUPERBUILD)
include(Macros)
CollectCommandLineArgs(CMAKE_ARGS)
message("-- CMAKE_ARGS: ${CMAKE_ARGS}")
message("-- CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
# Add explicit targets for triggering cmake in the external projects
set_property(DIRECTORY PROPERTY EP_STEP_TARGETS configure clean)
# Native targets
ExternalProject_Add(native
SOURCE_DIR ${CMAKE_SOURCE_DIR}
BINARY_DIR ${CMAKE_BINARY_DIR}/native
CMAKE_ARGS ${CMAKE_ARGS} -Wno-dev -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
CMAKE_CACHE_ARGS
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=${CMAKE_SOURCE_DIR}/cmake/toolchain/native.cmake
-DEdgeTX_SUPERBUILD:BOOL=0
-DNATIVE_BUILD:BOOL=1
INSTALL_COMMAND ""
EXCLUDE_FROM_ALL TRUE
)
# ARM targets
ExternalProject_Add(arm-none-eabi
SOURCE_DIR ${CMAKE_SOURCE_DIR}
BINARY_DIR ${CMAKE_BINARY_DIR}/arm-none-eabi
CMAKE_ARGS ${CMAKE_ARGS} -Wno-dev -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
CMAKE_CACHE_ARGS
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=${CMAKE_SOURCE_DIR}/cmake/toolchain/arm-none-eabi.cmake
-DEdgeTX_SUPERBUILD:BOOL=0
-DNATIVE_BUILD:BOOL=0
INSTALL_COMMAND ""
EXCLUDE_FROM_ALL TRUE
)
# Webasm targets — deferred to a separate CMake project so that
# the WASI SDK is only fetched when wasi-module is actually built.
add_custom_target(configure
DEPENDS native-configure arm-none-eabi-configure)
add_custom_target(simulator
COMMAND ${CMAKE_COMMAND} --build native --target simulator
DEPENDS native-configure
)
add_custom_target(simu
COMMAND ${CMAKE_COMMAND} --build native --target simu
DEPENDS native-configure
)
add_custom_target(wasi-module
COMMAND ${CMAKE_COMMAND}
-S ${CMAKE_SOURCE_DIR}/cmake/wasm
-B ${CMAKE_BINARY_DIR}/wasm
-DEDGETX_SOURCE_DIR=${CMAKE_SOURCE_DIR}
-DEDGETX_BUILD_TYPE=${CMAKE_BUILD_TYPE}
${CMAKE_ARGS}
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}/wasm
)
add_custom_target(companion
COMMAND ${CMAKE_COMMAND} --build native --target companion
DEPENDS native-configure
)
add_custom_target(gtests-radio
COMMAND ${CMAKE_COMMAND} --build native --target gtests-radio
DEPENDS native-configure
)
add_custom_target(tests-radio
COMMAND ${CMAKE_COMMAND} --build native --target tests-radio
DEPENDS native-configure
)
add_custom_target(bootloader
COMMAND ${CMAKE_COMMAND} --build arm-none-eabi --target bootloader
DEPENDS arm-none-eabi-configure
)
add_custom_target(firmware
COMMAND ${CMAKE_COMMAND} --build arm-none-eabi --target firmware
DEPENDS arm-none-eabi-configure
)
add_custom_target(firmware-size
COMMAND ${CMAKE_COMMAND} --build arm-none-eabi --target firmware-size
DEPENDS arm-none-eabi-configure
)
# Quit cmake at this point
return()
endif()
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake)
include(GenericDefinitions)
if (VERSION_TAG)
message(STATUS "EdgeTX ${VERSION_TAG} @ ${GIT_STR}")
else()
message(STATUS "EdgeTX ${VERSION_PREFIX}${VERSION}${VERSION_SUFFIX} @ ${GIT_STR}")
endif()
if(NATIVE_BUILD AND NOT WASI)
include(NativeTargets)
else()
# Prevent CMake warnings
set(IGNORE "${LIBUSB1_ROOT_DIR}" "${OPENSSL_ROOT_DIR}")
endif()
add_subdirectory(${RADIO_SRC_DIR})
+78
View File
@@ -0,0 +1,78 @@
{
"version": 6,
"configurePresets": [
{
"name": "firmware",
"displayName": "Firmware",
"binaryDir": "build/fw",
"toolchainFile": "cmake/toolchain/arm-none-eabi.cmake",
"cacheVariables": {
"EdgeTX_SUPERBUILD": false,
"NATIVE_BUILD": false
},
"warnings": {
"dev": false
}
},
{
"name": "companion",
"displayName": "Companion",
"binaryDir": "build/companion",
"toolchainFile": "cmake/toolchain/native.cmake",
"cacheVariables": {
"EdgeTX_SUPERBUILD": false,
"NATIVE_BUILD": true
},
"warnings": {
"dev": false
}
},
{
"name": "simu",
"displayName": "SDL Simu",
"binaryDir": "build/simu",
"toolchainFile": "cmake/toolchain/native.cmake",
"cacheVariables": {
"EdgeTX_SUPERBUILD": false,
"NATIVE_BUILD": true
},
"warnings": {
"dev": false
}
}
],
"buildPresets": [
{
"name": "firmware",
"displayName": "Firmware",
"configurePreset": "firmware",
"targets": [
"firmware"
]
},
{
"name": "companion",
"displayName": "Companion",
"configurePreset": "companion",
"targets": [
"companion"
]
},
{
"name": "simu",
"displayName": "SDL Simu",
"configurePreset": "simu",
"targets": [
"simu"
]
},
{
"name": "tests-radio",
"displayName": "Radio Tests",
"configurePreset": "simu",
"targets": [
"tests-radio"
]
}
]
}
+339
View File
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
+51
View File
@@ -0,0 +1,51 @@
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/Edgetx/edgetx)](https://github.com/EdgeTX/edgetx/releases/latest)
[![GitHub all releases](https://img.shields.io/github/downloads/EdgeTX/edgetx/total)](https://github.com/EdgeTX/edgetx/releases)
[![GitHub license](https://img.shields.io/github/license/Edgetx/edgetx)](https://github.com/EdgeTX/edgetx/blob/main/LICENSE)
[![Commit Tests](https://github.com/EdgeTX/edgetx/actions/workflows/build_fw.yml/badge.svg)](https://github.com/EdgeTX/edgetx/actions/workflows/build_fw.yml)
[![GitHub CodesSpaces ready-to-code](https://img.shields.io/badge/GitHub%20CodesSpaces-ready--to--code-blue?logo=github)](https://codespaces.new/EdgeTX/edgetx)
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org)
[![Discord](https://img.shields.io/discord/839849772864503828.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/wF9wUKnZ6H)
[![Support us on OpenCollective](https://img.shields.io/opencollective/all/edgetx)](https://opencollective.com/edgetx)
<p align="center">
<a href="https://raw.githubusercontent.com/EdgeTX/edgetx.github.io/master/docs/assets/logo.png"><img src="https://raw.githubusercontent.com/EdgeTX/edgetx.github.io/master/docs/assets/logo.png" align="center" height="150" width="150" ></a>
# Welcome to EdgeTX!
**The cutting edge open-source firmware for your R/C radio!**
### About EdgeTX
EdgeTX is the cutting edge of OpenTX. It is the place where innovative ideas and cutting-edge features are developed and field-tested by the enthusiasts of our hobby. EdgeTX is a community project ideas from the community, developed by the community, and enjoyed by the community! The community will always have a say in what EdgeTX is and what EdgeTX will be in the future. Without community feedback and involvement EdgeTX cannot exist.
### Community
- [Discord](https://discord.gg/wF9wUKnZ6H)
- [Facebook](https://www.facebook.com/groups/edgetx)
- [Github Discussions](https://github.com/EdgeTX/edgetx/discussions)
### Navigation Links
- [Community Guidelines](https://github.com/EdgeTX/edgetx.github.io/wiki/Community-Guidlines)
- [Installation Guide](https://manual.edgetx.org/installing-and-updating-edgetx/update-from-opentx-to-edgetx)
- [Installation Video](https://www.youtube.com/watch?v=Y9OvW9XCjOs)
- [Reporting Issues / Requesting features](https://github.com/EdgeTX/edgetx/issues/new/choose)
- [Lua Documentation Site](https://luadoc.edgetx.org/)
- Buddy: [Info](https://github.com/EdgeTX/buddy) - [Downloads](https://github.com/EdgeTX/buddy/releases)
- SD Card: [Info](https://github.com/EdgeTX/edgetx-sdcard) - [Downloads](https://github.com/EdgeTX/edgetx-sdcard/releases)
- Sound Packs: [Info](https://github.com/EdgeTX/edgetx-sdcard-sounds) - [Downloads](https://github.com/EdgeTX/edgetx-sdcard-sounds/releases)
- [Developer Documentation](https://edgetx.org/edgetx/latest/) - [Docker Build Environment](https://github.com/EdgeTX/build-edgetx)
## Acknowledgements
Some icon assets provided by [ICONS8](https://icons8.com).</br>
Lua Documentation site powered with the kind support of [GitBook](https://www.gitbook.com).
+232
View File
@@ -0,0 +1,232 @@
# Configuration for git-cliff changelog generation
# https://git-cliff.org/docs/configuration
[changelog]
header = ""
body = """
{% for group, commits in commits | group_by(attribute="group") -%}
{% if group == "🚀 Features" -%}
### {{ group }}
{% set commits_by_scope = commits | group_by(attribute="scope") -%}
{% if commits_by_scope.firmware is defined or commits_by_scope.fw is defined -%}
#### 🎮 Firmware
{% if commits_by_scope.firmware is defined -%}{% for commit in commits_by_scope.firmware -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% if commits_by_scope.fw is defined -%}{% for commit in commits_by_scope.fw -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% endif -%}
{% if commits_by_scope.bw is defined -%}
#### 🖥️ Black & White Radio
{% for commit in commits_by_scope.bw -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}
{% endif -%}
{% if commits_by_scope.color is defined or commits_by_scope.colour is defined -%}
#### 🎨 Color LCD Radio
{% if commits_by_scope.color is defined -%}{% for commit in commits_by_scope.color -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% if commits_by_scope.colour is defined -%}{% for commit in commits_by_scope.colour -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% endif -%}
{% if commits_by_scope.cpn is defined or commits_by_scope.companion is defined -%}
#### 💻 Companion
{% if commits_by_scope.cpn is defined -%}{% for commit in commits_by_scope.cpn -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% if commits_by_scope.companion is defined -%}{% for commit in commits_by_scope.companion -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% endif -%}
{% set_global has_other = false -%}
{% for commit in commits -%}{% if commit.scope != "firmware" and commit.scope != "fw" and commit.scope != "bw" and commit.scope != "color" and commit.scope != "colour" and commit.scope != "cpn" and commit.scope != "companion" -%}{% set_global has_other = true -%}{% endif -%}{% endfor -%}
{% if has_other -%}
#### 📦 Other
{% for commit in commits -%}{% if commit.scope != "firmware" and commit.scope != "fw" and commit.scope != "bw" and commit.scope != "color" and commit.scope != "colour" and commit.scope != "cpn" and commit.scope != "companion" -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endif -%}{% endfor -%}
{% endif -%}
{% endif -%}
{% endfor -%}
{% for group, commits in commits | group_by(attribute="group") -%}
{% if group == "🐛 Bug Fixes" -%}
### {{ group }}
{% set commits_by_scope = commits | group_by(attribute="scope") -%}
{% if commits_by_scope.firmware is defined or commits_by_scope.fw is defined -%}
#### 🎮 Firmware
{% if commits_by_scope.firmware is defined -%}{% for commit in commits_by_scope.firmware -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% if commits_by_scope.fw is defined -%}{% for commit in commits_by_scope.fw -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% endif -%}
{% if commits_by_scope.bw is defined -%}
#### 🖥️ Black & White Radio
{% for commit in commits_by_scope.bw -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}
{% endif -%}
{% if commits_by_scope.color is defined or commits_by_scope.colour is defined -%}
#### 🎨 Color LCD Radio
{% if commits_by_scope.color is defined -%}{% for commit in commits_by_scope.color -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% if commits_by_scope.colour is defined -%}{% for commit in commits_by_scope.colour -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% endif -%}
{% if commits_by_scope.cpn is defined or commits_by_scope.companion is defined -%}
#### 💻 Companion
{% if commits_by_scope.cpn is defined -%}{% for commit in commits_by_scope.cpn -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% if commits_by_scope.companion is defined -%}{% for commit in commits_by_scope.companion -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% endif -%}
{% set_global has_other = false -%}
{% for commit in commits -%}{% if commit.scope != "firmware" and commit.scope != "fw" and commit.scope != "bw" and commit.scope != "color" and commit.scope != "colour" and commit.scope != "cpn" and commit.scope != "companion" -%}{% set_global has_other = true -%}{% endif -%}{% endfor -%}
{% if has_other -%}
#### 📦 Other
{% for commit in commits -%}{% if commit.scope != "firmware" and commit.scope != "fw" and commit.scope != "bw" and commit.scope != "color" and commit.scope != "colour" and commit.scope != "cpn" and commit.scope != "companion" -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endif -%}{% endfor -%}
{% endif -%}
{% endif -%}
{% endfor -%}
{% for group, commits in commits | group_by(attribute="group") -%}
{% if group != "🚀 Features" and group != "🐛 Bug Fixes" and group != "📦 Other" -%}
### {{ group }}
{% set commits_by_scope = commits | group_by(attribute="scope") -%}
{% if commits_by_scope.firmware is defined or commits_by_scope.fw is defined -%}
#### 🎮 Firmware
{% if commits_by_scope.firmware is defined -%}{% for commit in commits_by_scope.firmware -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% if commits_by_scope.fw is defined -%}{% for commit in commits_by_scope.fw -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% endif -%}
{% if commits_by_scope.bw is defined -%}
#### 🖥️ Black & White Radio
{% for commit in commits_by_scope.bw -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}
{% endif -%}
{% if commits_by_scope.color is defined or commits_by_scope.colour is defined -%}
#### 🎨 Color LCD Radio
{% if commits_by_scope.color is defined -%}{% for commit in commits_by_scope.color -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% if commits_by_scope.colour is defined -%}{% for commit in commits_by_scope.colour -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% endif -%}
{% if commits_by_scope.cpn is defined or commits_by_scope.companion is defined -%}
#### 💻 Companion
{% if commits_by_scope.cpn is defined -%}{% for commit in commits_by_scope.cpn -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% if commits_by_scope.companion is defined -%}{% for commit in commits_by_scope.companion -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% endif -%}
{% set_global has_other = false -%}
{% for commit in commits -%}{% if commit.scope != "firmware" and commit.scope != "fw" and commit.scope != "bw" and commit.scope != "color" and commit.scope != "colour" and commit.scope != "cpn" and commit.scope != "companion" -%}{% set_global has_other = true -%}{% endif -%}{% endfor -%}
{% if has_other -%}
#### 📦 Other
{% for commit in commits -%}{% if commit.scope != "firmware" and commit.scope != "fw" and commit.scope != "bw" and commit.scope != "color" and commit.scope != "colour" and commit.scope != "cpn" and commit.scope != "companion" -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endif -%}{% endfor -%}
{% endif -%}
{% endif -%}
{% endfor -%}
{% for group, commits in commits | group_by(attribute="group") -%}
{% if group == "📦 Other" -%}
### {{ group }}
{% set commits_by_scope = commits | group_by(attribute="scope") -%}
{% if commits_by_scope.firmware is defined or commits_by_scope.fw is defined -%}
#### 🎮 Firmware
{% if commits_by_scope.firmware is defined -%}{% for commit in commits_by_scope.firmware -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% if commits_by_scope.fw is defined -%}{% for commit in commits_by_scope.fw -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% endif -%}
{% if commits_by_scope.bw is defined -%}
#### 🖥️ Black & White Radio
{% for commit in commits_by_scope.bw -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}
{% endif -%}
{% if commits_by_scope.color is defined or commits_by_scope.colour is defined -%}
#### 🎨 Color LCD Radio
{% if commits_by_scope.color is defined -%}{% for commit in commits_by_scope.color -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% if commits_by_scope.colour is defined -%}{% for commit in commits_by_scope.colour -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% endif -%}
{% if commits_by_scope.cpn is defined or commits_by_scope.companion is defined -%}
#### 💻 Companion
{% if commits_by_scope.cpn is defined -%}{% for commit in commits_by_scope.cpn -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% if commits_by_scope.companion is defined -%}{% for commit in commits_by_scope.companion -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endfor -%}{% endif -%}
{% endif -%}
{% set_global has_other = false -%}
{% for commit in commits -%}{% if commit.scope != "firmware" and commit.scope != "fw" and commit.scope != "bw" and commit.scope != "color" and commit.scope != "colour" and commit.scope != "cpn" and commit.scope != "companion" -%}{% set_global has_other = true -%}{% endif -%}{% endfor -%}
{% if has_other -%}
#### 📦 Other
{% for commit in commits -%}{% if commit.scope != "firmware" and commit.scope != "fw" and commit.scope != "bw" and commit.scope != "color" and commit.scope != "colour" and commit.scope != "cpn" and commit.scope != "companion" -%}
- {{ commit.message | split(pat="\\n") | first | trim }}
{% endif -%}{% endfor -%}
{% endif -%}
{% endif -%}
{% endfor -%}
"""
trim = true
[git]
conventional_commits = true
filter_unconventional = false
# Only look for nightly, release and rc tags
tag_pattern = "^(nightly|v[0-9]+\\.[0-9]+\\.[0-9]+(-rc[0-9]+)?)$"
# Ensures it looks at the branch structure
topo_order = true
# Repect the nearest tag on current branch
use_branch_tags = true
# Ensure it sorts from the tag forward
sort_commits = "oldest"
# Commit parsers - order matters! More specific patterns first
commit_parsers = [
# Type-based categorization (top-level headings)
{ message = "^feat", group = "🚀 Features" },
{ message = "^fix", group = "🐛 Bug Fixes" },
{ message = "^docs?", group = "📝 Documentation" },
{ message = "^perf", group = "⚡ Performance" },
{ message = "^refactor", group = "🔨 Refactoring" },
{ message = "^style", group = "🎨 Style" },
{ message = "^test", group = "🧪 Tests" },
{ message = "^chore", group = "🧹 Chores" },
{ message = "^ci", group = "🔧 CI/CD" },
# Everything else
{ body = ".*", group = "📦 Other" },
]
Executable
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
cmake --target simu "$@"
+64
View File
@@ -0,0 +1,64 @@
macro(add_bitmaps_target targetname filter format args)
set(bitmaps_files)
file(GLOB bitmaps ${filter})
foreach(bitmap ${bitmaps})
get_filename_component(target ${bitmap} NAME_WE)
set(target ${target}.lbm)
add_custom_command(
OUTPUT ${target}
COMMAND ${PYTHON_EXECUTABLE} ${UTILS_DIR}/encode-bitmap.py --format ${format} ${args} ${bitmap} ${target}
DEPENDS ${bitmap} ${UTILS_DIR}/encode-bitmap.py
)
list(APPEND bitmaps_files ${target})
endforeach()
add_custom_target(${targetname} DEPENDS ${bitmaps_files})
endmacro()
macro(add_png_target targetname filter)
set(bitmaps_files)
file(GLOB bitmaps ${filter})
foreach(bitmap ${bitmaps})
get_filename_component(target ${bitmap} NAME_WE)
set(target ${target}.lbm)
add_custom_command(
OUTPUT ${target}
COMMAND ${PYTHON_EXECUTABLE} ${UTILS_DIR}/bin2lbm.py ${bitmap} ${target}
DEPENDS ${bitmap} ${UTILS_DIR}/bin2lbm.py
)
list(APPEND bitmaps_files ${target})
endforeach()
add_custom_target(${targetname} DEPENDS ${bitmaps_files})
endmacro()
macro(add_fonts_target targetname filter)
set(fonts_files)
file(GLOB fonts ${filter})
foreach(font ${fonts})
get_filename_component(target ${font} NAME_WE)
set(target ${target}.lbm)
add_custom_command(
OUTPUT ${target}
COMMAND ${PYTHON_EXECUTABLE} ${TOOLS_DIR}/encode-font.py ${font} ${target}
DEPENDS ${font} ${TOOLS_DIR}/encode-font.py
)
list(APPEND fonts_files ${target})
endforeach()
add_custom_target(${targetname} DEPENDS ${fonts_files})
endmacro()
macro(add_truetype_font_target name size subset effect args)
set(target font_${name})
if(${effect} STREQUAL "bold")
set(font ${FONT_BOLD})
else()
set(font ${FONT})
endif()
add_custom_command(
OUTPUT ${target}.png ${target}.specs ${target}.lbm
COMMAND ${PYTHON_EXECUTABLE} ${TOOLS_DIR}/build-font-bitmap.py --subset ${subset} --size ${size} --font ${font} --output ${target}
COMMAND ${PYTHON_EXECUTABLE} ${UTILS_DIR}/bin2lbm.py ${target}.png ${target}.lbm
DEPENDS ${TOOLS_DIR}/build-font-bitmap.py ${TOOLS_DIR}/charset.py ${UTILS_DIR}/encode-bitmap.py
)
add_custom_target(truetype_font_${name} DEPENDS ${target}.png ${target}.specs)
set(truetype_fonts_targets ${truetype_fonts_targets} truetype_font_${name})
endmacro()
+5
View File
@@ -0,0 +1,5 @@
# Copy all WASM simulator modules from SRC_DIR to DST_DIR at build time.
# Invoked via cmake -P so the pattern is evaluated on every build.
file(INSTALL "${SRC_DIR}/" DESTINATION "${DST_DIR}"
MESSAGE_NEVER
FILES_MATCHING PATTERN "edgetx-*-simulator.wasm")
+21
View File
@@ -0,0 +1,21 @@
# Fetch gtest source code from Github
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest
GIT_TAG f8d7d77c06936315286eb55f8de22cd23c188571 # v1.14.0
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
option(BUILD_GMOCK "Build GMock" OFF)
option(INSTALL_GTEST "Install GTest" OFF)
FetchContent_MakeAvailable(googletest)
include_directories(
${googletest_SOURCE_DIR}/googletest/include
)
+31
View File
@@ -0,0 +1,31 @@
# Fetch imgui source code from Github
include(FetchContent)
FetchContent_Declare(
imgui
GIT_REPOSITORY https://github.com/ocornut/imgui
GIT_TAG dbb5eeaadffb6a3ba6a60de1290312e5802dba5a # v1.91.8
GIT_SHALLOW TRUE
UPDATE_DISCONNECTED TRUE
)
FetchContent_MakeAvailable(imgui)
message("Fetched imgui source code from Github: ${imgui_SOURCE_DIR}")
add_library(imgui STATIC
${imgui_SOURCE_DIR}/imgui.cpp
${imgui_SOURCE_DIR}/imgui_draw.cpp
${imgui_SOURCE_DIR}/imgui_tables.cpp
${imgui_SOURCE_DIR}/imgui_widgets.cpp
${imgui_SOURCE_DIR}/backends/imgui_impl_sdl2.cpp
${imgui_SOURCE_DIR}/backends/imgui_impl_sdlrenderer2.cpp
)
target_include_directories(imgui PUBLIC
${imgui_SOURCE_DIR}
${imgui_SOURCE_DIR}/backends
)
target_link_libraries(imgui PUBLIC SDL2::SDL2)
+17
View File
@@ -0,0 +1,17 @@
# Fetch maxLibQt source code from Github
include(FetchContent)
FetchContent_Declare(
maxLibQt
GIT_REPOSITORY https://github.com/edgetx/maxLibQt
GIT_TAG 7e433da60d3f2e975d46afc91804a88029cd1b78
)
FetchContent_MakeAvailable(maxLibQt)
message("Fetched maxLibQt source code from Github: ${maxLibQt_SOURCE_DIR}")
include_directories(
${maxLibQt_SOURCE_DIR}
${maxLibQt_BINARY_DIR}
)
+20
View File
@@ -0,0 +1,20 @@
# Fetch miniz source code from Github
include(FetchContent)
FetchContent_Declare(
miniz
GIT_REPOSITORY https://github.com/richgel999/miniz
GIT_TAG 89d7a5f6c3ce8893ea042b0a9d2a2d9975589ac9
)
FetchContent_MakeAvailable(miniz)
message("Fetched miniz source code from Github: ${miniz_SOURCE_DIR}")
include_directories(
${miniz_SOURCE_DIR}
${miniz_BINARY_DIR}
)
# suppress compiler warnings for this add-in only
target_compile_options(miniz PRIVATE -Wno-unused-function -Wno-newline-eof)
+42
View File
@@ -0,0 +1,42 @@
# Fetch rs_dfu
cmake_minimum_required(VERSION 3.14)
include(FetchContent)
# Determine platform and architecture
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(RS_DFU_PLATFORM "unknown-linux-gnu")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(RS_DFU_PLATFORM "apple-darwin")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(RS_DFU_PLATFORM "pc-windows-msvc")
else()
message(FATAL_ERROR "Unsupported platform: ${CMAKE_SYSTEM_NAME}")
endif()
if(APPLE)
set(RS_DFU_ARCH "universal")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64")
set(RS_DFU_ARCH "x86_64")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
set(RS_DFU_ARCH "aarch64")
else()
message(FATAL_ERROR "Unsupported architecture: ${CMAKE_SYSTEM_PROCESSOR}")
endif()
set(RS_DFU_BASE_URL "https://github.com/EdgeTX/rs-dfu/releases/latest/download")
set(RS_DFU_FILENAME "rs_dfu-${RS_DFU_ARCH}-${RS_DFU_PLATFORM}")
set(RS_DFU_URL "${RS_DFU_BASE_URL}/${RS_DFU_FILENAME}.tar.gz")
FetchContent_Declare(
RsDfu
URL ${RS_DFU_URL}
DOWNLOAD_EXTRACT_TIMESTAMP true
FIND_PACKAGE_ARGS
)
FetchContent_MakeAvailable(RsDfu)
set(rs_dfu_DIR "${rsdfu_SOURCE_DIR}/cmake")
message("Fetched rsdfu source code from Github: ${rsdfu_SOURCE_DIR}")
+63
View File
@@ -0,0 +1,63 @@
# Fetch and configure WAMR (WebAssembly Micro Runtime)
cmake_minimum_required(VERSION 3.14)
include(FetchContent)
# WAMR build configuration
set(WAMR_BUILD_INTERP 1)
set(WAMR_BUILD_FAST_INTERP 0)
set(WAMR_BUILD_AOT 0)
set(WAMR_BUILD_JIT 0)
set(WAMR_BUILD_LIBC_BUILTIN 1)
set(WAMR_BUILD_LIBC_WASI 1)
set(WAMR_BUILD_LIB_WASI_THREADS 1)
set(WAMR_BUILD_SHARED_MEMORY 1)
set(WAMR_BUILD_BULK_MEMORY 1)
set(WAMR_BUILD_REF_TYPES 1)
set(WAMR_BUILD_SIMD 0)
set(WAMR_BUILD_EXCE_HANDLING 1)
set(WAMR_DISABLE_HW_BOUND_CHECK 1)
FetchContent_Declare(
wamr
GIT_REPOSITORY https://github.com/bytecodealliance/wasm-micro-runtime
GIT_TAG WAMR-2.4.4
GIT_SHALLOW TRUE
)
# Populate separately so we can patch before building
FetchContent_GetProperties(wamr)
if(NOT wamr_POPULATED)
FetchContent_Populate(wamr)
# WAMR 2.4.4 win_file.c has trailing backslashes in comments that
# clang treats as line continuations, causing compile errors.
execute_process(
COMMAND git apply "${CMAKE_CURRENT_LIST_DIR}/wamr-fix-win-backslash-comments.patch"
WORKING_DIRECTORY "${wamr_SOURCE_DIR}"
RESULT_VARIABLE _patch_result
)
if(_patch_result)
message(WARNING "WAMR win_file.c patch failed (may already be applied)")
endif()
add_subdirectory("${wamr_SOURCE_DIR}" "${wamr_BINARY_DIR}" EXCLUDE_FROM_ALL)
if(WIN32)
# WAMR unconditionally adds -lm and -ldl as PUBLIC link libraries,
# which don't exist on Windows.
get_target_property(_vmlib_libs vmlib INTERFACE_LINK_LIBRARIES)
if(_vmlib_libs)
list(REMOVE_ITEM _vmlib_libs "-lm" "-ldl")
set_target_properties(vmlib PROPERTIES INTERFACE_LINK_LIBRARIES "${_vmlib_libs}")
endif()
# WAMR headers use __declspec(dllimport/dllexport) on MSVC-compatible
# compilers, but we link vmlib statically. Override both API macros to
# empty for vmlib itself (PUBLIC = PRIVATE + INTERFACE).
target_compile_definitions(vmlib PUBLIC
WASM_RUNTIME_API_EXTERN=
WASM_API_EXTERN=
)
endif()
endif()
+41
View File
@@ -0,0 +1,41 @@
cmake_minimum_required(VERSION 3.14)
set(WASI_SDK_VERSION "25")
set(WASI_SDK_FULL_VERSION "${WASI_SDK_VERSION}.0")
# Determine platform and architecture
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
set(WASI_SDK_PLATFORM "linux")
elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
set(WASI_SDK_PLATFORM "macos")
elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
set(WASI_SDK_PLATFORM "windows")
else()
message(FATAL_ERROR "Unsupported platform: ${CMAKE_HOST_SYSTEM_NAME}")
endif()
if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64")
set(WASI_SDK_ARCH "x86_64")
elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
set(WASI_SDK_ARCH "arm64")
else()
message(FATAL_ERROR "Unsupported Linux architecture: ${CMAKE_HOST_SYSTEM_PROCESSOR}")
endif()
set(WASI_SDK_BASE_URL "https://github.com/WebAssembly/wasi-sdk/releases/download")
set(WASI_SDK_FILENAME "wasi-sdk-${WASI_SDK_VERSION}.0-${WASI_SDK_ARCH}-${WASI_SDK_PLATFORM}")
set(WASI_SDK_URL "${WASI_SDK_BASE_URL}/wasi-sdk-${WASI_SDK_VERSION}/${WASI_SDK_FILENAME}.tar.gz")
set(FETCHCONTENT_QUIET FALSE)
include(FetchContent)
FetchContent_Declare(
WasiSDK
URL ${WASI_SDK_URL}
DOWNLOAD_EXTRACT_TIMESTAMP true
FIND_PACKAGE_ARGS
)
FetchContent_MakeAvailable(WasiSDK)
set(WASI_SDK_PATH ${wasisdk_SOURCE_DIR})
+12
View File
@@ -0,0 +1,12 @@
# Fetch yaml-cpp
include(FetchContent)
FetchContent_Declare(
yaml-cpp
GIT_REPOSITORY https://github.com/jbeder/yaml-cpp
GIT_TAG 28f93bdec6387d42332220afa9558060c8016795
)
FetchContent_MakeAvailable(yaml-cpp)
message("Fetched yaml-cpp source code from Github: ${yaml-cpp_SOURCE_DIR}")
+27
View File
@@ -0,0 +1,27 @@
if(WIN32)
include(InstallRequiredSystemLibraries)
# Determine architecture
# if(CMAKE_CL_64)
set(MSVC_ARCH x64)
# else()
# set(MSVC_ARCH x86)
# endif()
# Attempt to find the vcredist executable
find_program(MSVC_REDIST_EXE
NAMES vc_redist.${MSVC_ARCH}.exe
PATHS
"${MSVC_REDIST_DIR}" # If set by InstallRequiredSystemLibraries
"$ENV{VCINSTALLDIR}Redist/MSVC/v143/" # For VS 2022
"C:/Program Files (x86)/Microsoft Visual Studio/*/VC/Redist/MSVC/*/"
"C:/Program Files/Microsoft Visual Studio/*/*/VC/Redist/MSVC/*/"
NO_DEFAULT_PATH
)
if(MSVC_REDIST_EXE)
message(STATUS "Found VC Redistributable: ${MSVC_REDIST_EXE}")
else()
message(WARNING "VC Redistributable not found.")
endif()
endif()
+256
View File
@@ -0,0 +1,256 @@
#.rst:
# FindSDL
# -------
#
# Locate SDL library
#
# This module defines
#
# ::
#
# SDL_FOUND, if false, do not try to link to SDL
# SDL_LIBRARY, the name(s) of the library(ies) to link against
# SDL_LIBSDL, the full path and name of libSDL (or SDL.[lib|dll].
# SDL_LIBSDLMAIN, full path and name of libSDLmain (or SDLmain.lib).
# SDL_LIBRARY_PATH, the full path to the found libSDL
# SDL_INCLUDE_DIR, where to find SDL.h
# SDL_VERSION_STRING, human-readable string containing the version of SDL
#
#
#
# This module responds to the flag:
#
# ::
#
# SDL_BUILDING_LIBRARY
# If this is defined, then no SDL_main will be linked in because
# only applications need main().
# Otherwise, it is assumed you are building an application and this
# module will attempt to locate and set the proper link flags
# as part of the returned SDL_LIBRARY variable.
#
#
#
# Don't forget to include SDLmain.h and SDLmain.m your project for the
# OS X framework based version. (Other versions link to -lSDLmain which
# this module will try to find on your behalf.) Also for OS X, this
# module will automatically add the -framework Cocoa on your behalf.
#
#
#
# Additional Note: If you see an empty SDL_LIBSDL in your
# configuration and no SDL_LIBRARY, it means CMake did not find your SDL
# library (SDL.dll, libsdl.so, SDL.framework, etc). Set
# SDL_LIBSDL to point to your SDL library, and configure again.
# Similarly, if you see an empty SDL_LIBSDLMAIN, you should set this
# value as appropriate. These values are used to generate the final
# SDL_LIBRARY variable, but when these values are unset, SDL_LIBRARY
# does not get created.
#
#
#
# $SDLDIR is an environment variable that would correspond to the
# ./configure --prefix=$SDLDIR used in building SDL. l.e.galup 9-20-02
#
# Modified by Eric Wing. Added code to assist with automated building
# by using environmental variables and providing a more
# controlled/consistent search behavior. Added new modifications to
# recognize OS X frameworks and additional Unix paths (FreeBSD, etc).
# Also corrected the header search path to follow "proper" SDL
# guidelines. Added a search for SDLmain which is needed by some
# platforms. Added a search for threads which is needed by some
# platforms. Added needed compile switches for MinGW.
#
# On OSX, this will prefer the Framework version (if found) over others.
# People will have to manually change the cache values of SDL_LIBRARY to
# override this selection or set the CMake environment
# CMAKE_INCLUDE_PATH to modify the search paths.
#
# Note that the header path has changed from SDL/SDL.h to just SDL.h
# This needed to change because "proper" SDL convention is #include
# "SDL.h", not <SDL/SDL.h>. This is done for portability reasons
# because not all systems place things in SDL/ (see FreeBSD).
#
# Modified for OpenTx by Maxim Paperno 19-Dec-2016
# Added SDL_LIBSDL, SDL_LIBSDLMAIN & SDL_LIBRARY_PATH, removed SDLMAIN_LIBRARY,
# SDL_LIBRARY_TEMP, and fixed bug with recursive appending of supporting
# libraries to cached SDL_LIBRARY_TEMP variable.
#
#=============================================================================
#CMake - Cross Platform Makefile Generator
#Copyright 2000-2015 Kitware, Inc.
#Copyright 2000-2011 Insight Software Consortium
#All rights reserved.
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions
#are met:
#
#* Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
#* 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.
#
#* Neither the names of Kitware, Inc., the Insight Software Consortium,
# nor the names of their 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.
#
#------------------------------------------------------------------------------
#
#The above copyright and license notice applies to distributions of
#CMake in source and binary form. Some source files contain additional
#notices of original copyright by their contributors; see each source
#for details. Third-party software packages supplied with CMake under
#compatible licenses provide their own copyright notices documented in
#corresponding subdirectories.
#
#------------------------------------------------------------------------------
#
#CMake was initially developed by Kitware with the following sponsorship:
#
# * National Library of Medicine at the National Institutes of Health
# as part of the Insight Segmentation and Registration Toolkit (ITK).
#
# * US National Labs (Los Alamos, Livermore, Sandia) ASC Parallel
# Visualization Initiative.
#
# * National Alliance for Medical Image Computing (NAMIC) is funded by the
# National Institutes of Health through the NIH Roadmap for Medical Research,
# Grant U54 EB005149.
#
# * Kitware, Inc.
#=============================================================================
find_path(SDL_INCLUDE_DIR SDL.h
HINTS
ENV SDLDIR
PATH_SUFFIXES SDL SDL12 SDL11
# path suffixes to search inside ENV{SDLDIR}
include/SDL include/SDL12 include/SDL11 include
)
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(VC_LIB_PATH_SUFFIX lib/x64)
else()
set(VC_LIB_PATH_SUFFIX lib/x86)
endif()
# SDL-1.1 is the name used by FreeBSD ports...
# don't confuse it for the version number.
find_library(SDL_LIBSDL
NAMES SDL SDL-1.1
HINTS
ENV SDLDIR
PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX}
)
# Non-OS X framework versions expect you to also dynamically link to
# SDLmain. This is mainly for Windows and OS X. Other (Unix) platforms
# seem to provide SDLmain for compatibility even though they don't
# necessarily need it.
find_library(SDL_LIBSDLMAIN
NAMES SDLmain SDLmain-1.1
HINTS
ENV SDLDIR
PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX}
PATHS
/sw
/opt/local
/opt/csw
/opt
)
# SDL may require threads on your system.
# The Apple build may not need an explicit flag because one of the
# frameworks may already provide it.
# But for non-OSX systems, I will use the CMake Threads package.
if(NOT APPLE)
find_package(Threads)
endif()
# MinGW needs an additional library, mwindows
# It's total link flags should look like -lmingw32 -lSDLmain -lSDL
if(MINGW)
set(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW")
endif()
if(SDL_LIBSDL)
# For SDLmain
if(SDL_LIBSDLMAIN AND NOT SDL_BUILDING_LIBRARY AND NOT SDL_INCLUDE_DIR MATCHES ".framework")
list(FIND SDL_LIBSDL "${SDL_LIBSDLMAIN}" _SDL_MAIN_INDEX)
if(_SDL_MAIN_INDEX EQUAL -1)
list(APPEND _SDL_LIBRARY_TEMP "${SDL_LIBSDLMAIN}")
endif()
unset(_SDL_MAIN_INDEX)
endif()
list(APPEND _SDL_LIBRARY_TEMP "${SDL_LIBSDL}")
# For OS X, SDL uses Cocoa as a backend so it must link to Cocoa.
# CMake doesn't display the -framework Cocoa string in the UI even
# though it actually is there if I modify a pre-used variable.
# I think it has something to do with the CACHE STRING.
# So I use a temporary variable until the end so I can set the
# "real" variable in one-shot.
if(APPLE)
list(APPEND _SDL_LIBRARY_TEMP "-framework Cocoa")
endif()
if(Threads_FOUND)
list(APPEND _SDL_LIBRARY_TEMP ${CMAKE_THREAD_LIBS_INIT})
endif()
# MinGW needs an additional library, mwindows
# It's total link flags should look like -lmingw32 -lSDLmain -lSDL
if(MINGW)
list(INSERT 0 _SDL_LIBRARY_TEMP mingw32)
endif()
# Set the final string here so the GUI reflects the final state.
set(SDL_LIBRARY ${_SDL_LIBRARY_TEMP} CACHE STRING "Full list of required SDL & supporting libraries.")
unset(_SDL_LIBRARY_TEMP)
get_filename_component(_SDL_LIBRARY_TEMP ${SDL_LIBSDL} PATH)
set(SDL_LIBRARY_PATH ${_SDL_LIBRARY_TEMP} CACHE PATH "Path to SDL library.")
unset(_SDL_LIBRARY_TEMP)
endif()
if(SDL_INCLUDE_DIR AND EXISTS "${SDL_INCLUDE_DIR}/SDL_version.h")
file(STRINGS "${SDL_INCLUDE_DIR}/SDL_version.h" SDL_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_MAJOR_VERSION[ \t]+[0-9]+$")
file(STRINGS "${SDL_INCLUDE_DIR}/SDL_version.h" SDL_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_MINOR_VERSION[ \t]+[0-9]+$")
file(STRINGS "${SDL_INCLUDE_DIR}/SDL_version.h" SDL_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_PATCHLEVEL[ \t]+[0-9]+$")
string(REGEX REPLACE "^#define[ \t]+SDL_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL_VERSION_MAJOR "${SDL_VERSION_MAJOR_LINE}")
string(REGEX REPLACE "^#define[ \t]+SDL_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL_VERSION_MINOR "${SDL_VERSION_MINOR_LINE}")
string(REGEX REPLACE "^#define[ \t]+SDL_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL_VERSION_PATCH "${SDL_VERSION_PATCH_LINE}")
set(SDL_VERSION_STRING ${SDL_VERSION_MAJOR}.${SDL_VERSION_MINOR}.${SDL_VERSION_PATCH})
unset(SDL_VERSION_MAJOR_LINE)
unset(SDL_VERSION_MINOR_LINE)
unset(SDL_VERSION_PATCH_LINE)
unset(SDL_VERSION_MAJOR)
unset(SDL_VERSION_MINOR)
unset(SDL_VERSION_PATCH)
endif()
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL
REQUIRED_VARS SDL_LIBRARY SDL_LIBRARY_PATH SDL_INCLUDE_DIR
VERSION_VAR SDL_VERSION_STRING)
+47
View File
@@ -0,0 +1,47 @@
# FindWasiSDK.cmake
# Module to find WASI SDK installation
#
# This module defines the following variables:
# WasiSDK_FOUND - True if WASI SDK is found
# WasiSDK_ROOT_DIR - Root directory of WASI SDK installation
#
# Usage:
# set(WASI_SDK_PATH "/path/to/wasi-sdk")
# find_package(WasiSDK REQUIRED)
cmake_minimum_required(VERSION 3.10)
# Use WASI_SDK_PATH if provided, otherwise try to find it
if(WASI_SDK_PATH)
set(_wasi_sdk_search_paths "${WASI_SDK_PATH}")
else()
# Default search paths for common WASI SDK installations
set(_wasi_sdk_search_paths
"$ENV{WASI_SDK_PATH}"
"/opt/wasi-sdk"
)
endif()
# Find the WASI SDK root directory by looking for the toolchain file
find_path(WasiSDK_ROOT_DIR
NAMES share/cmake/wasi-sdk.cmake
PATHS ${_wasi_sdk_search_paths}
DOC "WASI SDK root directory"
NO_DEFAULT_PATH
)
# Handle find_package arguments
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(WasiSDK
DEFAULT_MSG
WasiSDK_ROOT_DIR
)
# Mark variables as advanced
mark_as_advanced(
WasiSDK_ROOT_DIR
)
if(WasiSDK_FOUND)
set(WASI_SDK_PATH ${WasiSDK_ROOT_DIR})
endif()
+90
View File
@@ -0,0 +1,90 @@
# Automatically link Qt executables to qtmain target on Windows
# https://cmake.org/cmake/help/latest/policy/CMP0020.html
cmake_policy(SET CMP0020 NEW)
# Use @rpath in a target's install name on MacOS X for locating shared libraries
# https://cmake.org/cmake/help/latest/policy/CMP0042.html
if(POLICY CMP0042)
cmake_policy(SET CMP0042 NEW)
endif()
# Ignore COMPILE_DEFINITIONS_<Config> properties (prefer generator expressions)
# https://cmake.org/cmake/help/latest/policy/CMP0043.html
if(POLICY CMP0043)
cmake_policy(SET CMP0043 NEW)
endif()
# Only interpret if() arguments as variables or keywords when unquoted
# https://cmake.org/cmake/help/latest/policy/CMP0054.html
if(POLICY CMP0054)
cmake_policy(SET CMP0054 NEW)
endif()
# Custom command byproducts must be explicit - for ninja
# https://cmake.org/cmake/help/latest/policy/CMP0058.html
if(POLICY CMP0058)
cmake_policy(SET CMP0058 NEW)
endif()
set(CMAKE_COLOR_MAKEFILE ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 17)
add_definitions(-D_GLIBCXX_USE_C99=1) # proper to_string definition
set(RADIO_DIRECTORY ${PROJECT_SOURCE_DIR}/radio)
set(RADIO_SRC_DIR ${RADIO_DIRECTORY}/src)
set(COMPANION_SRC_DIRECTORY ${PROJECT_SOURCE_DIR}/companion/src)
set(SIMU_SRC_DIRECTORY ${COMPANION_SRC_DIRECTORY}/simulation)
set(TOOLS_DIR ${PROJECT_SOURCE_DIR}/tools)
set(UTILS_DIR ${PROJECT_SOURCE_DIR}/radio/util)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake)
string(TIMESTAMP DATE "%Y-%m-%d")
string(TIMESTAMP TIME "%H:%M:%S")
include(Macros)
git_id(GIT_STR)
# Python check
set(Python3_FIND_VIRTUALENV FIRST)
set(Python3_FIND_STRATEGY LOCATION)
find_package(Python3 REQUIRED COMPONENTS Interpreter)
if(Python3_Interpreter_FOUND)
message(STATUS "Python found, version: ${Python3_VERSION}")
cmake_path(NATIVE_PATH Python3_EXECUTABLE PYTHON_EXECUTABLE)
else()
message(WARNING "Python not found! Most firmware and simu flavors not buildable.")
set(LUA NO)
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") # regular Clang or AppleClang
set(CLANG TRUE)
endif()
if(CMAKE_HOST_APPLE)
execute_process(COMMAND xcrun --show-sdk-path
OUTPUT_VARIABLE SDKROOT
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE)
message("-- SDKROOT: ${SDKROOT}")
#set(CMAKE_OSX_SYSROOT ${SDKROOT})
set(SYSROOT_ARG -isysroot ${SDKROOT})
endif()
# options shared by all targets
option(APP_CUSTOM_DBG_HANDLER "Use custom message handler for debug output in all Qt apps." ON)
set(APP_CUSTOM_DBG_LEVEL "0" CACHE STRING
"Debug output level: 0=debug+; 1=info+; 2=warn+; 3=critical+; 4=fatal only.")
set(APP_CUSTOM_DBG_SHOW_FILE "DEFAULT" CACHE STRING
"Show source file names in debug output. 1/0/DEFAULT (default is ON with DEBUG builds).")
option(VERBOSE_CMAKELISTS "Show extra information while processing CMakeLists.txt files." OFF)
option(WARNINGS_AS_ERRORS "Treat any compiler warning as an error (adds -Werror flag)." OFF)
+174
View File
@@ -0,0 +1,174 @@
find_package(Git)
macro(git_id RESULT)
set(${RESULT} 0)
if (GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
execute_process(COMMAND "${GIT_EXECUTABLE}" "--git-dir=${PROJECT_SOURCE_DIR}/.git" "rev-parse" "--short=8" "HEAD" OUTPUT_VARIABLE proc_out ERROR_VARIABLE proc_err)
if(proc_err)
message(WARNING "Git failed with error: ${proc_err}")
else()
string(REGEX REPLACE "(.*)\n" "\\1" ${RESULT} ${proc_out})
endif()
elseif(NOT GIT_FOUND)
message(WARNING "Git executable not found, revision number not available.")
else()
message(STATUS "Git repository not found, revision number not available.")
endif()
endmacro(git_id)
macro(PrintTargetReport targetName)
if(CMAKE_CXX_COMPILER MATCHES "/(clang-)?cl\\.exe$")
set(cpp_version ${CMAKE_CXX_COMPILER_VERSION})
else()
execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE cpp_version)
endif()
if(cpp_version)
string(STRIP "v${cpp_version}" cpp_version)
else()
set(cpp_version "WARNING: COMPILER NOT FOUND!")
endif()
message("TARGET ${targetName}: cpp compiler ${CMAKE_CXX_COMPILER} ${cpp_version}")
if(VERBOSE_CMAKELISTS)
get_directory_property(DirOpts COMPILE_OPTIONS)
get_directory_property(DirDefs COMPILE_DEFINITIONS)
string(REPLACE ";" " " DirOpts "${DirOpts}")
string(REPLACE ";" "; " DirDefs "${DirDefs}")
string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type)
message("\twith cpp flags: ${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${build_type}} ${DirOpts}")
message("\twith link flags: ${CMAKE_EXE_LINKER_FLAGS}")
message("\twith defs: ${DirDefs}")
message("--------------")
endif()
endmacro(PrintTargetReport)
function(AddCompilerFlags output)
get_property(flags DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY COMPILE_DEFINITIONS)
foreach(flag ${flags})
set(ARGS ${ARGS} -D${flag})
endforeach()
get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
foreach(dir ${dirs})
set(ARGS ${ARGS} -I${dir})
endforeach()
# Add hotfix for arm64
set(${output} ${${output}} ${ARGS} PARENT_SCOPE)
endfunction()
function(GenerateDatacopy source output)
set(GEN_DATACOPY ${RADIO_DIRECTORY}/util/generate_datacopy.py)
set(GEN_DATACOPY_DEPEND ${CMAKE_CURRENT_SOURCE_DIR}/${source} ${GEN_DATACOPY})
# Fetch defines / include directories in use
AddCompilerFlags(GEN_DATACOPY_ARGS)
# Hack to get rid of warnings in StdPeriph lib
set(GEN_DATACOPY_ARGS
# source file MUST be the first argument
${CMAKE_CURRENT_SOURCE_DIR}/${source}
-DBACKUP ${GEN_DATACOPY_ARGS} ${SYSROOT_ARG})
set(GEN_DATACOPY_CMD
${PYTHON_EXECUTABLE} ${GEN_DATACOPY} ${GEN_DATACOPY_ARGS})
add_custom_command(
OUTPUT ${output}
COMMAND ${GEN_DATACOPY_CMD} > ${output}
DEPENDS ${GEN_DATACOPY_DEPEND} ${CMAKE_BINARY_DIR}/CMakeCache.txt
)
endfunction()
function(AddHardwareDefTarget output)
AddCompilerFlags(HW_DEF_ARGS)
set(HW_DEF_SRC ${RADIO_DIRECTORY}/src/targets/${TARGET_DIR}/hal.h)
separate_arguments(flags UNIX_COMMAND ${CMAKE_CXX_FLAGS})
foreach(flag ${flags})
set(HW_DEF_ARGS ${HW_DEF_ARGS} ${flag})
endforeach()
set(GEN_HW_DEFS ${CMAKE_CXX_COMPILER} ${HW_DEF_ARGS} -x c++-header -E -dM ${HW_DEF_SRC})
# set(GEN_JSON ${PYTHON_EXECUTABLE} ${RADIO_DIRECTORY}/util/hw_defs/generate_hw_def.py)
# set(GEN_JSON ${GEN_JSON} -i defines -T ${FLAVOUR} -)
# add_custom_command(OUTPUT ${output}
# COMMAND ${GEN_HW_DEFS} | ${GEN_JSON} > ${output}
# DEPENDS ${HW_DEF_SRC} ${RADIO_DIRECTORY}/util/hw_defs/generate_hw_def.py
# )
add_custom_command(OUTPUT ${output}.h
COMMAND ${GEN_HW_DEFS} > ${output}.h
DEPENDS ${HW_DEF_SRC} ${RADIO_DIRECTORY}/util/hw_defs/generate_hw_def.py
)
endfunction()
function(AddHWGenTarget input template output)
# Script
set(GEN_JSON ${PYTHON_EXECUTABLE} ${RADIO_DIRECTORY}/util/hw_defs/generate_hw_def.py)
# Inputs
set(INPUT_JSON ${RADIO_SRC_DIR}/boards/hw_defs/${input})
set(TEMPLATE ${RADIO_DIRECTORY}/util/hw_defs/${template}.jinja)
set(GEN_PY_DEPS
${RADIO_DIRECTORY}/util/hw_defs/generator.py
${RADIO_DIRECTORY}/util/hw_defs/legacy_names.py
)
# Command
set(GEN_JSON ${GEN_JSON} -t ${TEMPLATE} -T ${FLAVOUR} ${INPUT_JSON})
add_custom_command(OUTPUT ${output}
COMMAND ${GEN_JSON} > ${output}
DEPENDS ${INPUT_JSON} ${TEMPLATE} ${GEN_PY_DEPS} ${CMAKE_BINARY_DIR}/CMakeCache.txt
)
endfunction()
macro(AddHeadersSources)
foreach(name
${${PROJECT_NAME}_NAMES})
set(${PROJECT_NAME}_SRCS
${${PROJECT_NAME}_SRCS}
${name}.cpp
)
set(${PROJECT_NAME}_HDRS
${${PROJECT_NAME}_HDRS}
${name}.h
)
endforeach()
endmacro()
# Collect all user-specified command-line cache variables into a list
# suitable for forwarding to ExternalProject or sub-project cmake invocations.
# Optional EXCLUDE parameter: regex to skip matching variable names.
function(CollectCommandLineArgs out_var)
cmake_parse_arguments(_cla "" "EXCLUDE" "" ${ARGN})
set(_args "")
get_cmake_property(_cache_vars CACHE_VARIABLES)
foreach(_var ${_cache_vars})
if(_cla_EXCLUDE AND _var MATCHES "${_cla_EXCLUDE}")
continue()
endif()
get_property(_help CACHE ${_var} PROPERTY HELPSTRING)
if(_help STREQUAL "No help, variable specified on the command line.")
get_property(_type CACHE ${_var} PROPERTY TYPE)
if(_type STREQUAL "UNINITIALIZED")
set(_type)
else()
set(_type :${_type})
endif()
list(APPEND _args "-D${_var}${_type}=${${_var}}")
endif()
endforeach()
set(${out_var} ${_args} PARENT_SCOPE)
endfunction()
+65
View File
@@ -0,0 +1,65 @@
option(DISABLE_COMPANION "Disable building companion and simulators" OFF)
if(NOT DISABLE_COMPANION)
include(QtDefs)
endif(NOT DISABLE_COMPANION)
find_package(SDL2 QUIET COMPONENTS SDL2 CONFIG)
if(TARGET SDL2::SDL2)
message(STATUS "SDL2 found")
else()
message(STATUS "SDL not found! Simulator audio, and joystick inputs, will not work.")
endif()
if(Qt6Core_FOUND AND NOT DISABLE_COMPANION)
# environment variable set in github workflows and build-edgetx Dockerfile
if(DEFINED ENV{OPENSSL_ROOT_DIR})
set(OPENSSL_ROOT_DIR "$ENV{OPENSSL_ROOT_DIR}")
endif()
find_package(OpenSSL)
include(FetchRsDfu)
find_package(rs_dfu REQUIRED)
endif()
# Windows-specific includes and libs shared by sub-projects
if(WIN32)
# TODO: is that still necessary?
set(CMAKE_C_USE_RESPONSE_FILE_FOR_INCLUDES OFF)
set(CMAKE_C_USE_RESPONSE_FILE_FOR_LIBRARIES OFF)
set(CMAKE_CXX_USE_RESPONSE_FILE_FOR_INCLUDES OFF)
set(CMAKE_CXX_USE_RESPONSE_FILE_FOR_LIBRARIES OFF)
endif()
# google tests
include(FetchGtest)
add_custom_target(tests-radio
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/gtests-radio
DEPENDS gtests-radio
)
if(Qt6Core_FOUND AND NOT DISABLE_COMPANION)
add_subdirectory(${COMPANION_SRC_DIRECTORY})
add_custom_target(tests-companion
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/gtests-companion
DEPENDS gtests-companion
)
add_custom_target(gtests
DEPENDS gtests-radio gtests-companion
)
add_custom_target(tests
DEPENDS tests-radio tests-companion
)
else()
add_custom_target(gtests
DEPENDS gtests-radio
)
add_custom_target(tests
DEPENDS tests-radio
)
endif()
set(IGNORE "${ARM_TOOLCHAIN_DIR}")
+65
View File
@@ -0,0 +1,65 @@
if(APPLE AND DEFINED ENV{HOMEBREW_PREFIX})
# If Homebrew is used, HOMEBREW_PREFIX should be defined
if(EXISTS $ENV{HOMEBREW_PREFIX}/opt/qt@6)
list(APPEND CMAKE_PREFIX_PATH "$ENV{HOMEBREW_PREFIX}/opt/qt@6")
endif()
endif()
find_package(Qt6 REQUIRED COMPONENTS Core)
if(Qt6_FOUND)
message(STATUS "Qt Version: ${Qt6_VERSION}")
# call after Qt6Core package is found
qt_standard_project_setup()
find_package(Qt6 REQUIRED COMPONENTS Widgets LinguistTools Multimedia PrintSupport SerialPort Svg Xml)
### Get locations of Qt binary executables & libs (libs are for distros, not for linking)
# first set up some hints
get_target_property(QtCore_LOCATION Qt::Core LOCATION)
cmake_path(GET QtCore_LOCATION ROOT_PATH qt_core_path)
if(APPLE)
set(qt_core_path "${qt_core_path}/..")
cmake_path(ABSOLUTE_PATH qt_core_path OUTPUT_VARIABLE qt_core_path)
endif()
set(QT_LIB_DIR ${qt_core_path} CACHE PATH "Path to Qt libraries (.dll|.framework|.so).")
find_path(QT_BIN_DIR NAMES "qmake" "qmake.exe" HINTS "${CMAKE_PREFIX_PATH}/bin" "${qt_core_path}/../bin" "${qt_core_path}" DOC "Path to Qt binaries (qmake, lupdate, etc.).")
find_program(QT_QMAKE_EXECUTABLE qmake HINTS "${QT_BIN_DIR}" DOC "Location of qmake program.")
find_program(QT_LUPDATE_EXECUTABLE lupdate HINTS "${QT_BIN_DIR}" DOC "Location of Qt's 'lupdate' program for updating translation files.")
# Try to find Qt translations
if(QT_QMAKE_EXECUTABLE)
execute_process(
COMMAND ${QT_QMAKE_EXECUTABLE} -query QT_INSTALL_TRANSLATIONS
OUTPUT_VARIABLE qt_translations_hint
OUTPUT_STRIP_TRAILING_WHITESPACE
)
endif()
find_path(QT_TRANSLATIONS_DIR NAMES "qt_en.qm" HINTS "${qt_translations_hint}" DOC "Path to prebuilt Qt translations (qt_*.qm).")
### Common definitions for the Qt-based apps
list(APPEND APP_COMMON_DEFINES -DSIMU)
list(APPEND APP_COMMON_DEFINES -DQXT_STATIC)
list(APPEND APP_COMMON_DEFINES -DQT_USE_QSTRINGBUILDER) # more efficient QString construction using % operator
if(MSVC)
list(APPEND APP_COMMON_DEFINES -D__STDC_LIMIT_MACROS)
endif()
if(APP_CUSTOM_DBG_HANDLER)
# provide full qDebug log context to our custom handler.
list(APPEND APP_COMMON_DEFINES -DQT_MESSAGELOGCONTEXT)
list(APPEND APP_COMMON_DEFINES -DAPP_DBG_HANDLER_ENABLE=1)
if(APP_CUSTOM_DBG_LEVEL GREATER -1 AND APP_CUSTOM_DBG_LEVEL LESS 5)
list(APPEND APP_COMMON_DEFINES -DAPP_DBG_HANDLER_DEFAULT_LEVEL=${APP_CUSTOM_DBG_LEVEL})
endif()
if(NOT APP_CUSTOM_DBG_SHOW_FILE STREQUAL "DEFAULT" AND (APP_CUSTOM_DBG_SHOW_FILE STREQUAL "1" OR APP_CUSTOM_DBG_SHOW_FILE STREQUAL "0"))
list(APPEND APP_COMMON_DEFINES -DAPP_DBG_HANDLER_SHOW_SRC_PATH=${APP_CUSTOM_DBG_SHOW_FILE})
endif()
else()
list(APPEND APP_COMMON_DEFINES -DAPP_DBG_HANDLER_ENABLE=0)
endif()
else()
message(WARNING "Required Qt version not found! Companion, Simulator and libsim builds disabled.")
endif()
+53
View File
@@ -0,0 +1,53 @@
# arm-none-eabi toolchain
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
if(MINGW OR WIN32)
set(EXE_SUFFIX ".exe")
set(CMAKE_OBJECT_PATH_MAX 200)
endif()
if(ARM_TOOLCHAIN_DIR)
cmake_path(SET ARM_TOOLCHAIN_DIR NORMALIZE ${ARM_TOOLCHAIN_DIR})
set(ARM_TOOLCHAIN_DIR "${ARM_TOOLCHAIN_DIR}/")
endif()
set(CMAKE_AR ${ARM_TOOLCHAIN_DIR}arm-none-eabi-ar${EXE_SUFFIX})
set(CMAKE_ASM_COMPILER ${ARM_TOOLCHAIN_DIR}arm-none-eabi-gcc${EXE_SUFFIX})
set(CMAKE_C_COMPILER ${ARM_TOOLCHAIN_DIR}arm-none-eabi-gcc${EXE_SUFFIX})
set(CMAKE_CXX_COMPILER ${ARM_TOOLCHAIN_DIR}arm-none-eabi-g++${EXE_SUFFIX})
set(CMAKE_LINKER ${ARM_TOOLCHAIN_DIR}arm-none-eabi-ld${EXE_SUFFIX})
set(CMAKE_OBJCOPY ${ARM_TOOLCHAIN_DIR}arm-none-eabi-objcopy${EXE_SUFFIX} CACHE INTERNAL "")
set(CMAKE_RANLIB ${ARM_TOOLCHAIN_DIR}arm-none-eabi-ranlib${EXE_SUFFIX} CACHE INTERNAL "")
set(CMAKE_SIZE_UTIL ${ARM_TOOLCHAIN_DIR}arm-none-eabi-size${EXE_SUFFIX} CACHE INTERNAL "")
set(CMAKE_STRIP ${ARM_TOOLCHAIN_DIR}arm-none-eabi-strip${EXE_SUFFIX} CACHE INTERNAL "")
set(CMAKE_GCOV ${ARM_TOOLCHAIN_DIR}arm-none-eabi-gcov${EXE_SUFFIX} CACHE INTERNAL "")
# Generate .elf files
set(CMAKE_EXECUTABLE_SUFFIX ".elf")
set(CMAKE_EXECUTABLE_SUFFIX_C ".elf")
set(CMAKE_EXECUTABLE_SUFFIX_CXX ".elf")
# Default C compiler flags
set(CMAKE_C_FLAGS_DEBUG_INIT "-g3 -Og -Wall -pedantic -DDEBUG")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG_INIT}" CACHE STRING "" FORCE)
set(CMAKE_C_FLAGS_RELEASE_INIT "-O3 -Wall")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE_INIT}" CACHE STRING "" FORCE)
# Default C++ compiler flags
set(CMAKE_CXX_FLAGS_DEBUG_INIT "-g3 -Og -Wall -pedantic -DDEBUG")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG_INIT}" CACHE STRING "" FORCE)
set(CMAKE_CXX_FLAGS_RELEASE_INIT "-O3 -Wall")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE_INIT}" CACHE STRING "" FORCE)
# customize linker command
set(CMAKE_EXE_LINKER_FLAGS "")
set(CMAKE_C_LINK_EXECUTABLE "<CMAKE_C_COMPILER> <FLAGS> <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>")
set(CMAKE_CXX_LINK_EXECUTABLE "<CMAKE_CXX_COMPILER> <FLAGS> <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+15
View File
@@ -0,0 +1,15 @@
# Native toolchain
set(CMAKE_CXX_STANDARD 17)
if(APPLE)
set(CMAKE_C_FLAGS "-Wno-deprecated-declarations")
set(CMAKE_CXX_FLAGS "-Wno-deprecated-declarations")
set(CMAKE_C_FLAGS_DEBUG "-Wno-deprecated-declarations")
set(CMAKE_CXX_FLAGS_DEBUG "-Wno-deprecated-declarations")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-address-of-packed-member -Wno-deprecated-declarations")
if(MINGW OR WIN32)
set(CMAKE_OBJECT_PATH_MAX 200)
endif()
+20
View File
@@ -0,0 +1,20 @@
# Wrapper around wasi-sdk's pthread toolchain that uses the non-deprecated
# wasm32-wasip1-threads triple (compatible with SDK 25+).
# Propagate WASI_SDK_PREFIX into try_compile sub-projects (used during ABI detection)
list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES WASI_SDK_PREFIX)
if(NOT WASI_SDK_PREFIX)
if(DEFINED ENV{WASI_SDK_PATH})
set(WASI_SDK_PREFIX $ENV{WASI_SDK_PATH})
elseif(EXISTS "/opt/wasi-sdk")
set(WASI_SDK_PREFIX "/opt/wasi-sdk")
endif()
endif()
include(${WASI_SDK_PREFIX}/share/cmake/wasi-sdk-pthread.cmake)
set(triple wasm32-wasip1-threads)
set(CMAKE_C_COMPILER_TARGET ${triple})
set(CMAKE_CXX_COMPILER_TARGET ${triple})
set(CMAKE_ASM_COMPILER_TARGET ${triple})
@@ -0,0 +1,29 @@
diff --git a/core/shared/platform/windows/win_file.c b/core/shared/platform/windows/win_file.c
index 55ea77a..df67705 100644
--- a/core/shared/platform/windows/win_file.c
+++ b/core/shared/platform/windows/win_file.c
@@ -1295,13 +1295,13 @@ os_readlinkat(os_file_handle handle, const char *path, char *buf,
if (wbufsize >= 4 && wbuf[0] == L'\\' && wbuf[1] == L'?'
&& wbuf[2] == L'?' && wbuf[3] == L'\\') {
- // Starts with \??\
+ // Has \??\ prefix
if (wbufsize >= 6
&& ((wbuf[4] >= L'A' && wbuf[4] <= L'Z')
|| (wbuf[4] >= L'a' && wbuf[4] <= L'z'))
&& wbuf[5] == L':' && (wbufsize == 6 || wbuf[6] == L'\\'))
{
- // \??\<drive>:\
+ // \??\<drive>:\ path
wbuf += 4;
wbufsize -= 4;
}
@@ -1310,7 +1310,7 @@ os_readlinkat(os_file_handle handle, const char *path, char *buf,
&& (wbuf[6] == L'C' || wbuf[6] == L'c')
&& wbuf[7] == L'\\')
{
- // \??\UNC\<server>\<share>\ - make sure the final path looks like \\<server>\<share>\
+ // \??\UNC\<server>\<share>\ - make sure the final path looks like \\<server>\<share>\ path
wbuf += 6;
wbuf[0] = L'\\';
wbufsize -= 6;
+49
View File
@@ -0,0 +1,49 @@
# Standalone CMake project for building WASM simulator modules.
# Invoked on-demand by the superbuild's wasi-module target so that
# the WASI SDK is only fetched when WASM targets are actually built.
#
# Usage from superbuild:
# cmake -S . -B build -DPCB=X10 -DPCBREV=TX16S
# cmake --build build --target wasi-module
cmake_minimum_required(VERSION 3.21)
project(EdgeTX-WASM NONE) # NONE = no compiler check (we set a toolchain below)
# Inherit the module path from the main project
list(APPEND CMAKE_MODULE_PATH "${EDGETX_SOURCE_DIR}/cmake")
# Collect command-line args to forward, excluding EDGETX_* (consumed here)
include(Macros)
CollectCommandLineArgs(_forwarded_args EXCLUDE "^EDGETX_")
# Fetch / find WASI SDK
include(FetchWasiSDK)
find_package(WasiSDK REQUIRED)
include(ExternalProject)
ExternalProject_Add(wasm
SOURCE_DIR ${EDGETX_SOURCE_DIR}
BINARY_DIR ${CMAKE_BINARY_DIR}/wasm-build
CMAKE_ARGS
${_forwarded_args}
-Wno-dev
-DCMAKE_BUILD_TYPE=${EDGETX_BUILD_TYPE}
-DWASI_SDK_PREFIX=${WASI_SDK_PATH}
-DCMAKE_TOOLCHAIN_FILE=${EDGETX_SOURCE_DIR}/cmake/toolchain/wasi-threads.cmake
CMAKE_CACHE_ARGS
-DEdgeTX_SUPERBUILD:BOOL=0
-DNATIVE_BUILD:BOOL=1
-DDISABLE_COMPANION:BOOL=1
BUILD_ALWAYS TRUE
BUILD_COMMAND ${CMAKE_COMMAND} --build <BINARY_DIR> --target wasi-module
INSTALL_COMMAND ""
)
# Copy .wasm files up after build
ExternalProject_Add_Step(wasm copy-wasm
DEPENDEES build
COMMAND ${CMAKE_COMMAND}
-DSRC_DIR=${CMAKE_BINARY_DIR}/wasm-build -DDST_DIR=${CMAKE_BINARY_DIR}
-P ${EDGETX_SOURCE_DIR}/cmake/CopyWasmModules.cmake
)
+1
View File
@@ -0,0 +1 @@
Mon Jan 26 18:05:25 AEST 2026
+650
View File
@@ -0,0 +1,650 @@
set(SIMULATOR_INSTALL_PREFIX "" CACHE STRING "Alternative simulator library search path")
set(SIMULATOR_WASM_DIR "" CACHE PATH "Directory containing .wasm simulator modules to bundle")
set(COMPANION_NAME "companion")
set(SIMULATOR_NAME "simulator")
set(APP_NAME_SUFFIX "")
if(${CMAKE_SYSTEM_NAME} MATCHES "(Linux|FreeBSD)")
set(APP_NAME_SUFFIX "${VERSION_MAJOR}${VERSION_MINOR}")
set(COMPANION_NAME "${COMPANION_NAME}${APP_NAME_SUFFIX}")
set(SIMULATOR_NAME "${SIMULATOR_NAME}${APP_NAME_SUFFIX}")
# by default cmake sets CMAKE_INSTALL_PREFIX to /usr/local however linuxdeploy works best with '/usr' as base directory
if (${CMAKE_INSTALL_PREFIX} MATCHES "/usr/local")
set(CMAKE_INSTALL_PREFIX "/usr")
endif()
if (NOT ${SIMULATOR_INSTALL_PREFIX} STREQUAL "")
set(SIMULATOR_LIB_PATH ${SIMULATOR_INSTALL_PREFIX}/lib/${COMPANION_NAME})
else()
set(SIMULATOR_LIB_PATH "../lib/${COMPANION_NAME}")
endif()
message(STATUS "Simulators library search path: " ${SIMULATOR_LIB_PATH})
endif()
option(DEBUG_STORAGE_IMPORT "Turn on debug output for storage import") # Disabled by default
if(DEBUG_STORAGE_IMPORT)
add_definitions(-DDEBUG_STORAGE_IMPORT)
message(STATUS "Storage import debugging enabled")
endif()
if(DEBUG)
add_definitions(-DDEBUG)
endif()
option(TABLE_LAYOUT "Use QTableWidget for grid layouts") # Disabled by default
if(TABLE_LAYOUT)
add_definitions(-DTABLE_LAYOUT)
message(STATUS "Using QTableWidget")
endif()
if(WIN32)
option(WIN_DO_FULL_INSTALL "'install' target will perform a full installation into CMAKE_INSTALL_PREFIX path, otherwise only supporting DLLs are copied to binaries folder (default)" OFF)
option(WIN_USE_CONSOLE "Open a Windows command console for Simulator debug output." OFF)
if(WIN_USE_CONSOLE)
add_definitions(-DWIN_USE_CONSOLE_STDIO)
endif()
endif()
if(TARGET SDL2::SDL2)
add_definitions(-DUSE_SDL)
set(SDL2_LIBRARIES SDL2::SDL2)
endif()
add_definitions(${APP_COMMON_DEFINES}) # set in top-level CMakeLists
set(WARNING_FLAGS "${WARNING_FLAGS} -Wall")
if(WARNINGS_AS_ERRORS)
set(WARNING_FLAGS "${WARNING_FLAGS} -Werror")
endif()
if(WIN32)
if(NOT WIN_USE_CONSOLE)
set(WIN_EXECUTABLE_TYPE WIN32) # GUI (WinMain) app
endif()
if(MINGW)
get_filename_component(MINGW_DIR ${CMAKE_CXX_COMPILER} DIRECTORY)
# struct packing breaks on MinGW w/out -mno-ms-bitfields: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52991
# & http://stackoverflow.com/questions/24015852/struct-packing-and-alignment-with-mingw
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mno-ms-bitfields")
endif()
else()
link_directories(/usr/local/lib)
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}")
include_directories(
${CMAKE_BINARY_DIR}
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_SOURCE_DIR}
${COMPANION_SRC_DIRECTORY}
${COMPANION_SRC_DIRECTORY}/shared
${SIMU_SRC_DIRECTORY}
${COMPANION_SRC_DIRECTORY}/thirdparty/qcustomplot
${COMPANION_SRC_DIRECTORY}/storage
${COMPANION_SRC_DIRECTORY}/flash
${COMPANION_SRC_DIRECTORY}/progress
${COMPANION_SRC_DIRECTORY}/print
)
############# Do macro replacements on input files #############
configure_file(${COMPANION_SRC_DIRECTORY}/version.h.in ${CMAKE_BINARY_DIR}/version.h @ONLY)
############# Translations ###############
### Generate .qm files and assemble resource (qrc) file.
# available Companion translations (src/translations/*.ts)
set(LANGUAGES cs da de en es fi fr he it ja ko nl pl pt ru sv zh_CN zh_TW)
foreach(language ${LANGUAGES})
list(APPEND companion_TS translations/companion_${language}.ts)
endforeach(language)
# .ts -> .qm
qt_add_translation(companion_QM ${companion_TS})
# add Qt translations if found
if(QT_TRANSLATIONS_DIR)
# Some languages have all translations in one qt_*.qm file and others break them up into modules, in which case we need qt_base_*.qm
file(GLOB qtbase_QM "${QT_TRANSLATIONS_DIR}/qtbase_*.qm")
file(GLOB qt_QM "${QT_TRANSLATIONS_DIR}/qt_??.qm") # don't want to match "qt_help_*.qm"
list(APPEND qt_QM "${QT_TRANSLATIONS_DIR}/qt_zh_CN.qm" "${QT_TRANSLATIONS_DIR}/qt_zh_TW.qm") # exceptions not matched by the GLOB
else()
message(STATUS "QT_TRANSLATIONS_DIR not found, unable to package pre-built Qt translations with application.")
endif()
set(TRANSLATIONS_QRC "${CMAKE_CURRENT_BINARY_DIR}/translations.qrc")
# Dynamically create translations.qrc file (XML) from all collected translation files.
foreach(qm_file ${companion_QM} ${qt_QM} ${qtbase_QM})
get_filename_component(qm_file_name ${qm_file} NAME)
# Add file with full path and file name (w/out path) as alias to be used in actual code
set(TRANSLATION_FILES_LIST "${TRANSLATION_FILES_LIST} <file alias=\"${qm_file_name}\">${qm_file}</file>\n")
endforeach()
configure_file(${COMPANION_SRC_DIRECTORY}/translations.qrc.in ${TRANSLATIONS_QRC} @ONLY)
### Add optional "companion_translations" target for updating .ts files
if(QT_LUPDATE_EXECUTABLE)
add_custom_target(companion_translations
WORKING_DIRECTORY ${COMPANION_SRC_DIRECTORY}
COMMAND ${QT_LUPDATE_EXECUTABLE} ${COMPANION_SRC_DIRECTORY} -no-obsolete -ts ${companion_TS}
)
else()
message(STATUS "Qt lupdate not found, 'companion_translations' target will not be availabe.")
endif()
############# Import radio hardware definitions ###############
set(HWDEFS_DIR "${CMAKE_SOURCE_DIR}/radio/src/boards/hw_defs")
set(HWDEFS_TMPL "${COMPANION_SRC_DIRECTORY}/hwdefs.qrc.in")
set(HWDEFS_PHDR "HWDEF_JSON_LIST")
set(HWDEFS_QRC "${CMAKE_CURRENT_BINARY_DIR}/hwdefs.qrc")
set(HWDEFS_CMD "${COMPANION_SRC_DIRECTORY}/../util/generate_hwdefs_qrc.py")
############# Common lib ###############
set(common_SRCS
customdebug.cpp
helpers.cpp
translations.cpp
modeledit/node.cpp # used in simulator
modeledit/edge.cpp # used by node
)
set(common_MOC_HDRS
helpers.h
modeledit/node.h
)
qt_wrap_cpp(common_SRCS ${common_MOC_HDRS})
# qrc used by Companion and Simulator
add_custom_command(OUTPUT ${HWDEFS_QRC}
COMMAND ${PYTHON_EXECUTABLE} ${HWDEFS_CMD}
-d ${HWDEFS_DIR}
-t ${HWDEFS_TMPL}
-p ${HWDEFS_PHDR}
-o ${HWDEFS_QRC}
DEPENDS ${HWDEFS_TMPL}
)
set(common_RESOURCES
companion.qrc
${TRANSLATIONS_QRC}
${HWDEFS_QRC}
)
qt_add_resources(common_RCC ${common_RESOURCES})
add_library(common ${common_SRCS} ${common_RCC})
target_link_libraries(common
datamodels
firmwares
progress
shared
simulation
storage
maxLibQt
Qt::Core
Qt::Xml
Qt::Widgets
Qt::SerialPort
${SDL2_LIBRARIES}
)
set(CPN_COMMON_LIB common)
############# Supporting libraries ###############
include(FetchMiniz)
include(FetchYamlCpp)
include(FetchMaxLibQt)
add_subdirectory(datamodels)
add_subdirectory(firmwares)
add_subdirectory(flash)
add_subdirectory(generaledit)
add_subdirectory(modeledit)
add_subdirectory(print)
add_subdirectory(progress)
add_subdirectory(shared)
add_subdirectory(simulation)
add_subdirectory(storage)
add_subdirectory(thirdparty/qcustomplot)
add_subdirectory(updates)
add_subdirectory(wizard)
############# Companion ###############
include_directories(${maxLibQt_SOURCE_DIR}/widgets)
set(companion_NAMES
apppreferencesdialog
helpers_html
labels
logsdialog
mainwindow
mdichild
modelslist
profilechooser
dialogs/filesyncdialog
)
foreach(name ${companion_NAMES})
set(companion_SRCS
${companion_SRCS}
${name}.cpp
)
set(companion_HDRS
${companion_HDRS}
${name}.h
)
endforeach()
set(companion_SRCS
${companion_SRCS}
companion.cpp
)
if(WIN32)
set(icon_RC images/winicons/icon.rc)
endif()
add_executable(${COMPANION_NAME} MACOSX_BUNDLE ${WIN_EXECUTABLE_TYPE}
${companion_SRCS}
${companion_HDRS}
${icon_RC}
)
target_link_libraries(${COMPANION_NAME} PRIVATE
${CPN_COMMON_LIB}
${SDL2_LIBRARIES}
generaledit
modeledit
qcustomplot
miniz
updates
flash
wizard
print
)
PrintTargetReport("${COMPANION_NAME}")
# Copy WASM simulator modules next to executables for local dev
if(SIMULATOR_WASM_DIR)
foreach(_target ${COMPANION_NAME} ${SIMULATOR_NAME})
add_custom_command(TARGET ${_target} POST_BUILD
COMMAND ${CMAKE_COMMAND}
-DSRC_DIR=${SIMULATOR_WASM_DIR}
-DDST_DIR=$<TARGET_FILE_DIR:${_target}>
-P ${CMAKE_SOURCE_DIR}/cmake/CopyWasmModules.cmake
COMMENT "Copying WASM simulator modules to ${_target} output"
)
endforeach()
endif()
############# Standalone simulator ###############
set(simu_SRCS simulator.cpp )
add_executable(${SIMULATOR_NAME} MACOSX_BUNDLE ${WIN_EXECUTABLE_TYPE}
${simu_SRCS}
${icon_RC}
)
target_link_libraries(${SIMULATOR_NAME} PRIVATE
${CPN_COMMON_LIB}
${SDL2_LIBRARIES}
)
add_subdirectory(tests)
############# Install ####################
set(COMPANION_TARGETS_DIR_ROOT "${COMPANION_SRC_DIRECTORY}/../targets")
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(COMPANION_TARGETS_DIR "${COMPANION_TARGETS_DIR_ROOT}/linux")
elseif(APPLE)
set(COMPANION_TARGETS_DIR "${COMPANION_TARGETS_DIR_ROOT}/mac")
elseif(WIN32)
set(COMPANION_TARGETS_DIR "${COMPANION_TARGETS_DIR_ROOT}/windows")
endif()
# updated license file
set(LICENSE_FILE "${CMAKE_CURRENT_BINARY_DIR}/license.txt")
configure_file("${COMPANION_TARGETS_DIR_ROOT}/license.txt.in" ${LICENSE_FILE} @ONLY)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(STATUS "Install " ${CMAKE_BINARY_DIR} " to " ${CMAKE_INSTALL_PREFIX})
install(TARGETS ${COMPANION_NAME}
DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})
install(TARGETS ${SIMULATOR_NAME}
DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})
# configure custom AppRun script from template
configure_file(${COMPANION_TARGETS_DIR}/AppRun.in ${CMAKE_BINARY_DIR}/AppDir/AppRun @ONLY
)
# Make it executable
file(CHMOD ${CMAKE_BINARY_DIR}/AppDir/AppRun
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
)
# configure and set variables used by package script
set(COMPANION_DESKTOP_FILE ${CMAKE_CURRENT_BINARY_DIR}/${COMPANION_NAME}.desktop)
configure_file(${COMPANION_TARGETS_DIR}/companion.desktop.in ${COMPANION_DESKTOP_FILE} @ONLY)
set(SIMULATOR_DESKTOP_FILE ${CMAKE_CURRENT_BINARY_DIR}/${SIMULATOR_NAME}.desktop)
configure_file(${COMPANION_TARGETS_DIR}/simulator.desktop.in ${SIMULATOR_DESKTOP_FILE} @ONLY)
# libsim*.so files
install(DIRECTORY ${CMAKE_BINARY_DIR}/plugins/
DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/${COMPANION_NAME})
# application icons
set(icon_sizes "16;22;24;32;48;64;128;256;512")
foreach(icon_size ${icon_sizes})
install(FILES "images/linuxicons/${icon_size}x${icon_size}/companion.png"
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/${icon_size}x${icon_size}/apps"
RENAME "${COMPANION_NAME}.png")
endforeach()
unset(icon_size)
unset(icon_sizes)
install(FILES "images/linuxicons/scalable/companion.svg"
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/apps"
RENAME "${COMPANION_NAME}.svg")
if(OPENSSL_FOUND)
get_filename_component(OPENSSL_SSL_LIBRARY_DIR ${OPENSSL_SSL_LIBRARY} DIRECTORY)
install(DIRECTORY ${OPENSSL_SSL_LIBRARY_DIR}/
DESTINATION ${CMAKE_INSTALL_PREFIX}/lib
FILES_MATCHING
PATTERN "libcrypto.so.*"
PATTERN "libssl.so.*")
endif()
set(OperatingSystem "Linux")
# Shortcut target
add_custom_target(companion DEPENDS ${COMPANION_NAME})
add_custom_target(simulator DEPENDS ${SIMULATOR_NAME})
elseif(WIN32)
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/_install")
message(STATUS "Install to " ${CMAKE_INSTALL_PREFIX})
# companion and simulator apps
install(TARGETS
${COMPANION_NAME}
${SIMULATOR_NAME}
DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})
# install libsim dlls into same directory as apps
install(DIRECTORY ${CMAKE_BINARY_DIR}/plugins/
DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})
install(FILES ${LICENSE_FILE}
DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})
if(OPENSSL_FOUND)
cmake_path(GET OPENSSL_SSL_LIBRARY
PARENT_PATH
OPENSSL_SSL_LIBRARY_DIR)
if(MINGW)
# the dlls are stored in the bin directory
set(OPENSSL_SSL_LIBRARY_DIR "${OPENSSL_SSL_LIBRARY_DIR}/../bin")
endif()
if(EXISTS "${OPENSSL_SSL_LIBRARY_DIR}")
install(DIRECTORY ${OPENSSL_SSL_LIBRARY_DIR}/
DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}
FILES_MATCHING
PATTERN "libcrypto-*.dll"
PATTERN "libssl-*.dll")
endif()
endif(OPENSSL_FOUND)
if(TARGET SDL2::SDL2)
install(IMPORTED_RUNTIME_ARTIFACTS SDL2::SDL2
RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})
endif()
include(FindMSVCRedist)
if(MSVC_REDIST_EXE)
install(FILES ${MSVC_REDIST_EXE}
DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})
endif()
elseif(APPLE)
# Qt + Cmake + Mac is poorly documented. A lot of this is guesswork
# and trial and error. Do not hesitate to fix it for the better
set(COMPANION_DISPLAY_NAME "EdgeTX Companion")
# This the name that the user will see in the generated DMG and what the application
# will be called under /Applications. We include the version string to make installing
# different versions side-by-side
set(COMPANION_OSX_APP_BUNDLE_NAME "${COMPANION_DISPLAY_NAME} ${VERSION_FAMILY}")
set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.edgetx.companion")
set_target_properties(${COMPANION_NAME} PROPERTIES
OUTPUT_NAME "${COMPANION_OSX_APP_BUNDLE_NAME}"
MACOSX_RPATH TRUE
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_BUNDLE_NAME "${COMPANION_DISPLAY_NAME}"
MACOSX_BUNDLE_INFO_PLIST ${COMPANION_TARGETS_DIR}/MacOSXBundleInfo.plist.in
INSTALL_RPATH "@executable_path/../Frameworks"
BUILD_WITH_INSTALL_RPATH TRUE
)
set(companion_app_dir "${COMPANION_OSX_APP_BUNDLE_NAME}.app")
set(companion_res_dir "${companion_app_dir}/Contents/Resources")
# Only Companion is distributed for macOS
install(TARGETS ${COMPANION_NAME}
BUNDLE DESTINATION . COMPONENT Runtime)
# libsims
install(DIRECTORY ${CMAKE_BINARY_DIR}/plugins/
DESTINATION "${companion_res_dir}"
COMPONENT Runtime
)
# menu file
install(DIRECTORY ${COMPANION_TARGETS_DIR}/qt_menu.nib
DESTINATION "${companion_res_dir}"
COMPONENT Runtime
)
# Add icon
set(MAC_ICON_FILE ${COMPANION_SRC_DIRECTORY}/images/iconmac.icns)
set_source_files_properties(
${MAC_ICON_FILE}
PROPERTIES MACOSX_PACKAGE_LOCATION Resources
)
install(FILES ${MAC_ICON_FILE}
DESTINATION "${companion_res_dir}"
COMPONENT Runtime)
endif()
############# Packaging ####################
string(TOLOWER "${PROJECT_NAME}" PROJECT_NAME_LOWERCASE)
# Variables common to all CPack generators
set(CPACK_GENERATOR)
set(CPACK_PACKAGE_NAME "companion${APP_NAME_SUFFIX}")
string(TOLOWER "${CPACK_PACKAGE_NAME}" CPACK_PACKAGE_NAME_LOWERCASE)
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Models and settings editor for the ${PROJECT_NAME} open source firmware")
set(CPACK_PACKAGE_VENDOR "${PROJECT_NAME}")
set(CPACK_PACKAGE_CONTACT "${PROJECT_NAME} Project Steering Committee <psc@${PROJECT_NAME_LOWERCASE}.org>")
set(CPACK_PACKAGE_HOMEPAGE_URL "https://${PROJECT_NAME_LOWERCASE}.org")
set(CPACK_PACKAGE_VERSION "${VERSION_FAMILY}")
set(CPACK_PACKAGE_VERSION_MAJOR ${VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${VERSION_REVISION})
set(CPACK_RESOURCE_FILE_LICENSE ${LICENSE_FILE})
# The file stripping is deliberately disabled, with the stripped file we get
# very poor trace-backs from the users when they report Companion crash
set(CPACK_STRIP_FILES FALSE)
if(WIN32)
set(companion_executable_path "\${CMAKE_INSTALL_BINDIR}/$<TARGET_FILE_NAME:companion>")
set(simulator_executable_path "\${CMAKE_INSTALL_BINDIR}/$<TARGET_FILE_NAME:simulator>")
cmake_path(GET CMAKE_CXX_COMPILER PARENT_PATH MINGW_BIN_DIR)
qt_generate_deploy_script(
TARGET companion
OUTPUT_SCRIPT qt_deploy_script
CONTENT "
set(QT_DEPLOY_PREFIX ${CMAKE_INSTALL_PREFIX})
qt_deploy_runtime_dependencies(
EXECUTABLE ${companion_executable_path}
ADDITIONAL_EXECUTABLES ${simulator_executable_path}
NO_TRANSLATIONS
NO_COMPILER_RUNTIME
GENERATE_QT_CONF
VERBOSE
)
"
)
# Create Windows installer with NSIS
# TODO use in-built CPack NSIS
find_program(NSIS_EXE makensis.exe
PATHS
"C:/Program Files/NSIS"
"C:/Program Files (x86)/NSIS"
"C:/Programs/NSIS"
"${WIN_EXTRA_LIBS_PATH}/NSIS"
)
if(NSIS_EXE)
# list(APPEND CPACK_GENERATOR "NSIS")
# Set CPACK_NSIS_variables
cmake_path(NATIVE_PATH CMAKE_INSTALL_PREFIX NORMALIZE NSIS_DISTRO) # all files in here will be included in installer
cmake_path(NATIVE_PATH COMPANION_TARGETS_DIR NORMALIZE NSIS_TARGETS_DIR) # all files in here will be included in installer
cmake_path(GET MSVC_REDIST_EXE FILENAME NSIS_MSVC_REDIST_EXE)
set(NSI_FILE "${PROJECT_BINARY_DIR}/companion/companion.nsi")
configure_file("${COMPANION_TARGETS_DIR}/companion.nsi.in" "${NSI_FILE}" @ONLY)
# The 'install' target prepares all the distro files, make sure it has been executed first.
add_custom_target(installer
COMMAND "${CMAKE_COMMAND}" -P "${qt_deploy_script}"
COMMAND "${NSIS_EXE}" "${NSI_FILE}"
DEPENDS install "${NSI_FILE}" "${qt_deploy_script}"
COMMENT "Building Windows NSIS installer..."
)
endif()
elseif(APPLE)
set(CPACK_GENERATOR "DragNDrop")
set(CPACK_BINARY_DRAGNDROP ON)
set(CPACK_DMG_VOLUME_NAME "${PROJECT_NAME} Companion")
set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME_LOWERCASE}-${CPACK_PACKAGE_NAME_LOWERCASE}-${VERSION}")
set(CPACK_DMG_DS_STORE ${COMPANION_TARGETS_DIR}/DS_Store)
qt_generate_deploy_script(
TARGET companion
OUTPUT_SCRIPT qt_deploy_companion
CONTENT "
qt_deploy_runtime_dependencies(
EXECUTABLE \"${companion_app_dir}\"
NO_TRANSLATIONS
NO_COMPILER_RUNTIME
GENERATE_QT_CONF
)
"
)
# Run macdeployqt
install(SCRIPT ${qt_deploy_companion} COMPONENT Runtime)
# Bundle SDL2
if(TARGET SDL2::SDL2)
install(IMPORTED_RUNTIME_ARTIFACTS SDL2::SDL2
FRAMEWORK DESTINATION "${companion_app_dir}/Contents/Frameworks"
LIBRARY DESTINATION "${companion_app_dir}/Contents/Frameworks"
COMPONENT Runtime)
endif()
# Re-sign bundle after all contents are installed
install(CODE "
set(app_dir \"\${CMAKE_INSTALL_PREFIX}/${companion_app_dir}\")
execute_process(COMMAND \${CMAKE_COMMAND} -E echo \"Re-signing: \${app_dir}\")
execute_process(
COMMAND codesign --remove-signature \${app_dir}
COMMAND codesign --force --deep -s - \${app_dir}
)
" COMPONENT Runtime)
else()
# Detect architecture for linuxdeploy AppImage
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
set(LINUXDEPLOY_ARCH "aarch64")
else()
set(LINUXDEPLOY_ARCH "x86_64")
endif()
set(LINUXDEPLOY_APPIMAGE "linuxdeploy-${LINUXDEPLOY_ARCH}.AppImage")
set(LINUXDEPLOY_PLUGIN_QT "linuxdeploy-plugin-qt-${LINUXDEPLOY_ARCH}.AppImage")
set(LINUXDEPLOY_URL "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous")
set(LINUXDEPLOY_PLUGIN_QT_URL "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous")
set(LINUXDEPLOY_DIRECTORY "${CMAKE_BINARY_DIR}/linuxdeploy")
set(LINUXDEPLOY_APP "${LINUXDEPLOY_DIRECTORY}/usr/bin/linuxdeploy")
if(NOT EXISTS "${LINUXDEPLOY_DIRECTORY}")
message(STATUS "Downloading linuxdeploy and plugins...")
file(DOWNLOAD "${LINUXDEPLOY_URL}/${LINUXDEPLOY_APPIMAGE}" "${CMAKE_BINARY_DIR}/${LINUXDEPLOY_APPIMAGE}")
file(DOWNLOAD "${LINUXDEPLOY_PLUGIN_QT_URL}/${LINUXDEPLOY_PLUGIN_QT}" "${CMAKE_BINARY_DIR}/${LINUXDEPLOY_PLUGIN_QT}")
message(STATUS "Downloading linuxdeploy and plugins finished")
# for security reasons by default AppImages are prohibited from running in Docker containers
# so extract Appimage contents and run as normal apps
file(GLOB linuxdeployfiles ${CMAKE_BINARY_DIR}/linuxdeploy*.AppImage)
foreach(file ${linuxdeployfiles})
execute_process(
COMMAND chmod +x "${file}"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
execute_process(
COMMAND "${file}" --appimage-extract
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
file(REMOVE "${file}")
endforeach()
file(REMOVE_RECURSE "${LINUXDEPLOY_DIRECTORY}")
execute_process(
COMMAND mv "${CMAKE_BINARY_DIR}/squashfs-root" "${LINUXDEPLOY_DIRECTORY}"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
# test installation
execute_process(
COMMAND ${LINUXDEPLOY_APP} --list-plugins
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
endif()
list(APPEND CPACK_GENERATOR "External")
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
set(APPIMAGE_DIR "${CPACK_PACKAGE_DIRECTORY}/_CPack_Packages/Linux/External/AppImage")
configure_file("${COMPANION_TARGETS_DIR}/CPackLinuxDeploy.cmake.in" "${CMAKE_BINARY_DIR}/CPackExternal.cmake" @ONLY)
set(CPACK_EXTERNAL_PACKAGE_SCRIPT "${CMAKE_BINARY_DIR}/CPackExternal.cmake")
endif()
include(CPack)
+959
View File
@@ -0,0 +1,959 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "apppreferencesdialog.h"
#include "ui_apppreferencesdialog.h"
#include "mainwindow.h"
#include "helpers.h"
#include "storage.h"
#if defined(USE_SDL)
#include "joystick.h"
#include "joystickdialog.h"
#endif
#include "moduledata.h"
#include "filtereditemmodels.h"
#include "updates/updatefactories.h"
#include "updates/updateoptionsdialog.h"
#include <QPalette>
#include <QColorDialog>
constexpr char FIM_TEMPLATESETUP[] {"Template Setup"};
AppPreferencesDialog::AppPreferencesDialog(QWidget * parent, UpdateFactories * factories) :
QDialog(parent),
ui(new Ui::AppPreferencesDialog),
updateLock(false),
mainWinHasDirtyChild(false),
factories(factories),
panelItemModels(nullptr)
{
ui->setupUi(this);
setWindowIcon(CompanionIcon("apppreferences.png"));
ui->tabWidget->setCurrentIndex(0);
panelItemModels = new FilteredItemModelFactory();
panelItemModels->registerItemModel(new FilteredItemModel(GeneralSettings::templateSetupItemModel()), FIM_TEMPLATESETUP);
initSettings();
connect(ui->boardCB, SIGNAL(currentIndexChanged(int)), this, SLOT(onBaseFirmwareChanged()));
connect(ui->opt_appDebugLog, &QCheckBox::toggled, this, &AppPreferencesDialog::toggleAppLogSettings);
connect(ui->opt_fwTraceLog, &QCheckBox::toggled, this, &AppPreferencesDialog::toggleAppLogSettings);
connect(ui->backupPath, &QLineEdit::editingFinished, this, &AppPreferencesDialog::onBackupPathEditingFinished);
connect(ui->backupPathButton, &QPushButton::clicked, this, &AppPreferencesDialog::onBackupPathButtonClicked);
connect(ui->profileBackupPath, &QLineEdit::editingFinished, this, &AppPreferencesDialog::onProfileBackupPathEditingFinished);
connect(ui->profileBackupPathButton, &QPushButton::clicked, this, &AppPreferencesDialog::onProfileBackupPathButtonClicked);
#if !defined(USE_SDL)
ui->joystickCB->hide();
ui->joystickCB->setDisabled(true);
ui->joystickcalButton->hide();
ui->joystickChkB->hide();
ui->label_11->hide();
#endif
shrink();
}
AppPreferencesDialog::~AppPreferencesDialog()
{
delete ui;
delete panelItemModels;
}
void AppPreferencesDialog::setMainWinHasDirtyChild(bool value)
{
mainWinHasDirtyChild = value;
}
void AppPreferencesDialog::accept()
{
Profile & profile = g.currentProfile();
g.showSplash(ui->showSplash->isChecked());
g.sortProfiles(ui->sortProfiles->isChecked());
g.promptProfile(ui->chkPromptProfile->isChecked());
g.removeModelSlots(ui->opt_removeBlankSlots->isChecked());
g.newModelAction((AppData::NewModelAction)ui->cboNewModelAction->currentIndex());
g.historySize(ui->historySize->value());
profile.volumeGain(round(ui->volumeGain->value() * 10.0));
g.libDir(ui->libraryPath->text());
g.gePath(ui->ge_lineedit->text());
g.embedSplashes(ui->splashincludeCB->currentIndex());
g.enableBackup(ui->backupEnable->isChecked());
g.appDebugLog(ui->opt_appDebugLog->isChecked());
g.fwTraceLog(ui->opt_fwTraceLog->isChecked());
g.appLogsDir(ui->appLogsDir->text());
g.runAppInstaller(ui->chkPromptInstall->isChecked());
g.useSavedSettings(ui->chkUseSavedSettingsApp->isChecked());
// Simulator tab
g.simuSW(ui->simuSW->isChecked());
g.backLight(ui->backLightColor->currentIndex());
g.simuGenericKeysPos((AppData::SimuGenericKeysPos)ui->cboSimuGenericKeysPos->currentIndex());
g.simuScrollButtons(ui->chkSimuScrollButtons->isChecked());
g.disableJoystickWarning(ui->joystickWarningCB->isChecked());
if (ui->joystickChkB ->isChecked()) {
g.jsSupport(ui->joystickChkB ->isChecked());
// Don't overwrite selected joystick if not connected. Avoid surprising the user.
if (ui->joystickCB->isEnabled()) {
profile.jsName(ui->joystickCB->currentText());
g.loadNamedJS();
}
}
else {
g.jsSupport(false);
}
// Updates tab
if (ui->leDownloadDir->text().isEmpty()) {
QMessageBox::warning(this, CPN_STR_APP_NAME, tr("Update Settings: Download folder path missing!"));
return;
}
if (ui->leDecompressDir->text().isEmpty()) {
QMessageBox::warning(this, CPN_STR_APP_NAME, tr("Update Settings: Decompress folder path missing!"));
return;
}
if (ui->leUpdateDir->text().isEmpty()) {
QMessageBox::warning(this, CPN_STR_APP_NAME, tr("Update Settings: Update folder path missing!"));
return;
}
if (!ui->chkDecompressDirUseDwnld->isChecked() &&
ui->leDecompressDir->text().trimmed() == ui->leDownloadDir->text().trimmed()) {
QMessageBox::warning(this, CPN_STR_APP_NAME, tr("Update Settings: Decompress and download folders have the same path!"));
return;
}
g.updateCheckFreq(AppData::UpdateCheckFreq(ui->cboUpdateCheckFreq->currentIndex()));
g.downloadDir(ui->leDownloadDir->text());
g.decompressDirUseDwnld(ui->chkDecompressDirUseDwnld->isChecked());
g.decompressDir(ui->leDecompressDir->text());
g.updateDirUseSD(ui->chkUpdateDirUseSD->isChecked());
g.updateDir(ui->leUpdateDir->text());
g.updDelDownloads(ui->chkDelDownloads->isChecked());
g.updDelDecompress(ui->chkDelDecompress->isChecked());
g.updLogLevel(ui->cboLogLevel->currentIndex());
QMapIterator<QString, int> it(factories->sortedComponentsList());
while (it.hasNext()) {
it.next();
int i = it.value();
g.component[i].checkForUpdate(chkCheckForUpdate[i]->isChecked());
g.component[i].releaseChannel((ComponentData::ReleaseChannel)cboReleaseChannel[i]->currentIndex());
}
profile.defaultInternalModule(ui->defaultInternalModuleCB->currentData().toInt());
profile.externalModuleSize(ui->externalModuleSizeCB->currentData().toInt());
profile.channelOrder(ui->channelorderCB->currentData().toInt());
profile.defaultMode(ui->stickmodeCB->currentData().toInt());
profile.burnFirmware(ui->burnFirmware->isChecked());
profile.sdPath(ui->sdPath->text());
profile.pBackupDir(ui->profileBackupPath->text());
profile.penableBackup(ui->profileBackupEnable->isChecked());
profile.splashFile(ui->SplashFileName->text());
profile.runSDSync(ui->chkPromptSDSync->isChecked());
profile.radioSimCaseColor(ui->lblRadioColorSample->palette().button().color());
profile.simBtnClickedUseOSTheme(ui->chkSimBtnClickedUseOSTheme->isChecked());
profile.simBtnClickedColor(ui->lblSimBtnClickedColorSample->palette().button().color());
profile.useSavedSettings(ui->chkUseSavedSettingsProfile->isChecked());
// The profile name may NEVER be empty
if (ui->profileNameLE->text().isEmpty())
profile.name(tr("My Radio"));
else
profile.name(ui->profileNameLE->text());
bool fwchange = false;
Firmware * newFw = getFirmwareVariant(); // always !null
// If a new fw type has been choosen, several things need to reset
if (Firmware::getCurrentVariant()->getId() != newFw->getId()) {
// check if we're going to be converting to a new radio type and there are unsaved files in the main window
if (mainWinHasDirtyChild && !Boards::isBoardCompatible(Firmware::getCurrentVariant()->getBoard(), newFw->getBoard())) {
QString q = tr("<p><b>You cannot switch Radio Type or change Build Options while there are unsaved file changes. What do you wish to do?</b></p> <ul>" \
"<li><i>Save All</i> - Save any open file(s) before saving Settings.<li>" \
"<li><i>Reset</i> - Revert to the previous Radio Type and Build Options before saving Settings.</li>" \
"<li><i>Cancel</i> - Return to the Settings editor dialog.</li></ul>");
int resp = QMessageBox::question(this, windowTitle(), q, (QMessageBox::SaveAll | QMessageBox::Reset | QMessageBox::Cancel), QMessageBox::Cancel);
if (resp == QMessageBox::SaveAll) {
// signal main window to save files, need to do this before the fw actually changes
emit firmwareProfileAboutToChange();
}
else if (resp == QMessageBox::Reset) {
// bail out early before saving the radio type & firmware options
QDialog::accept();
return;
}
else {
// we do not accept the dialog close
return;
}
}
Firmware::setCurrentVariant(newFw);
profile.fwName("");
profile.fwType(newFw->getId());
fwchange = true;
}
QDialog::accept();
if (fwchange)
emit firmwareProfileChanged(); // important to do this after the accepted() signal
}
void AppPreferencesDialog::on_snapshotPathButton_clicked()
{
QString fileName = QFileDialog::getExistingDirectory(this, tr("Select your snapshot folder"), g.snapshotDir());
if (!fileName.isEmpty()) {
g.snapshotDir(fileName);
g.snapToClpbrd(false);
ui->snapshotPath->setText(fileName);
}
}
void AppPreferencesDialog::on_btnRadioColor_clicked()
{
Profile & profile = g.currentProfile();
QColorDialog *dlg = new QColorDialog(this);
QColor color = dlg->getColor(profile.radioSimCaseColor(), this);
ui->lblRadioColorSample->setPalette(QPalette(color));
ui->lblRadioColorSample->repaint();
}
void AppPreferencesDialog::initSettings()
{
Profile & profile = g.currentProfile();
ui->burnFirmware->setChecked(profile.burnFirmware());
ui->showSplash->setChecked(g.showSplash());
ui->sortProfiles->setChecked(g.sortProfiles());
ui->chkPromptProfile->setChecked(g.promptProfile());
ui->historySize->setValue(g.historySize());
ui->opt_removeBlankSlots->setChecked(g.removeModelSlots());
ui->cboNewModelAction->addItems(AppData::newModelActionsList());
ui->cboNewModelAction->setCurrentIndex(g.newModelAction());
ui->libraryPath->setText(g.libDir());
ui->ge_lineedit->setText(g.gePath());
ui->backupPath->setText(g.backupDir());
if (!ui->backupPath->text().isEmpty() && QDir(ui->backupPath->text()).exists()) {
ui->backupEnable->setEnabled(true);
ui->backupEnable->setChecked(g.enableBackup());
} else {
ui->backupEnable->setChecked(false);
ui->backupEnable->setEnabled(false);
}
ui->splashincludeCB->setCurrentIndex(g.embedSplashes());
ui->opt_appDebugLog->setChecked(g.appDebugLog());
ui->opt_fwTraceLog->setChecked(g.fwTraceLog());
ui->appLogsDir->setText(g.appLogsDir());
toggleAppLogSettings();
ui->chkPromptInstall->setChecked(g.runAppInstaller());
// Simulator tab
ui->snapshotPath->setText(g.snapshotDir());
ui->snapshotPath->setReadOnly(true);
ui->snapshotClipboardCKB->setChecked(g.snapToClpbrd());
if (ui->snapshotClipboardCKB->isChecked()) {
ui->snapshotPath->setDisabled(true);
ui->snapshotPathButton->setDisabled(true);
}
ui->simuSW->setChecked(g.simuSW());
ui->backLightColor->setCurrentIndex(g.backLight());
if (!Boards::getCapability(getCurrentBoard(), Board::HasBacklightColor))
ui->backLightColor->setEnabled(false);
ui->volumeGain->setValue(profile.volumeGain() / 10.0);
ui->cboSimuGenericKeysPos->addItems(AppData::simuGenericKeysPosList());
ui->cboSimuGenericKeysPos->setCurrentIndex(g.simuGenericKeysPos());
ui->chkSimuScrollButtons->setChecked(g.simuScrollButtons());
ui->joystickWarningCB->setChecked(g.disableJoystickWarning());
ui->chkUseSavedSettingsApp->setChecked(g.useSavedSettings());
#if defined(USE_SDL)
ui->joystickChkB->setChecked(g.jsSupport());
if (ui->joystickChkB->isChecked()) {
QStringList joystickNames;
joystickNames << tr("No joysticks found");
joystick = new Joystick(0,0,false,0);
ui->joystickcalButton->setDisabled(true);
ui->joystickCB->setDisabled(true);
if ( joystick ) {
if ( joystick->joystickNames.count() > 0 ) {
joystickNames = joystick->joystickNames;
ui->joystickCB->setEnabled(true);
ui->joystickcalButton->setEnabled(true);
}
joystick->close();
}
ui->joystickCB->clear();
ui->joystickCB->insertItems(0, joystickNames);
int stick = joystick->findCurrent(g.currentProfile().jsName());
ui->joystickCB->setCurrentIndex(stick);
}
else {
ui->joystickCB->clear();
ui->joystickCB->setDisabled(true);
ui->joystickcalButton->setDisabled(true);
}
#endif
// Profile Tab Inits
ui->defaultInternalModuleCB->setModel(ModuleData::internalModuleItemModel());
ui->defaultInternalModuleCB->setCurrentIndex(ui->defaultInternalModuleCB->findData(profile.defaultInternalModule()));
ui->externalModuleSizeCB->setModel(Boards::externalModuleSizeItemModel());
ui->externalModuleSizeCB->setCurrentIndex(ui->externalModuleSizeCB->findData(profile.externalModuleSize()));
panelItemModels->getItemModel(FIM_TEMPLATESETUP)->setFilterFlags(Boards::isAir() ? GeneralSettings::RadioTypeContextAir :
GeneralSettings::RadioTypeContextSurface);
ui->sdPath->setText(profile.sdPath());
ui->profileBackupPath->setText(profile.pBackupDir());
if (!ui->profileBackupPath->text().isEmpty()) {
if (QDir(ui->profileBackupPath->text()).exists()) {
ui->profileBackupEnable->setEnabled(true);
ui->profileBackupEnable->setChecked(profile.penableBackup());
} else {
ui->profileBackupEnable->setChecked(false);
ui->profileBackupEnable->setEnabled(false);
}
} else if (!ui->backupPath->text().isEmpty() && QDir(ui->backupPath->text()).exists()) {
ui->profileBackupEnable->setEnabled(true);
ui->profileBackupEnable->setChecked(profile.penableBackup());
} else {
ui->profileBackupEnable->setChecked(false);
ui->profileBackupEnable->setEnabled(false);
}
ui->profileNameLE->setText(profile.name());
QString hwSettings;
if (profile.generalSettings().isEmpty()) {
hwSettings = tr("No backup available for this profile");
}
else {
QString str = profile.timeStamp();
if (str.isEmpty())
hwSettings = tr("Backup available of unknown age");
else
hwSettings = tr("Backup available dated %1").arg(str);
}
ui->stickmodeCB->setModel(GeneralSettings::stickModeItemModel());
ui->stickmodeCB->setCurrentIndex(ui->stickmodeCB->findData(profile.defaultMode()));
if (Boards::isSurface()) {
ui->stickmodeLabel->hide();
ui->stickmodeCB->hide();
}
ui->channelorderCB->setModel(panelItemModels->getItemModel(FIM_TEMPLATESETUP));
ui->channelorderCB->setCurrentIndex(ui->channelorderCB->findData(profile.channelOrder()));
ui->chkUseSavedSettingsProfile->setChecked(profile.useSavedSettings());
ui->lblGeneralSettings->setText(hwSettings);
on_chkUseSavedSettingsProfile_stateChanged();
ui->chkPromptSDSync->setChecked(profile.runSDSync());
ui->lblRadioColorSample->setPalette(QPalette(profile.radioSimCaseColor()));
ui->chkSimBtnClickedUseOSTheme->setChecked(profile.simBtnClickedUseOSTheme());
ui->lblSimBtnClickedColorSample->setPalette(QPalette(profile.simBtnClickedColor()));
if (ui->chkSimBtnClickedUseOSTheme->isChecked()) {
ui->lblSimBtnClickedColorSample->setVisible(false);
ui->btnSimBtnClickedColor->setEnabled(false);
} else {
ui->lblSimBtnClickedColorSample->setVisible(true);
ui->btnSimBtnClickedColor->setEnabled(true);
}
QString currType = QStringList(profile.fwType().split('-').mid(0, 2)).join('-');
foreach(Firmware * firmware, Firmware::getRegisteredFirmwares()) {
ui->boardCB->addItem(firmware->getName(), firmware->getId());
if (currType == firmware->getId()) {
ui->boardCB->setCurrentIndex(ui->boardCB->count() - 1);
}
}
populateFirmwareOptions(getBaseFirmware());
// Updates tab
ui->cboUpdateCheckFreq->addItems(AppData::updateCheckFreqsList());
connect(ui->btnResetUpdatesToDefaults, &QPushButton::clicked, [=]() {
if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Reset all update settings to defaults. Are you sure?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) {
g.resetUpdatesSettings();
QMessageBox::warning(this, CPN_STR_APP_NAME,
tr("Update settings have been reset. Please close and restart Companion to avoid unexpected behaviour!"));
loadUpdatesTab();
}
});
connect(ui->chkDecompressDirUseDwnld, &QCheckBox::toggled, [=](const bool checked) {
if (!checked) {
ui->leDecompressDir->setText(g.decompressDir());
ui->leDecompressDir->setEnabled(true);
ui->btnDecompressSelect->setEnabled(true);
ui->chkDelDownloads->setEnabled(true);
}
else {
ui->leDecompressDir->setText(g.downloadDir());
ui->leDecompressDir->setEnabled(false);
ui->btnDecompressSelect->setEnabled(false);
if (ui->chkDelDecompress->isChecked()) {
ui->chkDelDownloads->setEnabled(true);
}
else {
ui->chkDelDownloads->setEnabled(false);
ui->chkDelDownloads->setChecked(false);
}
}
});
connect(ui->chkUpdateDirUseSD, &QCheckBox::toggled, [=](const bool checked) {
if (!checked) {
ui->leUpdateDir->setText(g.updateDir());
ui->leUpdateDir->setEnabled(true);
ui->btnUpdateSelect->setEnabled(true);
}
else {
ui->leUpdateDir->setText(g.currentProfile().sdPath());
ui->leUpdateDir->setEnabled(false);
ui->btnUpdateSelect->setEnabled(false);
}
});
connect(ui->btnDownloadSelect, &QPushButton::clicked, [=]() {
QString dirPath = QFileDialog::getExistingDirectory(this,tr("Select your download folder"), g.downloadDir());
if (!dirPath.isEmpty()) {
ui->leDownloadDir->setText(dirPath);
}
});
connect(ui->btnDecompressSelect, &QPushButton::clicked, [=]() {
QString dirPath = QFileDialog::getExistingDirectory(this,tr("Select your decompress folder"), g.decompressDir());
if (!dirPath.isEmpty()) {
ui->leDecompressDir->setText(dirPath);
}
});
connect(ui->btnUpdateSelect, &QPushButton::clicked, [=]() {
QString dirPath = QFileDialog::getExistingDirectory(this,tr("Select your update destination folder"), g.updateDir());
if (!dirPath.isEmpty()) {
ui->leUpdateDir->setText(dirPath);
}
});
connect(ui->chkDelDecompress, &QCheckBox::checkStateChanged, [=](const int checked) {
if (!checked) {
if (ui->chkDecompressDirUseDwnld->isChecked()) {
ui->chkDelDownloads->setEnabled(false);
ui->chkDelDownloads->setChecked(false);
}
}
else {
ui->chkDelDownloads->setEnabled(true);
}
});
int row = 0;
int col = 0;
QGridLayout *grid = new QGridLayout();
col++; // leave col 0 blank
QLabel *h1 = new QLabel(tr("Check"));
grid->addWidget(h1, row, col++);
QLabel *h2 = new QLabel(tr("Release channel"));
grid->addWidget(h2, row, col++);
col++; // options button
QSpacerItem * spacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum );
grid->addItem(spacer, row, col++);
QMapIterator<QString, int> it(factories->sortedComponentsList());
while (it.hasNext()) {
it.next();
int i = it.value();
row++;
col = 0;
lblName[i] = new QLabel();
grid->addWidget(lblName[i], row, col++);
chkCheckForUpdate[i] = new QCheckBox();
chkCheckForUpdate[i]->setStyleSheet("spacing: 10px"); // workaround Qt 6.9.0 Qt::AlignHCenter causes text to overlap checkbox rhs
grid->addWidget(chkCheckForUpdate[i], row, col++);
grid->setAlignment(chkCheckForUpdate[i], Qt::AlignHCenter);
cboReleaseChannel[i] = new QComboBox();
cboReleaseChannel[i]->addItems(ComponentData::releaseChannelsList());
grid->addWidget(cboReleaseChannel[i], row, col++);
btnComponentOptions[i] = new QPushButton(tr("Options"));
connect(btnComponentOptions[i], &QPushButton::clicked, [=]() {
UpdateOptionsDialog *dlg = new UpdateOptionsDialog(this, factories->instance(i), i, false);
dlg->exec();
dlg->deleteLater();
});
grid->addWidget(btnComponentOptions[i], row, col++);
}
ui->grpComponents->setLayout(grid);
ui->cboLogLevel->addItems(AppData::updateLogLevelsList());
loadUpdatesTab();
}
void AppPreferencesDialog::loadUpdatesTab()
{
ui->cboUpdateCheckFreq->setCurrentIndex(g.updateCheckFreq());
ui->chkDelDownloads->setChecked(g.updDelDownloads());
ui->chkDelDecompress->setChecked(g.updDelDecompress());
ui->leDownloadDir->setText(g.downloadDir());
// trigger toggled signal by changing design value and then setting to saved value
ui->chkDecompressDirUseDwnld->setChecked(!ui->chkDecompressDirUseDwnld->isChecked());
ui->chkDecompressDirUseDwnld->setChecked(g.decompressDirUseDwnld());
if (g.currentProfile().sdPath().trimmed().isEmpty())
ui->chkUpdateDirUseSD->setEnabled(false);
else
ui->chkUpdateDirUseSD->setEnabled(true);
if (g.updateDirUseSD() && g.currentProfile().sdPath().trimmed().isEmpty()) {
g.updateDirUseSD(false);
g.updateDirReset();
}
if (g.updateDirUseSD()) {
// trigger toggled signal by changing design value and then setting to saved value
ui->chkUpdateDirUseSD->setChecked(!ui->chkUpdateDirUseSD->isChecked());
ui->chkUpdateDirUseSD->setChecked(g.updateDirUseSD());
}
else
ui->chkUpdateDirUseSD->setChecked(false);
ui->chkDelDownloads->setChecked(g.updDelDownloads());
ui->cboLogLevel->setCurrentIndex(g.updLogLevel());
QMapIterator<QString, int> it(factories->sortedComponentsList());
while (it.hasNext()) {
it.next();
int i = it.value();
lblName[i]->setText(it.key());
chkCheckForUpdate[i]->setChecked(g.component[i].checkForUpdate());
cboReleaseChannel[i]->setCurrentIndex(g.component[i].releaseChannel());
}
}
void AppPreferencesDialog::on_libraryPathButton_clicked()
{
QString fileName = QFileDialog::getExistingDirectory(this,tr("Select your library folder"), g.libDir());
if (!fileName.isEmpty()) {
g.libDir(fileName);
ui->libraryPath->setText(fileName);
}
}
void AppPreferencesDialog::on_snapshotClipboardCKB_clicked()
{
if (ui->snapshotClipboardCKB->isChecked()) {
ui->snapshotPath->setDisabled(true);
ui->snapshotPathButton->setDisabled(true);
g.snapToClpbrd(true);
}
else {
ui->snapshotPath->setEnabled(true);
ui->snapshotPath->setReadOnly(true);
ui->snapshotPathButton->setEnabled(true);
g.snapToClpbrd(false);
}
}
void AppPreferencesDialog::onBackupPathButtonClicked()
{
QString fileName = QFileDialog::getExistingDirectory(this,tr("Select your global backup folder"), g.backupDir());
if (!fileName.isEmpty()) {
g.backupDir(fileName);
ui->backupPath->setText(fileName);
ui->backupEnable->setEnabled(true);
ui->profileBackupEnable->setEnabled(true);
} else {
ui->backupEnable->setEnabled(false);
if (!g.currentProfile().pBackupDir().isEmpty() && QFileInfo(g.currentProfile().pBackupDir()).exists()) {
ui->profileBackupEnable->setEnabled(true);
} else {
ui->profileBackupEnable->setEnabled(false);
}
}
}
void AppPreferencesDialog::onProfileBackupPathButtonClicked()
{
QString fileName = QFileDialog::getExistingDirectory(this,tr("Select your profile backup folder"), g.backupDir());
if (!fileName.isEmpty()) {
ui->profileBackupPath->setText(fileName);
ui->profileBackupEnable->setEnabled(true);
}
}
void AppPreferencesDialog::on_btn_appLogsDir_clicked()
{
QString fileName = QFileDialog::getExistingDirectory(this, tr("Select a folder for application logs"), ui->appLogsDir->text());
if (!fileName.isEmpty()) {
ui->appLogsDir->setText(fileName);
}
}
void AppPreferencesDialog::on_ge_pathButton_clicked()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Select Google Earth executable"),ui->ge_lineedit->text());
if (!fileName.isEmpty()) {
ui->ge_lineedit->setText(fileName);
}
}
void AppPreferencesDialog::on_btnClearPos_clicked()
{
SimulatorOptions opts = g.profile[g.sessionId()].simulatorOptions();
opts.controlsState.clear();
g.profile[g.sessionId()].simulatorOptions(opts);
}
#if defined(USE_SDL)
void AppPreferencesDialog::on_joystickChkB_clicked() {
if (ui->joystickChkB->isChecked()) {
QStringList joystickNames;
joystickNames << tr("No joysticks found");
joystick = new Joystick(0,0,false,0);
ui->joystickcalButton->setDisabled(true);
ui->joystickCB->setDisabled(true);
if ( joystick ) {
if ( joystick->joystickNames.count() > 0 ) {
joystickNames = joystick->joystickNames;
ui->joystickCB->setEnabled(true);
ui->joystickcalButton->setEnabled(true);
}
joystick->close();
}
ui->joystickCB->clear();
ui->joystickCB->insertItems(0, joystickNames);
}
else {
ui->joystickCB->clear();
ui->joystickCB->setDisabled(true);
ui->joystickcalButton->setDisabled(true);
}
}
void AppPreferencesDialog::on_joystickcalButton_clicked() {
g.currentProfile().jsName(ui->joystickCB->currentText());
joystickDialog * jd = new joystickDialog(this);
jd->exec();
}
#endif
// ******** Profile tab functions
void AppPreferencesDialog::on_sdPathButton_clicked()
{
QString fileName = QFileDialog::getExistingDirectory(this,tr("Select the folder replicating your SD structure"), g.currentProfile().sdPath());
if (!fileName.isEmpty()) {
ui->sdPath->setText(fileName);
}
}
bool AppPreferencesDialog::displayImage(const QString & fileName)
{
// Start by clearing the label
ui->imageLabel->clear();
if (fileName.isEmpty())
return false;
QImage image(fileName);
if (image.isNull())
return false;
ui->imageLabel->setPixmap(makePixMap(image));
ui->imageLabel->setFixedSize(Boards::getCapability(getCurrentBoard(), Board::LcdWidth),
Boards::getCapability(getCurrentBoard(), Board::LcdHeight));
return true;
}
void AppPreferencesDialog::on_SplashSelect_clicked()
{
QString supportedImageFormats;
for (int formatIndex = 0; formatIndex < QImageReader::supportedImageFormats().count(); formatIndex++) {
supportedImageFormats += QLatin1String(" *.") + QImageReader::supportedImageFormats()[formatIndex];
}
QString fileName = QFileDialog::getOpenFileName(this,
tr("Open Image to load"), g.imagesDir(), tr("Images (%1)").arg(supportedImageFormats));
if (!fileName.isEmpty()){
g.imagesDir(QFileInfo(fileName).dir().absolutePath());
displayImage(fileName);
ui->SplashFileName->setText(fileName);
}
}
void AppPreferencesDialog::on_clearImageButton_clicked()
{
ui->imageLabel->clear();
ui->SplashFileName->clear();
}
void AppPreferencesDialog::onBaseFirmwareChanged()
{
populateFirmwareOptions(getBaseFirmware());
Firmware *newfw = getFirmwareVariant();
Profile & profile = g.currentProfile();
profile.defaultInternalModule(Boards::getDefaultInternalModules(newfw->getBoard()));
ui->defaultInternalModuleCB->setModel(ModuleData::internalModuleItemModel(newfw->getBoard()));
ui->defaultInternalModuleCB->setCurrentIndex(ui->defaultInternalModuleCB->findData(profile.defaultInternalModule()));
profile.externalModuleSize(Boards::getDefaultExternalModuleSize(newfw->getBoard()));
ui->externalModuleSizeCB->setModel(Boards::externalModuleSizeItemModel());
ui->externalModuleSizeCB->setCurrentIndex(ui->externalModuleSizeCB->findData(profile.externalModuleSize()));
if (Boards::isSurface()) {
profile.defaultMode(1);
ui->stickmodeLabel->hide();
ui->stickmodeCB->hide();
profile.channelOrder(0);
}
ui->stickmodeCB->setCurrentIndex(ui->stickmodeCB->findData(profile.defaultMode()));
panelItemModels->getItemModel(FIM_TEMPLATESETUP)->setFilterFlags(Boards::isAir() ? GeneralSettings::RadioTypeContextAir :
GeneralSettings::RadioTypeContextSurface);
ui->channelorderCB->setCurrentIndex(ui->channelorderCB->findData(profile.channelOrder()));
}
Firmware *AppPreferencesDialog::getBaseFirmware() const
{
return Firmware::getFirmwareForId(ui->boardCB->currentData().toString());
}
Firmware * AppPreferencesDialog::getFirmwareVariant() const
{
QString id = ui->boardCB->currentData().toString();
foreach(QCheckBox *cb, optionsCheckBoxes.values()) {
if (cb->isChecked())
id += "-" + cb->text();
}
if (ui->langCombo->count())
id += "-" + ui->langCombo->currentText();
return Firmware::getFirmwareForId(id);
}
void AppPreferencesDialog::onFirmwareOptionChanged(bool state)
{
QCheckBox *cb = qobject_cast<QCheckBox*>(sender());
if (!(cb && state))
return;
// This de-selects any mutually exlusive options (that is, members of the same QList<Option> list).
const Firmware::OptionsList & fwOpts = getBaseFirmware()->optionGroups();
for (const Firmware::OptionsGroup & optGrp : fwOpts) {
for (const Firmware::Option & opt : optGrp) {
if (cb->text() == opt.name) {
QCheckBox *ocb = nullptr;
foreach(const Firmware::Option & other, optGrp)
if (other.name != opt.name && (ocb = optionsCheckBoxes.value(other.name, nullptr)))
ocb->setChecked(false);
return;
}
}
}
}
void AppPreferencesDialog::toggleAppLogSettings()
{
bool vis = (ui->opt_appDebugLog->isChecked() || ui->opt_fwTraceLog->isChecked());
ui->appLogsDir->setVisible(vis);
ui->lbl_appLogsDir->setVisible(vis);
ui->btn_appLogsDir->setVisible(vis);
}
void AppPreferencesDialog::populateFirmwareOptions(const Firmware * firmware)
{
const Firmware * baseFw = firmware->getFirmwareBase();
QStringList currVariant = Firmware::getCurrentVariant()->getId().split('-');
QString fwLang = Firmware::getCurrentVariant()->getLanguage();
if (fwLang.isEmpty()) // try to detect os language
fwLang = QLocale::languageToString(QLocale().language()).split("_").first();
const QString currLang = ui->langCombo->count() ? ui->langCombo->currentText() : fwLang;
updateLock = true;
ui->langCombo->clear();
for (const char *lang : baseFw->languageList()) {
ui->langCombo->addItem(lang);
if (currLang == lang) {
ui->langCombo->setCurrentIndex(ui->langCombo->count() - 1);
}
}
if (optionsCheckBoxes.size()) {
currVariant.clear();
QMutableMapIterator<QString, QCheckBox *> it(optionsCheckBoxes);
while (it.hasNext()) {
it.next();
QCheckBox * cb = it.value();
if (cb->isChecked())
currVariant.append(it.key()); // keep previous selections
ui->optionsLayout->removeWidget(cb);
cb->deleteLater();
it.remove();
}
}
int index = 0;
QWidget * prevFocus = ui->langCombo;
for (const Firmware::OptionsGroup &optGrp : baseFw->optionGroups()) {
for (const Firmware::Option &opt : optGrp) {
QCheckBox * cb = new QCheckBox(ui->profileTab);
cb->setText(opt.name);
cb->setToolTip(opt.tooltip);
cb->setChecked(currVariant.contains(opt.name));
ui->optionsLayout->addWidget(cb, index / 4, index % 4);
QWidget::setTabOrder(prevFocus, cb);
// connect to duplicates check handler if this option is part of a group
if (optGrp.size() > 1)
connect(cb, &QCheckBox::toggled, this, &AppPreferencesDialog::onFirmwareOptionChanged);
optionsCheckBoxes.insert(opt.name, cb);
prevFocus = cb;
++index;
}
}
if (Boards::getCapability(baseFw->getBoard(), Board::HasColorLcd)) {
ui->widget_splashImage->hide();
ui->SplashFileName->setText("");
}
else {
ui->widget_splashImage->show();
ui->SplashFileName->setText(g.currentProfile().splashFile());
displayImage(g.currentProfile().splashFile());
}
updateLock = false;
QTimer::singleShot(50, this, &AppPreferencesDialog::shrink);
}
void AppPreferencesDialog::shrink()
{
adjustSize();
}
void AppPreferencesDialog::onBackupPathEditingFinished()
{
if(!ui->backupPath->text().isEmpty() && QFileInfo(ui->backupPath->text()).exists()) {
ui->backupEnable->setEnabled(true);
} else {
ui->backupEnable->setChecked(false);
ui->backupEnable->setEnabled(false);
}
onProfileBackupPathEditingFinished();
}
void AppPreferencesDialog::onProfileBackupPathEditingFinished()
{
if (!ui->profileBackupPath->text().isEmpty()) {
if (QDir(ui->profileBackupPath->text()).exists()) {
ui->profileBackupEnable->setEnabled(true);
} else {
ui->profileBackupEnable->setChecked(false);
ui->profileBackupEnable->setEnabled(false);
}
} else if (!ui->backupPath->text().isEmpty() && QDir(ui->backupPath->text()).exists()) {
ui->profileBackupEnable->setEnabled(true);
} else {
ui->profileBackupEnable->setChecked(false);
ui->profileBackupEnable->setEnabled(false);
}
}
void AppPreferencesDialog::on_chkSimBtnClickedUseOSTheme_stateChanged()
{
if (ui->chkSimBtnClickedUseOSTheme->isChecked()) {
ui->lblSimBtnClickedColorSample->setVisible(false);
ui->btnSimBtnClickedColor->setEnabled(false);
} else {
ui->lblSimBtnClickedColorSample->setVisible(true);
ui->btnSimBtnClickedColor->setEnabled(true);
}
}
void AppPreferencesDialog::on_btnSimBtnClickedColor_clicked()
{
QColorDialog *dlg = new QColorDialog(this);
QColor color = dlg->getColor(g.currentProfile().simBtnClickedColor(), this);
ui->lblSimBtnClickedColorSample->setPalette(QPalette(color));
ui->lblSimBtnClickedColorSample->repaint();
}
void AppPreferencesDialog::on_chkUseSavedSettingsProfile_stateChanged()
{
if (ui->chkUseSavedSettingsProfile->isChecked() && !g.currentProfile().generalSettings().isEmpty()) {
ui->stickmodeLabel->setEnabled(false);
ui->stickmodeCB->setEnabled(false);
ui->channelorderLabel->setEnabled(false);
ui->channelorderCB->setEnabled(false);
} else {
ui->stickmodeLabel->setEnabled(true);
ui->stickmodeCB->setEnabled(true);
ui->channelorderLabel->setEnabled(true);
ui->channelorderCB->setEnabled(true);
}
}
+111
View File
@@ -0,0 +1,111 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "eeprominterface.h"
#include "appdata.h"
#include <QDialog>
#include <QCheckBox>
#include <QComboBox>
class FilteredItemModelFactory;
namespace Ui {
class AppPreferencesDialog;
}
class Joystick;
class UpdateFactories;
class AppPreferencesDialog : public QDialog
{
Q_OBJECT
public:
explicit AppPreferencesDialog(QWidget * parent, UpdateFactories * factories);
~AppPreferencesDialog();
Joystick * joystick;
public slots:
void accept() Q_DECL_OVERRIDE;
void setMainWinHasDirtyChild(bool value);
signals:
void firmwareProfileChanged();
void firmwareProfileAboutToChange(bool saveFiles = true);
protected slots:
void shrink();
void onBaseFirmwareChanged();
void onFirmwareOptionChanged(bool state);
void toggleAppLogSettings();
void on_libraryPathButton_clicked();
void on_snapshotPathButton_clicked();
void on_snapshotClipboardCKB_clicked();
void onBackupPathButtonClicked();
void onProfileBackupPathButtonClicked();
void on_ge_pathButton_clicked();
void on_sdPathButton_clicked();
void on_SplashSelect_clicked();
void on_clearImageButton_clicked();
void on_btn_appLogsDir_clicked();
void on_btnClearPos_clicked();
#if defined(USE_SDL)
void on_joystickChkB_clicked();
void on_joystickcalButton_clicked();
#endif
void on_btnRadioColor_clicked();
void on_chkSimBtnClickedUseOSTheme_stateChanged();
void on_btnSimBtnClickedColor_clicked();
void onBackupPathEditingFinished();
void onProfileBackupPathEditingFinished();
void on_chkUseSavedSettingsProfile_stateChanged();
private:
void initSettings();
void populateFirmwareOptions(const Firmware *);
Firmware * getBaseFirmware() const;
Firmware * getFirmwareVariant() const;
bool displayImage(const QString & fileName);
Ui::AppPreferencesDialog *ui;
QMap<QString, QCheckBox *> optionsCheckBoxes;
bool updateLock;
bool mainWinHasDirtyChild;
UpdateFactories *factories;
QLabel *lblName[MAX_COMPONENTS];
QCheckBox *chkCheckForUpdate[MAX_COMPONENTS];
QComboBox *cboReleaseChannel[MAX_COMPONENTS];
QPushButton *btnComponentOptions[MAX_COMPONENTS];
FilteredItemModelFactory *panelItemModels;
void loadUpdatesTab();
};
File diff suppressed because it is too large Load Diff
+290
View File
@@ -0,0 +1,290 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <QApplication>
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QMessageBox>
#include <QSplashScreen>
#if defined(USE_SDL)
#include <SDL.h>
#undef main
#endif
#include <AppDebugMessageHandler>
#include "customdebug.h"
#include "mainwindow.h"
#include "version.h"
#include "appdata.h"
#include "simulatorinterface.h"
#include "storage.h"
#include "translations.h"
#include "helpers.h"
#include "boardfactories.h"
#ifdef __APPLE__
#include <QProxyStyle>
class MyProxyStyle : public QProxyStyle
{
public:
void polish ( QWidget * w ) {
QMenu* mn = dynamic_cast<QMenu*>(w);
QPushButton* pb = dynamic_cast<QPushButton*>(w);
if(!(mn || pb) && !w->testAttribute(Qt::WA_MacNormalSize))
w->setAttribute(Qt::WA_MacSmallSize);
}
};
#endif
void importError()
{
QMessageBox::critical(nullptr, CPN_STR_APP_NAME, QCoreApplication::translate("Companion", "The saved settings could not be imported, please try again or continue with current settings."), QMessageBox::Ok, QMessageBox::NoButton);
}
void checkSettingsImport(bool force = false)
{
QString previousVersion;
bool found;
if (!(found = g.findPreviousVersionSettings(&previousVersion))) {
found = QDir(CPN_SETTINGS_BACKUP_DIR).entryList(QStringList({"*.ini"}), QDir::Files).size() > 0;
}
if (!found && !force)
return;
if (previousVersion.isEmpty()) {
const QString impFileBtn = QCoreApplication::translate("Companion", "Import from File");
const QString impNoneBtn = QCoreApplication::translate("Companion", "Do not import");
QString msg;
if (found)
msg = QCoreApplication::translate("Companion", "We have found possible Companion settings backup file(s).\nDo you want to import settings from a file?");
else
msg = QCoreApplication::translate("Companion", "Import settings from a file, or start with current values.");
QMessageBox msgBox(QMessageBox::Question, CPN_STR_APP_NAME, msg);
msgBox.addButton(impFileBtn, QMessageBox::AcceptRole);
QPushButton *btnCancel = msgBox.addButton(impNoneBtn, QMessageBox::RejectRole);
msgBox.exec();
if (msgBox.clickedButton() == btnCancel)
return;
}
else {
if (!g.importSettings(previousVersion)) {
// very unlikely, but just in case of unexpected error, restart the import
importError();
checkSettingsImport();
}
return;
}
// Import from file
bool err = false;
QString impFile = CPN_SETTINGS_BACKUP_DIR;
impFile = QFileDialog::getOpenFileName(nullptr, QCoreApplication::translate("Companion", "Select %1:").arg(CPN_STR_APP_SETTINGS_FILES), impFile, CPN_STR_APP_SETTINGS_FILTER);
if (!impFile.isEmpty() && QFileInfo(impFile).isReadable()) {
QSettings fromSettings(impFile, QSettings::IniFormat);
if (g.importSettings(&fromSettings)) {
return;
}
else {
importError();
err = true;
}
}
// Restart the import prompt if user cancelled the file select dialog but we do have previous settings, or if we had a file import error.
if (err || !previousVersion.isEmpty())
checkSettingsImport();
}
void printHelpText()
{
printf(qPrintable(QString(APP_COMPANION % " v%s\n\n")), VERSION);
const char tmpl[] = " %s \t %s\n";
printf(tmpl, "--export", QCoreApplication::translate("Companion", "Save application settings to file...").toUtf8().constData());
printf(tmpl, "--import", QCoreApplication::translate("Companion", "Load application settings from file or previous version...").toUtf8().constData());
printf(tmpl, "--defaults", QCoreApplication::translate("Companion", "Reset ALL application settings to default and remove radio profiles...").toUtf8().constData());
printf(tmpl, "--quit ", QCoreApplication::translate("Companion", "Exit before settings initialization and application startup.").toUtf8().constData());
printf(tmpl, "--version", QCoreApplication::translate("Companion", "Print version number and exit.").toUtf8().constData());
printf(tmpl, "--help|-h", QCoreApplication::translate("Companion", "Print this help text.").toUtf8().constData());
fflush(stdout);
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setApplicationName(APP_COMPANION);
app.setOrganizationName(COMPANY);
app.setOrganizationDomain(COMPANY_DOMAIN);
app.setAttribute(Qt::AA_DontShowIconsInMenus, false);
const QStringList args(QApplication::arguments());
if (args.contains("--help") || args.contains("-h") || args.contains("/?")) {
printHelpText();
exit(0);
}
if (args.contains("--version")) {
printf("%s\n", VERSION);
fflush(stdout);
exit(0);
}
Q_INIT_RESOURCE(companion);
if (AppDebugMessageHandler::instance())
AppDebugMessageHandler::instance()->installAppMessageHandler();
CustomDebug::setFilterRules();
// handle settings export/import/reset via CLI
bool doExport = args.contains("--export");
bool doImport = args.contains("--import");
bool doReset = args.contains("--defaults");
if (doExport)
Helpers::exportAppSettings();
if (doReset) {
char ok;
printf("%s [Y/N] ", QCoreApplication::translate("Companion", "Reset ALL application settings to default values and remove radio profiles, are you sure?").toUtf8().constData());
std::cin >> ok;
if (ok != 'Y' && ok != 'y')
exit(0);
if (!doExport) {
printf("%s [Y/N] ", QCoreApplication::translate("Companion", "Would you like to perform a backup first?").toUtf8().constData());
std::cin >> ok;
if (ok == 'Y' || ok == 'y')
Helpers::exportAppSettings();
}
g.resetAllSettings();
g.storeAllSettings();
std::cout << QCoreApplication::translate("Companion", "Application settings were reset and saved.").toUtf8().constData() << std::endl;
}
if (doImport)
checkSettingsImport(true);
// give option to quit before settings init
if (args.contains("--quit"))
exit(0);
// end CLI handling
if (!doImport && g.isFirstUse())
checkSettingsImport(false);
g.init();
QFile dbgLog;
if (AppDebugMessageHandler::instance() && g.appDebugLog() && !g.appLogsDir().isEmpty() && QDir().mkpath(g.appLogsDir())) {
QString fn = g.appLogsDir() % "/CompanionDebug_" % QDateTime::currentDateTime().toString("yy-MM-dd_HH-mm-ss") % ".log";
dbgLog.setFileName(fn);
if (dbgLog.open(QIODevice::WriteOnly | QIODevice::Text)) {
AppDebugMessageHandler::instance()->addOutputDevice(&dbgLog);
}
}
#ifdef __APPLE__
app.setStyle(new MyProxyStyle);
#endif
Translations::installTranslators();
#if defined(USE_SDL)
#if defined(_WIN32) || defined(_WIN64)
putenv("SDL_AUDIODRIVER=directsound");
#endif
if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_AUDIO) < 0) {
fprintf(stderr, "ERROR: couldn't initialize SDL: %s\n", SDL_GetError());
}
#endif
Q_INIT_RESOURCE(hwdefs);
gBoardFactories = new BoardFactories();
registerStorageFactories();
registerOpenTxFirmwares();
SimulatorLoader::registerSimulators();
QTemporaryDir tempDir(QDir::tempPath() % "/etx-cpn-XXXXXX");
if (!tempDir.isValid()) {
qDebug() << "Unable to create application temporary directory";
gAppTempPath = "";
} else {
gAppTempPath = tempDir.path();
if (!QDir(gAppTempPath).mkdir("IMAGES"))
qDebug() << "Unable to create images cache directory:" << Helpers::getImagesCacheDir();
}
Profile & profile = g.currentProfile();
if (profile.fwType().isEmpty()){
profile.fwType(Firmware::getDefaultVariant()->getId());
profile.fwName("");
}
Firmware::setCurrentVariant(Firmware::getFirmwareForId(g.profile[g.id()].fwType()));
MainWindow *mainWin = new MainWindow();
mainWin->show();
if (g.showSplash()) {
QSplashScreen *splash = new QSplashScreen(QPixmap(":/images/splash.png"));
QTimer::singleShot(1, [=] {
splash->show();
});
QTimer::singleShot(1000*SPLASH_TIME, [=] {
splash->close();
delete splash;
});
}
int result = app.exec();
delete mainWin;
SimulatorLoader::unregisterSimulators();
unregisterOpenTxFirmwares();
unregisterStorageFactories();
gBoardFactories->unregisterBoardFactories();
#if defined(USE_SDL)
SDL_Quit();
#endif
qDebug() << "COMPANION EXIT" << result;
if (dbgLog.isOpen()) {
AppDebugMessageHandler::instance()->removeOutputDevice(&dbgLog);
dbgLog.close();
}
return result;
}
File diff suppressed because it is too large Load Diff
+102
View File
@@ -0,0 +1,102 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include <QCoreApplication>
#define CPN_MAX_TIMERS 3
#define CPN_MAX_FLIGHT_MODES 9
#define CPN_MAX_MIXERS 64
#define CPN_MAX_INPUTS 32
#define CPN_MAX_EXPOS 64
#define CPN_MAX_CURVES 32
#define CPN_MAX_POINTS 17
#define CPN_MAX_GVARS 15 // 0, 9, 15
#define CPN_MAX_CHNOUT 32 // number of real output channels
#define CPN_MAX_LOGICAL_SWITCHES 64 // number of custom switches
#define CPN_MAX_SPECIAL_FUNCTIONS 64 // number of functions assigned to switches
#define CPN_MAX_MODELS 60 // B&W
#define CPN_MAX_MODULES 2
#define CPN_MAX_STICKS Board::STICK_AXIS_COUNT
#define CPN_MAX_TRIMS Board::TRIM_AXIS_COUNT
#define CPN_MAX_CYC 3
#define CPN_MAX_SWITCHES_FLEX 4 // v2.10 cmake parameter FLEXSW
#define CPN_MAX_SWITCHES_FUNCTION 8
#define CPN_MAX_SWITCHES_STD 20
#define CPN_MAX_SWITCHES (CPN_MAX_SWITCHES_STD + CPN_MAX_SWITCHES_FLEX + CPN_MAX_SWITCHES_FUNCTION)
#define CPN_MAX_SENSORS 99
#define CPN_MAX_SCRIPTS 9 // Max for Color LCD
#define CPN_LEN_SCRIPT_FILENAME 6
#define CPN_LEN_SCRIPT_NAME 6
#define CPN_MAX_SCRIPT_INPUTS 6
#define CPN_MAX_SCRIPT_OUTPUTS 6
#define CPN_MAX_SPACEMOUSE 6
#define CPN_MAX_INPUTS 32 // v2.10 replaces CPN_MAX_ANALOGS - the value is abitary as radio ADC refactor is still a WIP
// pre v2.10
#define CPN_MAX_POTS 8
#define CPN_MAX_SLIDERS 4
#define CPN_MAX_MOUSE_ANALOGS 2
#define CPN_MAX_GYRO_ANALOGS 2
#define CPN_MAX_ANALOGS (CPN_MAX_STICKS + CPN_MAX_POTS + CPN_MAX_SLIDERS + CPN_MAX_MOUSE_ANALOGS + CPN_MAX_GYRO_ANALOGS)
// =========
#define CPN_STR_APP_NAME QCoreApplication::translate("Companion", "EdgeTX Companion")
#define CPN_STR_SIMU_NAME QCoreApplication::translate("Companion", "EdgeTX Simulator")
#define CPN_STR_TTL_INFO QCoreApplication::translate("Companion", "Information") // shared Title Case words, eg. for a window title or section heading
#define CPN_STR_TTL_WARNING QCoreApplication::translate("Companion", "Warning")
#define CPN_STR_TTL_ERROR QCoreApplication::translate("Companion", "Error")
#define CPN_STR_TTL_ACCEPT QCoreApplication::translate("Companion", "Accept")
#define CPN_STR_TTL_DECLINE QCoreApplication::translate("Companion", "Decline")
#define CPN_STR_FILES QCoreApplication::translate("Companion", "files")
#define CPN_STR_RAD_MOD_SETTINGS QCoreApplication::translate("Companion", "Radio and Models settings")
#define BIN_FILES_FILTER "BIN " % CPN_STR_FILES % " (*.bin);;"
#define UF2_FILES_FILTER "UF2 " % CPN_STR_FILES % " (*.uf2);;"
#define ETX_FILES_FILTER "EdgeTX " % CPN_STR_FILES % " (*.etx);;"
#define YML_FILES_FILTER "EdgeTX YAML " % CPN_STR_FILES % " (*.yml);;"
#define EEPROM_FILES_FILTER CPN_STR_RAD_MOD_SETTINGS % " " % CPN_STR_FILES % " (*.etx *.yml);;" % ETX_FILES_FILTER % YML_FILES_FILTER
#define FIRMWARE_FILES_FILTER "Firmware " % CPN_STR_FILES % " (*.bin *.uf2);;" % BIN_FILES_FILTER % UF2_FILES_FILTER
#define EXTERNAL_EEPROM_FILES_FILTER "EEPROM " % CPN_STR_FILES % " (*.bin);;" % BIN_FILES_FILTER
#define EEPE_EEPROM_FILE_HEADER "EEPE EEPROM FILE"
#define SIMU_FILES_FILTER ETX_FILES_FILTER
#define CPN_STR_APP_SETTINGS QCoreApplication::translate("Companion", "Application Settings")
#define CPN_STR_APP_SETTINGS_FILES CPN_STR_APP_SETTINGS % " " % CPN_STR_FILES
#define CPN_STR_APP_SETTINGS_FILTER CPN_STR_APP_SETTINGS_FILES % " (*.ini)"
#define CPN_STR_UNKNOWN_ITEM "???"
#define CPN_STR_NONE_ITEM "----"
#define CPN_STR_SW_INDICATOR_UP QCoreApplication::translate("RawSwitch", "\xE2\x86\x91") // Switch up position indicator: Up arrow, or similar.
#define CPN_STR_SW_INDICATOR_DN QCoreApplication::translate("RawSwitch", "\xE2\x86\x93") // Switch down position indicator: Down arrow, or similar.
#define CPN_STR_SW_INDICATOR_NEUT QCoreApplication::translate("RawSwitch", "-") // Switch neutral (middle) position indicator.
#define CPN_STR_SW_INDICATOR_REV QCoreApplication::translate("RawSwitch", "!") // Switch reversed logic (NOT) indicator.
#define CPN_STR_SRC_INDICATOR_NEG QCoreApplication::translate("RawSource", "-") // Source negative value indicator.
#define EDGETX_HOME_PAGE_URL QStringLiteral("https://edgetx.org")
#define EDGETX_DONATE_URL QStringLiteral("https://opencollective.com/edgetx/donate")
#define EDGETX_COMMIT_URL QStringLiteral("https://github.com/EdgeTX/edgetx/commit/")
#define EDGETX_ISSUES_URL QStringLiteral("https://github.com/EdgeTX/edgetx/issues/new/choose")
#define CPN_ADC_REFACTOR_VERSION "2.10.0"
+41
View File
@@ -0,0 +1,41 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "customdebug.h"
Q_LOGGING_CATEGORY(eepromImport, "eeprom.import")
Q_LOGGING_CATEGORY(simulatorInterfaceLoader, "simulator.interface.loader")
void CustomDebug::setFilterRules()
{
QString rules;
rules.append("eeprom.import=");
#if defined(DEBUG_STORAGE_IMPORT)
rules.append("true\n");
#else
rules.append("false\n");
#endif
rules.append("simulator.interface.*=true\n");
QLoggingCategory::setFilterRules(rules);
}
+39
View File
@@ -0,0 +1,39 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef _CUSTOMDEBUG_H_
#define _CUSTOMDEBUG_H_
#include <QLoggingCategory>
// Controls the generation of debug output for EEPROM import
Q_DECLARE_LOGGING_CATEGORY(eepromImport) // "eeprom.import"
// Controls the generation of debug output of SimulatorLoader class
Q_DECLARE_LOGGING_CATEGORY(simulatorInterfaceLoader) // "simulator.interface.loader"
class CustomDebug
{
public:
static void setFilterRules();
};
#endif // _CUSTOMDEBUG_H_
+28
View File
@@ -0,0 +1,28 @@
project(datamodels)
set(${PROJECT_NAME}_NAMES
compounditemmodels
filtereditemmodels
)
AddHeadersSources()
add_library(${PROJECT_NAME}
${${PROJECT_NAME}_HDRS}
${${PROJECT_NAME}_SRCS}
)
target_link_libraries(${PROJECT_NAME}
PRIVATE
${CPN_COMMON_LIB}
)
target_include_directories(${PROJECT_NAME}
PRIVATE
"${CMAKE_SOURCE_DIR}"
"${CMAKE_CURRENT_BINARY_DIR}"
"${COMPANION_SRC_DIRECTORY}"
PUBLIC
"${CMAKE_CURRENT_LIST_DIR}"
)
@@ -0,0 +1,866 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "compounditemmodels.h"
#include "generalsettings.h"
#include "eeprominterface.h"
#include "modeldata.h"
#include "adjustmentreference.h"
// static
QString AbstractItemModel::idToString(const int value)
{
switch (value) {
case IMID_Unknown:
return "Unknown";
case IMID_RawSource:
return "RawSource";
case IMID_RawSwitch:
return "RawSwitch";
case IMID_Curve:
return "Curve";
case IMID_GVarRef:
return "GVarRef";
case IMID_ThrSource:
return "ThrSource";
case IMID_CustomFuncAction:
return "CustomFuncAction";
case IMID_CustomFuncResetParam:
return "CustomFuncResetParam";
case IMID_TeleSource:
return "TeleSource";
case IMID_CurveRefType:
return "CurveRefType";
case IMID_CurveRefFunc:
return "CurveRefFunc";
case IMID_FlexSwitches:
return "FlexSwitches";
default:
return "Custom";
}
}
// static
void AbstractItemModel::dumpItemModelContents(AbstractItemModel * itemModel)
{
if (itemModel) {
qDebug() << "id:" << itemModel->getId() << "name:" << itemModel->getName();
for (int i = 0; i < itemModel->rowCount(); ++i) {
qDebug() << "row:" << i
<< "text:" << itemModel->data(itemModel->index(i, 0), Qt::DisplayRole).toString()
<< "id:" << itemModel->data(itemModel->index(i, 0), AbstractItemModel::IMDR_Id).toInt()
<< "avail:" << itemModel->data(itemModel->index(i, 0), AbstractItemModel::IMDR_Available).toBool()
<< "flags:" << itemModel->data(itemModel->index(i, 0), AbstractItemModel::IMDR_Flags).toInt()
<< "type:" << itemModel->data(itemModel->index(i, 0), AbstractItemModel::IMDR_Type).toInt();
}
}
else
qDebug() << "Error: model not of class AbstractItemModel";
}
//
// AbstractStaticItemModel
//
void AbstractStaticItemModel::loadItemList()
{
foreach (const ListItem *item, itemList) {
QStandardItem * modelItem = new QStandardItem();
modelItem->setText(item->text);
modelItem->setData(item->id, IMDR_Id);
modelItem->setData(item->type, IMDR_Type);
modelItem->setData(item->flags, IMDR_Flags);
modelItem->setData(item->isAvailable, IMDR_Available);
appendRow(modelItem);
}
itemList.clear();
};
//
// RawSourceItemModel
//
RawSourceItemModel::RawSourceItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType) :
AbstractDynamicItemModel(generalSettings, modelData, firmware, board, boardType)
{
setId(IMID_RawSource);
setUpdateMask(IMUE_All &~ (IMUE_Curves | IMUE_Scripts));
int groups = board->getCapability(Board::FunctionSwitchGroups);
// Descending source direction: inverted (!) sources
addItems(SOURCE_TYPE_TELEMETRY, RawSource::TelemGroup, -firmware->getCapability(Sensors) * 3);
addItems(SOURCE_TYPE_TIMER, RawSource::TelemGroup, -firmware->getCapability(Timers));
addItems(SOURCE_TYPE_SPECIAL, RawSource::TelemGroup, -(SOURCE_TYPE_SPECIAL_COUNT - 1));
addItems(SOURCE_TYPE_GVAR, RawSource::GVarsGroup, -firmware->getCapability(Gvars));
addItems(SOURCE_TYPE_CH, RawSource::SourcesGroup, -firmware->getCapability(Outputs));
addItems(SOURCE_TYPE_PPM, RawSource::SourcesGroup, -firmware->getCapability(TrainerInputs));
addItems(SOURCE_TYPE_CYC, RawSource::SourcesGroup, -CPN_MAX_CYC);
addItems(SOURCE_TYPE_CUSTOM_SWITCH, RawSource::SwitchesGroup, -firmware->getCapability(LogicalSwitches));
if (modelData && groups > 0)
addItems(SOURCE_TYPE_FUNCTIONSWITCH_GROUP, RawSource::SourcesGroup, -groups);
addItems(SOURCE_TYPE_SWITCH, RawSource::SwitchesGroup, -board->getCapability(Board::Switches));
addItems(SOURCE_TYPE_MAX, RawSource::SourcesGroup, -1);
addItems(SOURCE_TYPE_MIN, RawSource::SourcesGroup, -1);
addItems(SOURCE_TYPE_SPACEMOUSE, RawSource::SourcesGroup, -CPN_MAX_SPACEMOUSE);
addItems(SOURCE_TYPE_TRIM, RawSource::TrimsGroup, -board->getCapability(Board::NumTrims));
addItems(SOURCE_TYPE_INPUT, RawSource::SourcesGroup, -board->getCapability(Board::Inputs));
addItems(SOURCE_TYPE_VIRTUAL_INPUT, RawSource::InputsGroup, -firmware->getCapability(VirtualInputs));
for (int i = firmware->getCapability(LuaScripts) - 1; i >= 0; i--)
addItems(SOURCE_TYPE_LUA_OUTPUT, RawSource::ScriptsGroup, -firmware->getCapability(LuaOutputsPerScript), -i * 16);
// Ascending source direction (including zero)
addItems(SOURCE_TYPE_NONE, RawSource::NoneGroup, 1);
for (int i = 0; i < firmware->getCapability(LuaScripts); i++)
addItems(SOURCE_TYPE_LUA_OUTPUT, RawSource::ScriptsGroup, firmware->getCapability(LuaOutputsPerScript), i * 16);
addItems(SOURCE_TYPE_VIRTUAL_INPUT, RawSource::InputsGroup, firmware->getCapability(VirtualInputs));
addItems(SOURCE_TYPE_INPUT, RawSource::SourcesGroup, board->getCapability(Board::Inputs));
addItems(SOURCE_TYPE_TRIM, RawSource::TrimsGroup, board->getCapability(Board::NumTrims));
addItems(SOURCE_TYPE_SPACEMOUSE, RawSource::SourcesGroup, CPN_MAX_SPACEMOUSE);
addItems(SOURCE_TYPE_MIN, RawSource::SourcesGroup, 1);
addItems(SOURCE_TYPE_MAX, RawSource::SourcesGroup, 1);
addItems(SOURCE_TYPE_SWITCH, RawSource::SwitchesGroup, board->getCapability(Board::Switches));
if (modelData && groups > 0)
addItems(SOURCE_TYPE_FUNCTIONSWITCH_GROUP, RawSource::SourcesGroup, groups);
addItems(SOURCE_TYPE_CUSTOM_SWITCH, RawSource::SwitchesGroup, firmware->getCapability(LogicalSwitches));
addItems(SOURCE_TYPE_CYC, RawSource::SourcesGroup, CPN_MAX_CYC);
addItems(SOURCE_TYPE_PPM, RawSource::SourcesGroup, firmware->getCapability(TrainerInputs));
addItems(SOURCE_TYPE_CH, RawSource::SourcesGroup, firmware->getCapability(Outputs));
addItems(SOURCE_TYPE_GVAR, RawSource::GVarsGroup, firmware->getCapability(Gvars));
addItems(SOURCE_TYPE_SPECIAL, RawSource::TelemGroup, SOURCE_TYPE_SPECIAL_COUNT - 1);
addItems(SOURCE_TYPE_TIMER, RawSource::TelemGroup, firmware->getCapability(Timers));
addItems(SOURCE_TYPE_TELEMETRY, RawSource::TelemGroup, firmware->getCapability(Sensors) * 3);
}
void RawSourceItemModel::setDynamicItemData(QStandardItem * item, const RawSource & src) const
{
item->setText(src.toString(modelData, generalSettings, boardType));
item->setData(src.isAvailable(modelData, generalSettings, boardType), IMDR_Available);
}
void RawSourceItemModel::addItems(const RawSourceType & type, const int group, int count, const int start)
{
const int idxAdj = (type == SOURCE_TYPE_NONE ? -1 : 0);
int first = start + count < 0 ? start + count : start + 1;
int last = start + count < 0 ? start : start + count + 1;
for (int i = first; i < last; ++i) {
const RawSource src = RawSource(type, i + idxAdj);
QStandardItem * modelItem = new QStandardItem();
modelItem->setData(src.toValue(), IMDR_Id);
modelItem->setData(type, IMDR_Type);
modelItem->setData((group | (i + idxAdj < 0 ? RawSource::NegativeGroup : i > 0 ? RawSource::PositiveGroup : 0)), IMDR_Flags);
setDynamicItemData(modelItem, src);
appendRow(modelItem);
}
}
void RawSourceItemModel::update(const int event)
{
if (doUpdate(event)) {
emit aboutToBeUpdated();
for (int i = 0; i < rowCount(); ++i)
setDynamicItemData(item(i), RawSource(item(i)->data(IMDR_Id).toInt()));
emit updateComplete();
}
}
//
// RawSwitchItemModel
//
RawSwitchItemModel::RawSwitchItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType) :
AbstractDynamicItemModel(generalSettings, modelData, firmware, board, boardType)
{
setId(IMID_RawSwitch);
setUpdateMask(IMUE_FlightModes | IMUE_LogicalSwitches | IMUE_TeleSensors | IMUE_FunctionSwitches);
// Descending switch direction: NOT (!) switches
addItems(SWITCH_TYPE_TRAINER, -1);
addItems(SWITCH_TYPE_ACT, -1);
addItems(SWITCH_TYPE_SENSOR, -firmware->getCapability(Sensors));
addItems(SWITCH_TYPE_TELEMETRY, -1);
addItems(SWITCH_TYPE_FLIGHT_MODE, -firmware->getCapability(FlightModes));
addItems(SWITCH_TYPE_VIRTUAL, -firmware->getCapability(LogicalSwitches));
addItems(SWITCH_TYPE_TRIM, -board->getCapability(Board::NumTrimSwitches));
addItems(SWITCH_TYPE_MULTIPOS_POT, -(board->getCapability(Board::MultiposPots) * board->getCapability(Board::MultiposPotsPositions)));
addItems(SWITCH_TYPE_SWITCH, -board->getCapability(Board::SwitchesPositions));
// Ascending switch direction (including zero)
addItems(SWITCH_TYPE_NONE, 1);
addItems(SWITCH_TYPE_SWITCH, board->getCapability(Board::SwitchesPositions));
addItems(SWITCH_TYPE_MULTIPOS_POT, board->getCapability(Board::MultiposPots) * board->getCapability(Board::MultiposPotsPositions));
addItems(SWITCH_TYPE_TRIM, board->getCapability(Board::NumTrimSwitches));
addItems(SWITCH_TYPE_VIRTUAL, firmware->getCapability(LogicalSwitches));
addItems(SWITCH_TYPE_FLIGHT_MODE, firmware->getCapability(FlightModes));
addItems(SWITCH_TYPE_TELEMETRY, 1);
addItems(SWITCH_TYPE_SENSOR, firmware->getCapability(Sensors));
addItems(SWITCH_TYPE_ON, 1);
addItems(SWITCH_TYPE_ONE, 1);
addItems(SWITCH_TYPE_ACT, 1);
addItems(SWITCH_TYPE_TRAINER, 1);
}
void RawSwitchItemModel::setDynamicItemData(QStandardItem * item, const RawSwitch & rsw) const
{
item->setText(rsw.toString(boardType, generalSettings, modelData));
item->setData(rsw.isAvailable(modelData, generalSettings, boardType), IMDR_Available);
}
void RawSwitchItemModel::addItems(const RawSwitchType & type, int count)
{
// Most RawSwitch() indices are one-based (vs. typical zero); these are exceptions to the rule:
const static QVector<int> rawSwitchIndexBaseZeroTypes = QVector<int>() << SWITCH_TYPE_NONE << SWITCH_TYPE_ON << SWITCH_TYPE_OFF << SWITCH_TYPE_TIMER_MODE;
// handle exceptions in RawSwitch() index values
const short rawIdxAdj = rawSwitchIndexBaseZeroTypes.contains(type) ? -1 : 0;
// determine context flags
int context = RawSwitch::AllSwitchContexts;
switch (type) {
case SWITCH_TYPE_FLIGHT_MODE:
context &= ~RawSwitch::MixesContext;
// fallthrough
case SWITCH_TYPE_VIRTUAL:
case SWITCH_TYPE_SENSOR:
context &= ~RawSwitch::GlobalFunctionsContext;
break;
case SWITCH_TYPE_ON:
case SWITCH_TYPE_ONE:
context = RawSwitch::SpecialFunctionsContext | RawSwitch::GlobalFunctionsContext;
break;
default:
break;
}
int i = (count < 0 ? count : 1);
count = (i < 0 ? 0 : count + i);
for ( ; i < count; ++i) {
RawSwitch rs(type, i + rawIdxAdj);
QStandardItem * modelItem = new QStandardItem();
modelItem->setData(rs.toValue(), IMDR_Id);
modelItem->setData(type, IMDR_Type);
if (type == SWITCH_TYPE_SWITCH) {
if (Boards::isSwitchFunc(div(abs(rs.index) - 1, 3).quot, boardType))
context &= ~RawSwitch::GlobalFunctionsContext;
else
context |= RawSwitch::GlobalFunctionsContext;
}
modelItem->setData(context, IMDR_Flags);
setDynamicItemData(modelItem, rs);
appendRow(modelItem);
}
}
void RawSwitchItemModel::update(const int event)
{
if (doUpdate(event)) {
emit aboutToBeUpdated();
for (int i = 0; i < rowCount(); ++i)
setDynamicItemData(item(i), RawSwitch(item(i)->data(IMDR_Id).toInt()));
emit updateComplete();
}
}
//
// CurveItemModel
//
CurveItemModel::CurveItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType) :
AbstractDynamicItemModel(generalSettings, modelData, firmware, board, boardType)
{
setId(IMID_Curve);
if (!modelData)
return;
setUpdateMask(IMUE_Curves);
const int count = firmware->getCapability(NumCurves);
for (int i = -count ; i <= count; ++i) {
QStandardItem * modelItem = new QStandardItem();
modelItem->setData(i, IMDR_Id);
modelItem->setData(i < 0 ? IMDG_Negative : i > 0 ? IMDG_Positive : IMDG_None, IMDR_Flags);
setDynamicItemData(modelItem, i);
appendRow(modelItem);
}
}
void CurveItemModel::setDynamicItemData(QStandardItem * item, const int value) const
{
CurveReference cr = CurveReference(CurveReference::CURVE_REF_CUSTOM, value);
item->setText(cr.toString(modelData, false));
item->setData(cr.isAvailable(), IMDR_Available);
}
void CurveItemModel::update(const int event)
{
if (doUpdate(event)) {
emit aboutToBeUpdated();
for (int i = 0; i < rowCount(); ++i)
setDynamicItemData(item(i), item(i)->data(IMDR_Id).toInt());
emit updateComplete();
}
}
//
// GVarReferenceItemModel
//
GVarReferenceItemModel::GVarReferenceItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType) :
AbstractDynamicItemModel(generalSettings, modelData, firmware, board, boardType)
{
setId(IMID_GVarRef);
if (!modelData)
return;
setUpdateMask(IMUE_GVars | IMUE_FlightModes | IMUE_LogicalSwitches);
const int count = firmware->getCapability(Gvars);
if (count > 0) {
addItems(-count);
addItems(count);
}
}
void GVarReferenceItemModel::addItems(int count)
{
int i = (count < 0 ? count : 1);
count = (i < 0 ? 0 : count + i);
for ( ; i < count; ++i) {
QStandardItem * modelItem = new QStandardItem();
AdjustmentReference ar(AdjustmentReference::ADJUST_REF_GVAR, i);
modelItem->setData(ar.toValue(), IMDR_Id);
modelItem->setData(i < 0 ? IMDG_Negative : i > 0 ? IMDG_Positive : IMDG_None, IMDR_Flags);
setDynamicItemData(modelItem, ar);
appendRow(modelItem);
}
}
void GVarReferenceItemModel::setDynamicItemData(QStandardItem * item, const AdjustmentReference & ar) const
{
item->setText(ar.toString(modelData, false));
item->setData(ar.isAvailable(), IMDR_Available);
}
void GVarReferenceItemModel::update(const int event)
{
if (doUpdate(event)) {
emit aboutToBeUpdated();
for (int i = 0; i < rowCount(); ++i)
setDynamicItemData(item(i), AdjustmentReference(item(i)->data(IMDR_Id).toInt()));
emit updateComplete();
}
}
//
// ThrottleSourceItemModel
//
ThrottleSourceItemModel::ThrottleSourceItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType) :
AbstractDynamicItemModel(generalSettings, modelData, firmware, board, boardType)
{
setId(IMID_ThrSource);
if (!modelData)
return;
setUpdateMask(IMUE_Channels);
for (int i = 0; i < modelData->thrTraceSrcCount(); i++) {
QStandardItem * modelItem = new QStandardItem();
modelItem->setData(i, IMDR_Id);
setDynamicItemData(modelItem, i);
appendRow(modelItem);
}
}
void ThrottleSourceItemModel::setDynamicItemData(QStandardItem * item, const int value) const
{
item->setText(modelData->thrTraceSrcToString(generalSettings, value));
item->setData(modelData->isThrTraceSrcAvailable(generalSettings, value), IMDR_Available);
}
void ThrottleSourceItemModel::update(const int event)
{
if (doUpdate(event)) {
emit aboutToBeUpdated();
for (int i = 0; i < rowCount(); ++i)
setDynamicItemData(item(i), item(i)->data(IMDR_Id).toInt());
emit updateComplete();
}
}
//
// CustomFuncActionItemModel
//
CustomFuncActionItemModel::CustomFuncActionItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType) :
AbstractDynamicItemModel(generalSettings, modelData, firmware, board, boardType)
{
setId(IMID_CustomFuncAction);
setUpdateMask(IMUE_All &~ (IMUE_Curves | IMUE_Scripts));
for (int i = 0; i < AssignFunc::FuncCount; i++) {
QStandardItem * modelItem = new QStandardItem();
modelItem->setData(i, IMDR_Id);
modelItem->setData(CustomFunctionData::funcContext(i), IMDR_Flags);
setDynamicItemData(modelItem, i);
appendRow(modelItem);
}
sort(0);
}
void CustomFuncActionItemModel::setDynamicItemData(QStandardItem * item, const int value) const
{
item->setText(CustomFunctionData::funcToString((AssignFunc)value, modelData));
item->setData(CustomFunctionData::isFuncAvailable(value, modelData), IMDR_Available);
}
void CustomFuncActionItemModel::update(const int event)
{
if (doUpdate(event)) {
emit aboutToBeUpdated();
for (int i = 0; i < rowCount(); ++i)
setDynamicItemData(item(i), item(i)->data(IMDR_Id).toInt());
sort(0);
emit updateComplete();
}
}
//
// CustomFuncResetParamItemModel
//
CustomFuncResetParamItemModel::CustomFuncResetParamItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType) :
AbstractDynamicItemModel(generalSettings, modelData, firmware, board, boardType)
{
setId(IMID_CustomFuncResetParam);
setUpdateMask(IMUE_TeleSensors | IMUE_Timers);
for (int i = 0; i < CustomFunctionData::resetParamCount(); i++) {
QStandardItem * modelItem = new QStandardItem();
modelItem->setData(i, IMDR_Id);
setDynamicItemData(modelItem, i);
appendRow(modelItem);
}
}
void CustomFuncResetParamItemModel::setDynamicItemData(QStandardItem * item, const int value) const
{
item->setText(CustomFunctionData::resetToString(value, modelData));
item->setData(CustomFunctionData::isResetParamAvailable(value, modelData), IMDR_Available);
}
void CustomFuncResetParamItemModel::update(const int event)
{
if (doUpdate(event)) {
emit aboutToBeUpdated();
for (int i = 0; i < rowCount(); ++i)
setDynamicItemData(item(i), item(i)->data(IMDR_Id).toInt());
emit updateComplete();
}
}
//
// TelemetrySourceItemModel
//
TelemetrySourceItemModel::TelemetrySourceItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType) :
AbstractDynamicItemModel(generalSettings, modelData, firmware, board, boardType)
{
setId(IMID_TeleSource);
if (!modelData)
return;
setUpdateMask(IMUE_TeleSensors | IMUE_Modules);
const int count = firmware->getCapability(Sensors);
for (int i = -count; i <= count; ++i) {
QStandardItem * modelItem = new QStandardItem();
modelItem->setData(i, IMDR_Id);
setDynamicItemData(modelItem, i);
appendRow(modelItem);
}
}
void TelemetrySourceItemModel::setDynamicItemData(QStandardItem * item, const int value) const
{
item->setText(SensorData::sourceToString(modelData, value));
item->setData(SensorData::isSourceAvailable(modelData, value), IMDR_Available);
int flags = value < 0 ? SensorData::SensorTypeFlagNeg : value > 0 ? SensorData::SensorTypeFlagPos : SensorData::SensorTypeFlagNone;
if (value > 0 && SensorData::isSourceVario(modelData, value))
flags |= SensorData::SensorTypeFlagVario;
item->setData(flags, IMDR_Flags);
}
void TelemetrySourceItemModel::update(const int event)
{
if (doUpdate(event)) {
emit aboutToBeUpdated();
for (int i = 0; i < rowCount(); ++i) {
setDynamicItemData(item(i), item(i)->data(IMDR_Id).toInt());
}
emit updateComplete();
}
}
//
// CurveRefTypeItemModel
//
CurveRefTypeItemModel::CurveRefTypeItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType) :
AbstractStaticItemModel(generalSettings, modelData, firmware, board, boardType)
{
setId(IMID_CurveRefType);
for (int i = 0; i <= CurveReference::MAX_CURVE_REF_TYPE; i++) {
QStandardItem * modelItem = new QStandardItem();
modelItem->setText(CurveReference::typeToString((CurveReference::CurveRefType)i));
modelItem->setData(i, IMDR_Id);
modelItem->setData(CurveReference::isTypeAvailable((CurveReference::CurveRefType)i), IMDR_Available);
appendRow(modelItem);
}
}
//
// CurveRefFuncItemModel
//
CurveRefFuncItemModel::CurveRefFuncItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType) :
AbstractStaticItemModel(generalSettings, modelData, firmware, board, boardType)
{
setId(IMID_CurveRefFunc);
for (int i = 1; i <= CurveReference::functionCount(); i++) {
QStandardItem * modelItem = new QStandardItem();
modelItem->setText(CurveReference::functionToString(i));
modelItem->setData(i, IMDR_Id);
modelItem->setData(CurveReference::isFunctionAvailable(i), IMDR_Available);
appendRow(modelItem);
}
}
//
// PrecisionItemModel
//
PrecisionItemModel::PrecisionItemModel(const int minDecimals, const int maxDecimals, const QString suffix, const bool placeholders) :
AbstractStaticItemModel()
{
for (int i = minDecimals, j = maxDecimals; j >= 0; i++, j--) {
QString str = QString("0.%1").arg(QString(i, '0'));
if (placeholders)
str.append(QString(j, '_'));
if (!suffix.isEmpty())
str.append(QString(" %1").arg(suffix));
QStandardItem * modelItem = new QStandardItem();
modelItem->setText(str);
modelItem->setData(i, IMDR_Id);
modelItem->setData(true, IMDR_Available);
appendRow(modelItem);
}
}
//
// FlexSwitchesItemModel
//
FlexSwitchesItemModel::FlexSwitchesItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType) :
AbstractDynamicItemModel(generalSettings, modelData, firmware, board, boardType)
{
setId(IMID_FlexSwitches);
if (!generalSettings)
return;
setUpdateMask(IMUE_FunctionSwitches);
const int count = Boards::getCapability(boardType, Board::Inputs);
{
QStandardItem * modelItem = new QStandardItem();
modelItem->setData(-1, IMDR_Id);
modelItem->setText(tr("None"));
modelItem->setData(true, IMDR_Available);
appendRow(modelItem);
}
for (int i = 0; i < count; ++i) {
QStandardItem * modelItem = new QStandardItem();
modelItem->setData(i, IMDR_Id);
setDynamicItemData(modelItem, i);
appendRow(modelItem);
}
}
void FlexSwitchesItemModel::setDynamicItemData(QStandardItem * item, const int value) const
{
item->setText(Boards::getInputName(value, boardType));
item->setData(generalSettings->isInputFlexSwitchAvailable(value), IMDR_Available);
}
void FlexSwitchesItemModel::update(const int event)
{
if (doUpdate(event)) {
emit aboutToBeUpdated();
for (int i = 1; i < rowCount(); ++i) { // skip None
setDynamicItemData(item(i), item(i)->data(IMDR_Id).toInt());
}
emit updateComplete();
}
}
//
// ControlSourceItemModel
//
ControlSourceItemModel::ControlSourceItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType) :
AbstractDynamicItemModel(generalSettings, modelData, firmware, board, boardType)
{
setId(IMID_ControlSource);
setUpdateMask(IMUE_Hardware);
// Descending source direction: inverted (!) sources
addItems(SOURCE_TYPE_SWITCH, -board->getCapability(Board::Switches));
addItems(SOURCE_TYPE_INPUT, -board->getCapability(Board::Inputs), -board->getCapability(Board::Sticks));
// Ascending source direction (including zero)
addItems(SOURCE_TYPE_NONE, 1);
addItems(SOURCE_TYPE_INPUT, board->getCapability(Board::Inputs), board->getCapability(Board::Sticks));
addItems(SOURCE_TYPE_SWITCH, board->getCapability(Board::Switches));
}
void ControlSourceItemModel::setDynamicItemData(QStandardItem * item, const RawSource & src) const
{
item->setText(src.toString(modelData, generalSettings, boardType));
item->setData(src.isAvailable(modelData, generalSettings, boardType, RawSource::AVAILABLE_CONTROLSRC), IMDR_Available);
}
void ControlSourceItemModel::addItems(const RawSourceType & type, int count, const int start)
{
const int idxAdj = (type == SOURCE_TYPE_NONE ? -1 : 0);
int first = start + count < 0 ? start + count : start + 1;
int last = start + count < 0 ? start : start + count + 1;
for (int i = first; i < last; ++i) {
const RawSource src = RawSource(type, i + idxAdj);
QStandardItem * modelItem = new QStandardItem();
modelItem->setData(src.toValue(), IMDR_Id);
modelItem->setData(type, IMDR_Type);
setDynamicItemData(modelItem, src);
appendRow(modelItem);
}
}
void ControlSourceItemModel::update(const int event)
{
if (doUpdate(event)) {
emit aboutToBeUpdated();
for (int i = 0; i < rowCount(); ++i)
setDynamicItemData(item(i), RawSource(item(i)->data(IMDR_Id).toInt()));
emit updateComplete();
}
}
//
// CompoundItemModelFactory
//
CompoundItemModelFactory::CompoundItemModelFactory(const GeneralSettings * const generalSettings, const ModelData * const modelData) :
generalSettings(generalSettings),
modelData(modelData)
{
firmware = getCurrentFirmware();
board = new Boards(getCurrentBoard());
boardType = getCurrentBoard();
}
CompoundItemModelFactory::~CompoundItemModelFactory()
{
unregisterItemModels();
delete board;
}
void CompoundItemModelFactory::addItemModel(const int id)
{
switch (id) {
case AbstractItemModel::IMID_RawSource:
registerItemModel(new RawSourceItemModel(generalSettings, modelData, firmware, board, boardType));
break;
case AbstractItemModel::IMID_RawSwitch:
registerItemModel(new RawSwitchItemModel(generalSettings, modelData, firmware, board, boardType));
break;
case AbstractItemModel::IMID_Curve:
registerItemModel(new CurveItemModel(generalSettings, modelData, firmware, board, boardType));
break;
case AbstractItemModel::IMID_GVarRef:
registerItemModel(new GVarReferenceItemModel(generalSettings, modelData, firmware, board, boardType));
break;
case AbstractItemModel::IMID_ThrSource:
registerItemModel(new ThrottleSourceItemModel(generalSettings, modelData, firmware, board, boardType));
break;
case AbstractItemModel::IMID_CustomFuncAction:
registerItemModel(new CustomFuncActionItemModel(generalSettings, modelData, firmware, board, boardType));
break;
case AbstractItemModel::IMID_CustomFuncResetParam:
registerItemModel(new CustomFuncResetParamItemModel(generalSettings, modelData, firmware, board, boardType));
break;
case AbstractItemModel::IMID_TeleSource:
registerItemModel(new TelemetrySourceItemModel(generalSettings, modelData, firmware, board, boardType));
break;
case AbstractItemModel::IMID_CurveRefType:
registerItemModel(new CurveRefTypeItemModel(generalSettings, modelData, firmware, board, boardType));
break;
case AbstractItemModel::IMID_CurveRefFunc:
registerItemModel(new CurveRefFuncItemModel(generalSettings, modelData, firmware, board, boardType));
break;
case AbstractItemModel::IMID_FlexSwitches:
registerItemModel(new FlexSwitchesItemModel(generalSettings, modelData, firmware, board, boardType));
break;
case AbstractItemModel::IMID_ControlSource:
registerItemModel(new ControlSourceItemModel(generalSettings, modelData, firmware, board, boardType));
break;
default:
qDebug() << "Error: unknown item model: id";
break;
}
}
int CompoundItemModelFactory::registerItemModel(AbstractItemModel * itemModel)
{
if (itemModel) {
if (!isItemModelRegistered(itemModel->getId())) {
setSourceId(itemModel);
registeredItemModels.append(itemModel);
return itemModel->getId();
}
else
return itemModel->getId();
}
return -1;
}
void CompoundItemModelFactory::unregisterItemModels()
{
foreach (AbstractItemModel *itemModel, registeredItemModels) {
delete itemModel;
}
}
void CompoundItemModelFactory::unregisterItemModel(const int id)
{
AbstractItemModel * itemModel = getItemModel(id);
if (itemModel)
delete itemModel;
}
AbstractItemModel * CompoundItemModelFactory::getItemModel(const int id) const
{
foreach (AbstractItemModel * itemModel, registeredItemModels) {
if (itemModel->getId() == id)
return itemModel;
}
return nullptr;
}
AbstractItemModel * CompoundItemModelFactory::getItemModel(const QString name) const
{
foreach (AbstractItemModel * itemModel, registeredItemModels) {
if (itemModel->getName() == name)
return itemModel;
}
return nullptr;
}
bool CompoundItemModelFactory::isItemModelRegistered(const int id) const
{
foreach (AbstractItemModel * itemModel, registeredItemModels) {
if (itemModel->getId() == id)
return true;
}
return false;
}
void CompoundItemModelFactory::update(const int event)
{
foreach (AbstractItemModel * itemModel, registeredItemModels) {
itemModel->update(event);
}
}
void CompoundItemModelFactory::dumpAllItemModelContents() const
{
foreach (AbstractItemModel * itemModel, registeredItemModels) {
AbstractItemModel::dumpItemModelContents(itemModel);
}
}
void CompoundItemModelFactory::setSourceId(AbstractItemModel * itemModel)
{
if (itemModel && itemModel->getId() == AbstractItemModel::IMID_Unknown)
itemModel->setId(AbstractItemModel::IMID_ReservedCount + registeredItemModels.count());
}
@@ -0,0 +1,401 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "rawsource.h"
#include "rawswitch.h"
#include <QStandardItemModel>
class GeneralSettings;
class ModelData;
class AdjustmentReference;
class AbstractItemModel: public QStandardItemModel
{
Q_OBJECT
public:
enum ItemModelId {
IMID_Unknown,
IMID_RawSource,
IMID_RawSwitch,
IMID_Curve,
IMID_GVarRef,
IMID_ThrSource,
IMID_CustomFuncAction,
IMID_CustomFuncResetParam,
IMID_TeleSource,
IMID_CurveRefType,
IMID_CurveRefFunc,
IMID_FlexSwitches,
IMID_ControlSource,
IMID_ReservedCount,
IMID_Custom
};
Q_ENUM(ItemModelId)
enum ItemModelDataRoles {
IMDR_Id = Qt::UserRole,
IMDR_Type,
IMDR_Flags,
IMDR_Available
};
Q_ENUM(ItemModelDataRoles)
enum ItemModelDataGroups {
IMDG_None = 0x01,
IMDG_Negative = 0x02,
IMDG_Positive = 0x04
};
Q_ENUM(ItemModelDataGroups)
enum ItemModelUpdateEvent {
IMUE_None = 0,
IMUE_SystemRefresh = 1 << 0,
IMUE_Channels = 1 << 1,
IMUE_Curves = 1 << 2,
IMUE_FlightModes = 1 << 3,
IMUE_GVars = 1 << 4,
IMUE_Inputs = 1 << 5,
IMUE_LogicalSwitches = 1 << 6,
IMUE_Scripts = 1 << 7,
IMUE_TeleSensors = 1 << 8,
IMUE_Timers = 1 << 9,
IMUE_Modules = 1 << 10,
IMUE_FunctionSwitches = 1 << 11,
IMUE_Hardware = 1 << 12,
IMUE_All = IMUE_SystemRefresh | IMUE_Channels | IMUE_Curves | IMUE_FlightModes | IMUE_GVars | IMUE_Inputs |
IMUE_LogicalSwitches | IMUE_Scripts | IMUE_TeleSensors | IMUE_Timers | IMUE_Modules | IMUE_FunctionSwitches
};
Q_ENUM(ItemModelUpdateEvent)
explicit AbstractItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType) :
QStandardItemModel(nullptr),
generalSettings(generalSettings),
modelData(modelData),
firmware(firmware),
board(board),
boardType(boardType),
m_id(IMID_Unknown),
m_name(""),
m_updateMask(IMUE_None)
{}
virtual ~AbstractItemModel() {}
void setId(int id) { m_id = id; }
int getId () const { return m_id; }
bool isReservedModelId() const { return m_id > IMID_Unknown && m_id < IMID_ReservedCount; }
void setName(QString name) { m_name = name; }
QString getName() const { return isReservedModelId() ? idToString(m_id) : m_name; }
void setUpdateMask(const int mask) { m_updateMask = mask; }
int getUpdateMask() const { return m_updateMask; }
inline bool doUpdate(const int event) const { return m_updateMask & event; }
AbstractItemModel * getItemModel(const int id) const;
static void dumpItemModelContents(AbstractItemModel * itemModel);
public slots:
virtual void update(const int event = IMUE_SystemRefresh) = 0;
protected:
const GeneralSettings * generalSettings;
const ModelData * modelData;
Firmware * firmware;
const Boards * board;
const Board::Type boardType;
private:
int m_id = IMID_Unknown;
QString m_name = "";
int m_updateMask = IMUE_None;
static QString idToString(const int value);
};
class AbstractStaticItemModel: public AbstractItemModel
{
Q_OBJECT
public:
explicit AbstractStaticItemModel(const GeneralSettings * const generalSettings = nullptr, const ModelData * const modelData = nullptr,
Firmware * firmware = nullptr, const Boards * const board = nullptr,
const Board::Type boardType = Board::BOARD_UNKNOWN) :
AbstractItemModel(generalSettings, modelData, firmware, board, boardType) {}
virtual ~AbstractStaticItemModel() {};
inline void appendToItemList(QString text, int id, bool isAvailable = true, int type = 0, int flags = 0)
{ itemList.append(new ListItem(text, id, isAvailable, type, flags)); }
void loadItemList();
public slots:
virtual void update(const int event) override final {}
protected:
struct ListItem
{
QString text;
int id;
bool isAvailable;
int type;
int flags;
ListItem() {}
ListItem(QString p_text, int p_id, bool p_isAvailable, int p_type, int p_flags) :
text(p_text), id(p_id), isAvailable(p_isAvailable), type(p_type), flags(p_flags) {}
};
QVector<ListItem *> itemList;
};
class AbstractDynamicItemModel: public AbstractItemModel
{
Q_OBJECT
public:
explicit AbstractDynamicItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType) :
AbstractItemModel(generalSettings, modelData, firmware, board, boardType) {}
virtual ~AbstractDynamicItemModel() {};
public slots:
virtual void update(const int event = IMUE_SystemRefresh) {}
signals:
void aboutToBeUpdated();
void updateComplete();
};
class RawSourceItemModel: public AbstractDynamicItemModel
{
Q_OBJECT
public:
explicit RawSourceItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType);
virtual ~RawSourceItemModel() {};
public slots:
virtual void update(const int event = IMUE_SystemRefresh) override;
protected:
virtual void setDynamicItemData(QStandardItem * item, const RawSource & src) const;
void addItems(const RawSourceType & type, const int group, int count, const int start = 0);
};
class RawSwitchItemModel: public AbstractDynamicItemModel
{
Q_OBJECT
public:
explicit RawSwitchItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType);
virtual ~RawSwitchItemModel() {};
public slots:
virtual void update(const int event = IMUE_SystemRefresh) override;
protected:
virtual void setDynamicItemData(QStandardItem * item, const RawSwitch & rsw) const;
void addItems(const RawSwitchType & type, int count);
};
class CurveItemModel: public AbstractDynamicItemModel
{
Q_OBJECT
public:
explicit CurveItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType);
virtual ~CurveItemModel() {};
public slots:
virtual void update(const int event = IMUE_SystemRefresh) override;
protected:
virtual void setDynamicItemData(QStandardItem * item, const int value) const;
};
class GVarReferenceItemModel: public AbstractDynamicItemModel
{
Q_OBJECT
public:
explicit GVarReferenceItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType);
virtual ~GVarReferenceItemModel() {};
public slots:
virtual void update(const int event = IMUE_SystemRefresh) override;
protected:
virtual void setDynamicItemData(QStandardItem * item, const AdjustmentReference & ar) const;
void addItems(int count);
};
class ThrottleSourceItemModel: public AbstractDynamicItemModel
{
Q_OBJECT
public:
explicit ThrottleSourceItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType);
virtual ~ThrottleSourceItemModel() {};
public slots:
virtual void update(const int event = IMUE_SystemRefresh) override;
protected:
virtual void setDynamicItemData(QStandardItem * item, const int value) const;
};
class CustomFuncActionItemModel: public AbstractDynamicItemModel
{
Q_OBJECT
public:
explicit CustomFuncActionItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType);
virtual ~CustomFuncActionItemModel() {};
public slots:
virtual void update(const int event = IMUE_SystemRefresh) override;
protected:
virtual void setDynamicItemData(QStandardItem * item, const int value) const;
};
class CustomFuncResetParamItemModel: public AbstractDynamicItemModel
{
Q_OBJECT
public:
explicit CustomFuncResetParamItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType);
virtual ~CustomFuncResetParamItemModel() {};
public slots:
virtual void update(const int event = IMUE_SystemRefresh) override;
protected:
virtual void setDynamicItemData(QStandardItem * item, const int value) const;
};
class TelemetrySourceItemModel: public AbstractDynamicItemModel
{
Q_OBJECT
public:
explicit TelemetrySourceItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType);
virtual ~TelemetrySourceItemModel() {};
public slots:
virtual void update(const int event = IMUE_SystemRefresh) override;
protected:
virtual void setDynamicItemData(QStandardItem * item, const int value) const;
};
class CurveRefTypeItemModel : public AbstractStaticItemModel
{
Q_OBJECT
public:
explicit CurveRefTypeItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType);
virtual ~CurveRefTypeItemModel() {};
};
class CurveRefFuncItemModel : public AbstractStaticItemModel
{
Q_OBJECT
public:
explicit CurveRefFuncItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType);
virtual ~CurveRefFuncItemModel() {};
};
class PrecisionItemModel : public AbstractStaticItemModel
{
Q_OBJECT
public:
explicit PrecisionItemModel(const int minDecimals, const int maxDecimals, const QString suffix = "", const bool placeholders = false);
virtual ~PrecisionItemModel() {};
};
class FlexSwitchesItemModel: public AbstractDynamicItemModel
{
Q_OBJECT
public:
explicit FlexSwitchesItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType);
virtual ~FlexSwitchesItemModel() {};
public slots:
virtual void update(const int event = IMUE_SystemRefresh) override;
protected:
virtual void setDynamicItemData(QStandardItem * item, const int value) const;
};
class ControlSourceItemModel: public AbstractDynamicItemModel
{
Q_OBJECT
public:
explicit ControlSourceItemModel(const GeneralSettings * const generalSettings, const ModelData * const modelData,
Firmware * firmware, const Boards * const board, const Board::Type boardType);
virtual ~ControlSourceItemModel() {};
public slots:
virtual void update(const int event = IMUE_SystemRefresh) override;
protected:
virtual void setDynamicItemData(QStandardItem * item, const RawSource & src) const;
void addItems(const RawSourceType & type, int count, const int start = 0);
};
//
// CompoundItemModelFactory
//
class CompoundItemModelFactory
{
public:
CompoundItemModelFactory(const GeneralSettings * const generalSettings, const ModelData * const modelData);
virtual ~CompoundItemModelFactory();
void addItemModel(const int id);
int registerItemModel(AbstractItemModel * itemModel);
void unregisterItemModels();
void unregisterItemModel(const int id);
bool isItemModelRegistered(const int id) const;
AbstractItemModel * getItemModel(const int id) const;
AbstractItemModel * getItemModel(const QString name) const;
void update(const int event = AbstractItemModel::IMUE_SystemRefresh);
void dumpAllItemModelContents() const;
protected:
const GeneralSettings * generalSettings;
const ModelData * modelData;
Firmware * firmware;
Boards * board;
Board::Type boardType;
QVector<AbstractItemModel *> registeredItemModels;
private:
void setSourceId(AbstractItemModel * itemModel);
};
@@ -0,0 +1,251 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "filtereditemmodels.h"
FilteredItemModel::FilteredItemModel(AbstractItemModel * sourceModel, int flags) :
QSortFilterProxyModel(nullptr),
filterFlags(0),
m_id(0),
m_name("")
{
setFilterRole(AbstractItemModel::IMDR_Available);
setFilterKeyColumn(0);
setFilterFlags(flags);
setDynamicSortFilter(true);
setSourceModel(sourceModel);
AbstractDynamicItemModel * itemModel = qobject_cast<AbstractDynamicItemModel *>(sourceModel);
if (itemModel) {
connect(itemModel, &AbstractDynamicItemModel::aboutToBeUpdated, this, &FilteredItemModel::onAboutToBeUpdated);
connect(itemModel, &AbstractDynamicItemModel::updateComplete, this, &FilteredItemModel::onUpdateComplete);
}
}
void FilteredItemModel::setFilterFlags(int flags)
{
if (filterFlags != flags) {
filterFlags = flags;
invalidateFilter();
}
}
bool FilteredItemModel::filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const
{
const QModelIndex & srcIdx = sourceModel()->index(sourceRow, 0, sourceParent);
if (!srcIdx.isValid() || !sourceModel()->data(srcIdx, filterRole()).toBool())
return false;
if (!filterFlags)
return true;
bool ok;
const int flags = sourceModel()->data(srcIdx, AbstractItemModel::IMDR_Flags).toInt(&ok);
return (ok && (!flags || (filterFlags & flags)));
}
void FilteredItemModel::update() const
{
AbstractDynamicItemModel * itemModel = qobject_cast<AbstractDynamicItemModel *>(sourceModel());
if (itemModel)
itemModel->update();
}
void FilteredItemModel::onAboutToBeUpdated()
{
emit aboutToBeUpdated();
}
void FilteredItemModel::onUpdateComplete()
{
emit updateComplete();
}
// static
void FilteredItemModel::dumpItemModelContents(FilteredItemModel * itemModel)
{
if (itemModel) {
qDebug() << "id:" << itemModel->getId() << "name:" << itemModel->getName();
for (int i = 0; i < itemModel->rowCount(); ++i) {
qDebug() << "row:" << i
<< "text:" << itemModel->data(itemModel->index(i, 0), Qt::DisplayRole).toString()
<< "id:" << itemModel->data(itemModel->index(i, 0), AbstractItemModel::IMDR_Id).toInt()
<< "avail:" << itemModel->data(itemModel->index(i, 0), AbstractItemModel::IMDR_Available).toBool()
<< "flags:" << itemModel->data(itemModel->index(i, 0), AbstractItemModel::IMDR_Flags).toInt()
<< "type:" << itemModel->data(itemModel->index(i, 0), AbstractItemModel::IMDR_Type).toInt();
}
}
else
qDebug() << "Error: model not of class FilteredItemModel";
}
//
// FilteredItemModelFactory
//
FilteredItemModelFactory::FilteredItemModelFactory()
{
}
FilteredItemModelFactory::~FilteredItemModelFactory()
{
unregisterItemModels();
}
int FilteredItemModelFactory::registerItemModel(FilteredItemModel * itemModel, const QString name, const int id)
{
if (itemModel) {
if (isItemModelRegistered(name)) {
qDebug() << "Error: item model already registered with name:" << name;
return 0;
}
else if (id > -1 && isItemModelRegistered(id)) {
qDebug() << "Error: item model already registered with id:" << id;
return 0;
}
else {
int fid = id;
if (fid < 0) {
fid = registeredItemModels.count() + 1;
AbstractItemModel * mdl = qobject_cast<AbstractItemModel *>(itemModel->sourceModel());
if (mdl) {
fid += mdl->getId() * 128;
}
}
itemModel->setId(fid);
itemModel->setName(name);
registeredItemModels.append(itemModel);
return fid;
}
}
qDebug() << "Error: invalid filtered item model pointer";
return 0;
}
void FilteredItemModelFactory::unregisterItemModels()
{
foreach (FilteredItemModel * itemModel, registeredItemModels) {
delete itemModel;
}
}
void FilteredItemModelFactory::unregisterItemModel(const int id)
{
FilteredItemModel * itemModel = getItemModel(id);
if (itemModel)
delete itemModel;
}
bool FilteredItemModelFactory::isItemModelRegistered(const int id) const
{
return getItemModel(id);
}
bool FilteredItemModelFactory::isItemModelRegistered(const QString name) const
{
return getItemModel(name);
}
FilteredItemModel * FilteredItemModelFactory::getItemModel(const int id) const
{
foreach (FilteredItemModel * itemModel, registeredItemModels) {
if (itemModel->getId() == id)
return itemModel;
}
return nullptr;
}
FilteredItemModel * FilteredItemModelFactory::getItemModel(const QString name) const
{
foreach (FilteredItemModel * itemModel, registeredItemModels) {
if (itemModel->getName() == name)
return itemModel;
}
return nullptr;
}
void FilteredItemModelFactory::update()
{
foreach (FilteredItemModel * itemModel, registeredItemModels) {
itemModel->update();
}
}
void FilteredItemModelFactory::dumpItemModelContents(const int id) const
{
FilteredItemModel::dumpItemModelContents(getItemModel(id));
}
void FilteredItemModelFactory::dumpItemModelContents(const QString name) const
{
FilteredItemModel::dumpItemModelContents(getItemModel(name));
}
void FilteredItemModelFactory::dumpAllItemModelContents() const
{
foreach (FilteredItemModel * itemModel, registeredItemModels) {
FilteredItemModel::dumpItemModelContents(itemModel);
}
}
//
// CurveRefFilteredFactory
//
CurveRefFilteredFactory::CurveRefFilteredFactory(CompoundItemModelFactory * sharedItemModels, const int curveFlags, const int gvarRefFlags)
{
if (!sharedItemModels) {
qDebug() << "Error: invalid compound item model factory pointer";
return;
}
registerItemModel(new FilteredItemModel(sharedItemModels->getItemModel(AbstractItemModel::IMID_Curve), curveFlags),
fidToString(CRFIM_CURVE), CRFIM_CURVE);
registerItemModel(new FilteredItemModel(sharedItemModels->getItemModel(AbstractItemModel::IMID_CurveRefType)),
fidToString(CRFIM_TYPE), CRFIM_TYPE);
registerItemModel(new FilteredItemModel(sharedItemModels->getItemModel(AbstractItemModel::IMID_CurveRefFunc)),
fidToString(CRFIM_FUNC), CRFIM_FUNC);
}
CurveRefFilteredFactory::~CurveRefFilteredFactory()
{
}
// static
QString CurveRefFilteredFactory::fidToString(const int value)
{
switch(value) {
case CRFIM_CURVE:
return "Curves";
case CRFIM_GVARREF:
return "Global Variables";
case CRFIM_TYPE:
return "Types";
case CRFIM_FUNC:
return "Functions";
default:
return CPN_STR_UNKNOWN_ITEM;
}
}
@@ -0,0 +1,116 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef FILTEREDITEMMODELS_H
#define FILTEREDITEMMODELS_H
#include "compounditemmodels.h"
#include <QObject>
#include <QSortFilterProxyModel>
#include <QString>
class FilteredItemModel: public QSortFilterProxyModel
{
Q_OBJECT
public:
enum DataFilters {
AllFilter = AbstractItemModel::IMDG_Negative | AbstractItemModel::IMDG_None | AbstractItemModel::IMDG_Positive,
AllExcludeNoneFilter = AllFilter &~ AbstractItemModel::IMDG_None,
NegativeFilter = AbstractItemModel::IMDG_Negative | AbstractItemModel::IMDG_None,
NegativeExcludeNoneFilter = AbstractItemModel::IMDG_Negative,
PositiveFilter = AbstractItemModel::IMDG_Positive | AbstractItemModel::IMDG_None,
PositiveExcludeNoneFilter = AbstractItemModel::IMDG_Positive
};
Q_ENUM(DataFilters)
explicit FilteredItemModel(AbstractItemModel * sourceModel, int flags);
explicit FilteredItemModel(AbstractItemModel * sourceModel) :
FilteredItemModel(sourceModel, 0) {}
virtual ~FilteredItemModel() {};
void setId(int id) { m_id = id; }
int getId() const { return m_id; };
void setName(QString name) { m_name = name; }
QString getName() const { return m_name; }
static void dumpItemModelContents(FilteredItemModel * itemModel);
public slots:
void setFilterFlags(int flags);
void update() const;
void onAboutToBeUpdated();
void onUpdateComplete();
signals:
void aboutToBeUpdated();
void updateComplete();
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const override;
int filterFlags;
private:
int m_id = 0;
QString m_name = "";
};
class FilteredItemModelFactory
{
public:
explicit FilteredItemModelFactory();
virtual ~FilteredItemModelFactory();
int registerItemModel(FilteredItemModel * itemModel, const QString name, const int id = -1);
void unregisterItemModels();
void unregisterItemModel(const int id);
bool isItemModelRegistered(const int id) const;
bool isItemModelRegistered(const QString name) const;
FilteredItemModel * getItemModel(const int id) const;
FilteredItemModel * getItemModel(const QString name) const;
void update();
void dumpItemModelContents(const int id) const;
void dumpItemModelContents(const QString name) const;
void dumpAllItemModelContents() const;
protected:
QVector<FilteredItemModel *> registeredItemModels;
};
class CurveRefFilteredFactory : public FilteredItemModelFactory
{
public:
enum FilteredItemModelId {
CRFIM_CURVE,
CRFIM_GVARREF,
CRFIM_TYPE,
CRFIM_FUNC
};
explicit CurveRefFilteredFactory(CompoundItemModelFactory * sharedItemModels, const int curveFlags = 0, const int gvarRefFlags = 0);
virtual ~CurveRefFilteredFactory();
static QString fidToString(const int value);
};
#endif // FILTEREDITEMMODELS_H
+517
View File
@@ -0,0 +1,517 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "filesyncdialog.h"
#include "autobitsetcheckbox.h"
#include "autocheckbox.h"
#include "autocombobox.h"
#include "autolineedit.h"
#include "constants.h"
#include "helpers.h"
#include "progresswidget.h"
#include <QApplication>
#include <QComboBox>
#include <QCompleter>
#include <QCheckBox>
#include <QDir>
#include <QFileDialog>
#include <QFileSystemModel>
#include <QLayout>
#include <QLineEdit>
#include <QMenu>
#include <QSpinBox>
#include <QMessageBox>
#include <QPushButton>
FileSyncDialog::FileSyncDialog(QWidget * parent, const SyncProcess::SyncOptions & syncOptions) :
QDialog(parent),
m_syncOptions(syncOptions),
m_running(false)
{
setWindowTitle(tr("Synchronize Files"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setSizeGripEnabled(true);
setupUi();
toggleExtraOptions(false);
validate();
updateRunningState();
}
void FileSyncDialog::reject()
{
// override close event via window system close or esc. key
if (m_running) {
if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Are you sure you wish to abort the sync?")) == QMessageBox::No)
return;
emit stopSync();
// Force termination if the sync hasn't finished within 5s
QElapsedTimer tim;
tim.start();
while (!tim.hasExpired(5000) && m_running)
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 10);
if (m_running)
qWarning() << "Terminating thread while Sync process was still running.";
}
QDialog::reject();
}
// Create a widget with a line edit and folder select button and handles all interactions. Features autosuggest
// path hints while typing, invalid paths shown in red.
// This should probably be moved some place more reusable, esp. the QFileSystemModel.
static QWidget * folderSelectorWidget(QLineEdit * le, QWidget * parent)
{
static QFileSystemModel fileModel;
static bool init = false;
if (!init) {
init = true;
fileModel.setFilter(QDir::Dirs);
fileModel.setRootPath("/");
}
QWidget * fsw = new QWidget(parent);
//le = new QLineEdit(parent);
QCompleter * fsc = new QCompleter(fsw);
fsc->setModel(&fileModel);
//fsc->setCompletionMode(QCompleter::InlineCompletion);
le->setCompleter(fsc);
QToolButton * btn = new QToolButton(fsw);
btn->setIcon(CompanionIcon("open.png"));
QHBoxLayout * l = new QHBoxLayout(fsw);
l->setContentsMargins(0,0,0,0);
l->setSpacing(3);
l->addWidget(le);
l->addWidget(btn);
QObject::connect(btn, &QToolButton::clicked, [=]() {
QString dir = QFileDialog::getExistingDirectory(parent, fsw->property("fileDialogTitle").toString(), le->text());
if (!dir.isEmpty()) {
le->setText(QDir::toNativeSeparators(dir));
le->setFocus();
}
});
QObject::connect(le, &QLineEdit::textChanged, [=](const QString & text) {
if (QFile::exists(text))
le->setStyleSheet("");
else
le->setStyleSheet("QLineEdit {color: red;}");
});
return fsw;
}
void FileSyncDialog::setupUi()
{
m_folderNameA = tr("Folder A");
m_folderNameB = tr("Folder B");
int row = 0;
CompanionIcon playIcon("play.png");
playIcon.addImage("stop.png", QIcon::Normal, QIcon::On);
QLabel * lblFolderA = new QLabel(m_folderNameA % ":", this);
AutoLineEdit * leFolderA = new AutoLineEdit(this, true);
leFolderA->setField(m_syncOptions.folderA);
leFolderA->setText(QDir::toNativeSeparators(m_syncOptions.folderA));
QWidget * wdgFolderA = folderSelectorWidget(leFolderA, this);
QLabel * lblFolderB = new QLabel(m_folderNameB % ":", this);
AutoLineEdit * leFolderB = new AutoLineEdit(this, true);
leFolderB->setField(m_syncOptions.folderB);
leFolderB->setText(QDir::toNativeSeparators(m_syncOptions.folderB));
QWidget * wdgFolderB = folderSelectorWidget(leFolderB, this);
ui_syncDir = new AutoComboBox(this);
setSyncDirList();
ui_syncDir->setField(m_syncOptions.direction);
AutoComboBox * copyMode = new AutoComboBox(this);
copyMode->setToolTip(tr("How to handle overwriting files which already exist in the destination folder."));
copyMode->addItem(tr("Copy only if newer and different (compare contents)"), SyncProcess::OVERWR_NEWER_IF_DIFF);
copyMode->addItem(tr("Copy only if newer (do not compare contents)"), SyncProcess::OVERWR_NEWER_ALWAYS);
copyMode->addItem(tr("Copy only if different (ignore file time stamps)"), SyncProcess::OVERWR_IF_DIFF);
copyMode->addItem(tr("Always copy (force overwite existing files)"), SyncProcess::OVERWR_ALWAYS);
copyMode->setField(m_syncOptions.compareType);
QSpinBox * maxSize = new QSpinBox(this);
maxSize->setRange(0, 100 * 1024);
maxSize->setAccelerated(true);
maxSize->setSpecialValueText(tr("Any size"));
maxSize->setToolTip(tr("Skip files larger than this size. Enter zero for unlimited."));
maxSize->setGroupSeparatorShown(true);
maxSize->setValue(0); // we set the real default value later
AutoComboBox * logLevel = new AutoComboBox(this);
logLevel->setToolTip(tr("Minimum reporting level. Events of this type and of higher importance are shown.\n" \
"WARNING: High log rates may make the user interface temporarily unresponsive."));
logLevel->addItem(tr("Skipped"), QtDebugMsg);
logLevel->addItem(tr("Created"), QtInfoMsg);
logLevel->addItem(tr("Updated"), QtWarningMsg);
//logLevel->addItem(tr("Item Deleted"), QtCriticalMsg); // unused
logLevel->addItem(tr("Errors Only"), QtFatalMsg);
logLevel->setField(m_syncOptions.logLevel);
AutoBitsetCheckBox * testRun = new AutoBitsetCheckBox(m_syncOptions.flags, SyncProcess::OPT_DRY_RUN, tr("Test-run only"), this);
testRun->setToolTip(tr("Run as normal but do not actually copy anything. Useful for verifying results before real sync."));
// layout to hold size spinbox and checkbox option(s)
QHBoxLayout * hlay1 = new QHBoxLayout();
hlay1->addWidget(maxSize, 1);
hlay1->addWidget(testRun);
hlay1->addWidget(new QLabel(tr("Log Level:"), this));
hlay1->addWidget(logLevel);
// "extra" options
QLabel * lbFilter = new QLabel(tr("Filters:"), this);
lbFilter->setToolTip(tr("The \"Include\" filter will only copy files which match the pattern(s).\n" \
"The \"Exclude\" filter will skip files matching the filter pattern(s).\n" \
"The Include filter is evaluated first."));
AutoLineEdit * leFiltExc = new AutoLineEdit(this, true);
leFiltExc->setField(m_syncOptions.excludeFilter);
leFiltExc->setToolTip(tr("One or more file pattern(s) to exclude, separated by commas.\n" "Blank means exclude none. ?, *, and [...] wildcards accepted."));
AutoLineEdit * leFiltInc = new AutoLineEdit(this, true);
leFiltInc->setField(m_syncOptions.includeFilter);
leFiltInc->setToolTip(tr("One or more file pattern(s) to include, separated by commas.\n" "Blank means include all. ?, *, and [...] wildcards accepted."));
// layout to hold filter options
QHBoxLayout * filtLo = new QHBoxLayout();
filtLo->addWidget(new QLabel(tr("Include:"), this));
filtLo->addWidget(leFiltInc, 1);
filtLo->addWidget(new QLabel(tr("Exclude:"), this));
filtLo->addWidget(leFiltExc, 1);
// file options
QHBoxLayout * filOptsLo = new QHBoxLayout();
filOptsLo->addWidget(new AutoBitsetCheckBox(m_syncOptions.dirFilterFlags, QDir::CaseSensitive, tr("Case sensitive"), this));
filOptsLo->addWidget(new AutoBitsetCheckBox(m_syncOptions.dirFilterFlags, QDir::NoSymLinks, true, tr("Follow links"), this));
filOptsLo->addWidget(new AutoBitsetCheckBox(m_syncOptions.dirFilterFlags, QDir::Hidden, tr("Include hidden"), this));
// folder options
QHBoxLayout * dirOptsLo = new QHBoxLayout();
dirOptsLo->addWidget(new AutoBitsetCheckBox(m_syncOptions.flags, SyncProcess::OPT_RECURSIVE, tr("Recursive"), this));
dirOptsLo->addWidget(new AutoBitsetCheckBox(m_syncOptions.flags, SyncProcess::OPT_SKIP_DIR_LINKS, true, tr("Follow links"), this));
dirOptsLo->addWidget(new AutoBitsetCheckBox(m_syncOptions.dirFilterFlags, QDir::Dirs, true, tr("Skip empty"), this));
dirOptsLo->addWidget(new AutoBitsetCheckBox(m_syncOptions.dirFilterFlags, QDir::AllDirs, true, tr("Apply filters"), this));
// extra options container widget and layout
ui_extraOptions = new QWidget(this);
ui_extraOptions->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
QGridLayout * xOptsLay = new QGridLayout(ui_extraOptions);
xOptsLay->setContentsMargins(0, 0, 0, 0);
xOptsLay->addWidget(lbFilter, row, 0);
xOptsLay->addLayout(filtLo, row++, 1);
xOptsLay->addWidget(new QLabel(tr("Filter Options:"), this), row, 0);
xOptsLay->addLayout(filOptsLo, row++, 1);
xOptsLay->addWidget(new QLabel(tr("Folder Options:"), this), row, 0);
xOptsLay->addLayout(dirOptsLo, row++, 1);
// status label
ui_statusLabel = new QLabel(this);
// progress widget
ui_progress = new ProgressWidget(this);
ui_progress->setMinimumWidth(0);
ui_progress->setVisible(false);
// dialog buttons & menu
QMenu * optionsMenu = new QMenu(tr("Options"));
QAction * xtrasAct = optionsMenu->addAction(CompanionIcon("fuses.png"), tr("Show extra options"), this, &FileSyncDialog::toggleExtraOptions);
xtrasAct->setCheckable(true);
connect(this, &FileSyncDialog::extraOptionsToggled, xtrasAct, &QAction::setChecked);
optionsMenu->addAction(CompanionIcon("paintbrush.png"), tr("Reset to defaults"), this, &FileSyncDialog::resetOptions);
QToolButton * btnOpts = new QToolButton(this);
btnOpts->setDefaultAction(xtrasAct);
btnOpts->setMenu(optionsMenu);
btnOpts->setToolButtonStyle(Qt::ToolButtonIconOnly);
btnOpts->setPopupMode(QToolButton::MenuButtonPopup);
btnOpts->setToolTip(xtrasAct->text());
ui_btnStartStop = new QToolButton(this);
ui_btnStartStop->setCheckable(true);
ui_btnStartStop->setIcon(playIcon);
ui_btnStartStop->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
ui_btnClose = new QToolButton(this);
ui_btnClose->setText(tr("Close"));
ui_btnClose->setToolButtonStyle(Qt::ToolButtonTextOnly);
ui_btnClose->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
ui_buttonBox = new QDialogButtonBox(this);
ui_buttonBox->addButton(btnOpts, QDialogButtonBox::ActionRole);
ui_buttonBox->addButton(ui_btnStartStop, QDialogButtonBox::ActionRole);
ui_buttonBox->addButton(ui_btnClose, QDialogButtonBox::RejectRole);
// The options group box.
ui_optionsPanel = new QGroupBox(tr("Options"), this);
QGridLayout * optsLay = new QGridLayout(ui_optionsPanel);
row = 0;
optsLay->addWidget(lblFolderA, row, 0);
optsLay->addWidget(wdgFolderA, row++, 1);
optsLay->addWidget(lblFolderB, row, 0);
optsLay->addWidget(wdgFolderB, row++, 1);
optsLay->addWidget(new QLabel(tr("Sync. Direction:"), this), row, 0);
optsLay->addWidget(ui_syncDir, row++, 1);
optsLay->addWidget(new QLabel(tr("Existing Files:"), this), row, 0);
optsLay->addWidget(copyMode, row++, 1);
optsLay->addWidget(new QLabel(tr("Max. File Size:"), this), row, 0);
optsLay->addLayout(hlay1, row++, 1);
optsLay->addWidget(ui_extraOptions, row, 0, 1, 2);
// layout to hold warning label and button box
QHBoxLayout * hlay2 = new QHBoxLayout();
hlay2->addWidget(ui_statusLabel, 1);
hlay2->addWidget(ui_buttonBox);
// Create main layout and add options, progress, buttons.
QVBoxLayout * dlgL = new QVBoxLayout(this);
dlgL->addWidget(ui_optionsPanel, 0, Qt::AlignTop);
dlgL->addLayout(hlay2);
dlgL->addWidget(ui_progress, 1);
// Connect all signal handlers
// folder A path
connect(leFolderA, &AutoLineEdit::textChanged, this, &FileSyncDialog::validate);
// folder B path
connect(leFolderB, &AutoLineEdit::textChanged, this, &FileSyncDialog::validate);
// function to dis/enable the OVERWR_ALWAYS option depending on sync direction
connect(ui_syncDir, &AutoComboBox::currentDataChanged, [=](int dir) {
int idx = copyMode->findData(SyncProcess::OVERWR_ALWAYS);
int flg = (dir == SyncProcess::SYNC_A2B || dir == SyncProcess::SYNC_B2A) ? 33 : 0;
if (!flg && idx == copyMode->currentIndex())
copyMode->setCurrentIndex(copyMode->findData(SyncProcess::OVERWR_NEWER_IF_DIFF));
copyMode->setItemData(idx, flg, Qt::UserRole - 1);
});
// function to set magnitude of file size spinbox, KB or MB
connect(maxSize, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), [=](int value) {
const QSignalBlocker blocker(maxSize);
int multi = maxSize->property("multi").isValid() ? maxSize->property("multi").toInt() : 0;
if (value >= 10 * 1024 && multi != 1024 * 1024) {
// KB -> MB
multi = 1024 * 1024;
maxSize->setValue(value / 1024);
maxSize->setMaximum(100);
maxSize->setSingleStep(1);
maxSize->setSuffix(tr(" MB"));
}
else if ((value < 10 && multi != 1024) || !multi) {
// MB -> KB
if (multi)
value *= 1024;
multi = 1024;
if (value == 9 * 1024)
value += 1024 - 32; // avoid large jump when stepping from 10MB to 10,208KB
maxSize->setMaximum(100 * 1024);
maxSize->setValue(value);
maxSize->setSingleStep(32);
maxSize->setSuffix(tr(" KB"));
}
maxSize->setProperty("multi", multi);
m_syncOptions.maxFileSize = value * multi;
});
// we set these after connections are made because we want the signal processors to run
ui_syncDir->setField(m_syncOptions.direction);
maxSize->setValue(m_syncOptions.maxFileSize / 1024);
// connect Reset action to all fields
for (AutoLineEdit * a : ui_optionsPanel->findChildren<AutoLineEdit *>())
connect(this, &FileSyncDialog::optionsWereReset, a, &AutoLineEdit::updateValue);
for (AutoComboBox * a : ui_optionsPanel->findChildren<AutoComboBox *>())
connect(this, &FileSyncDialog::optionsWereReset, a, &AutoComboBox::updateValue);
for (AutoBitsetCheckBox * a : ui_optionsPanel->findChildren<AutoBitsetCheckBox *>())
connect(this, &FileSyncDialog::optionsWereReset, a, &AutoBitsetCheckBox::updateValue);
connect(this, &FileSyncDialog::optionsWereReset, [=]() { maxSize->setValue(m_syncOptions.maxFileSize / 1024); });
// React to name changes for folder path labels
connect(this, &FileSyncDialog::folderNameAChanged, [=](const QString &text) {
lblFolderA->setText(text % ":");
wdgFolderA->setProperty("fileDialogTitle", text);
setSyncDirList();
});
connect(this, &FileSyncDialog::folderNameBChanged, [=](const QString &text) {
lblFolderB->setText(text % ":");
wdgFolderB->setProperty("fileDialogTitle", text);
setSyncDirList();
});
// button actions
connect(ui_btnStartStop, &QPushButton::clicked, this, &FileSyncDialog::toggleSync);
connect(ui_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(ui_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(ui_progress, &ProgressWidget::detailsToggled, this, &FileSyncDialog::adjustSizeDelayed);
}
void FileSyncDialog::toggleSync(bool)
{
if (m_running) {
setCursor(Qt::WaitCursor);
emit stopSync();
return;
}
ui_progress->clearDetails();
ui_progress->setInfo("");
SyncProcess * syncProcess = new SyncProcess(m_syncOptions);
// move sync process to separate thread, we only use signals/slots from here on...
QThread * syncThread = new QThread(this);
syncProcess->moveToThread(syncThread);
// ...and quite a few of them!
connect(this, &FileSyncDialog::startSync, syncProcess, &SyncProcess::run);
connect(this, &FileSyncDialog::stopSync, syncProcess, &SyncProcess::stop);
connect(syncProcess, &SyncProcess::started, this, &FileSyncDialog::onSyncStarted);
connect(syncProcess, &SyncProcess::finished, this, &FileSyncDialog::onSyncFinished);
connect(syncProcess, &SyncProcess::statusMessage, this, &FileSyncDialog::setStatusText);
connect(syncProcess, &SyncProcess::statusUpdate, this, &FileSyncDialog::onStatusUpdate);
connect(syncProcess, &SyncProcess::fileCountChanged, ui_progress, &ProgressWidget::setMaximum);
connect(syncProcess, &SyncProcess::progressMessage, ui_progress, &ProgressWidget::addMessage);
connect(syncProcess, &SyncProcess::finished, syncThread, &QThread::quit);
connect(syncThread, &QThread::finished, syncThread, &QThread::deleteLater);
connect(syncThread, &QThread::destroyed, syncProcess, &SyncProcess::deleteLater);
// go
syncThread->start(QThread::LowPriority);
emit startSync();
}
void FileSyncDialog::onSyncStarted()
{
m_running = true;
updateRunningState();
}
void FileSyncDialog::onSyncFinished()
{
m_running = false;
updateRunningState();
if (parentWidget())
QApplication::alert(parentWidget());
}
void FileSyncDialog::updateRunningState()
{
ui_btnStartStop->setChecked(m_running);
ui_optionsPanel->setEnabled(!m_running);
ui_btnClose->setEnabled(!m_running);
ui_btnStartStop->setText(m_running ? tr("Abort") : tr("Start"));
if (m_running) {
ui_progress->setVisible(true); // don't hide after run
}
else {
setCursor(Qt::ArrowCursor); // in case we set a wait cursor upon abort request
}
}
void FileSyncDialog::onStatusUpdate(const SyncProcess::SyncStatus & stat)
{
static const QString reportTemplate = tr("Total: <b>%1</b>; Created: <b>%2</b>; Updated: <b>%3</b>; Skipped: <b>%4</b>; Errors: <font color=%6><b>%5</b></font>;");
ui_progress->setInfo((stat.index < stat.count ? tr("Current: <b>%1</b> of ").arg(stat.index) : "") % reportTemplate.arg(stat.count).arg(stat.created).arg(stat.updated).arg(stat.skipped).arg(stat.errored).arg(stat.errored ? "red" : "black"));
ui_progress->setValue(stat.index);
}
void FileSyncDialog::setSyncOptions(const SyncProcess::SyncOptions & syncOptions)
{
m_syncOptions = syncOptions;
emit optionsWereReset();
}
void FileSyncDialog::resetOptions()
{
m_syncOptions.reset();
emit optionsWereReset();
}
void FileSyncDialog::toggleExtraOptions(bool show)
{
ui_extraOptions->setVisible(show);
if (!ui_progress->detailsVisible())
adjustSizeDelayed();
m_extraOptionsVisible = show;
emit extraOptionsToggled(show);
}
void FileSyncDialog::setFolderNameA(const QString & name)
{
if (m_folderNameA != name) {
m_folderNameA = name;
emit folderNameAChanged(name);
}
}
void FileSyncDialog::setFolderNameB(const QString & name)
{
if (m_folderNameB != name) {
m_folderNameB = name;
emit folderNameBChanged(name);
}
}
void FileSyncDialog::setStatusText(const QString & text, int type)
{
ui_statusLabel->setText(text);
ui_statusLabel->setStyleSheet(type != QtInfoMsg && type >= QtWarningMsg ? "QLabel { color: red; }:" : "");
}
bool FileSyncDialog::validate()
{
QString msg;
if (m_syncOptions.folderA.isEmpty() || !QFileInfo(m_syncOptions.folderA).exists())
msg = tr("%1 not found.").arg(m_folderNameA);
else if (m_syncOptions.folderB.isEmpty() || !QFileInfo(m_syncOptions.folderB).exists())
msg = tr("%1 not found.").arg(m_folderNameB);
else if (QFileInfo(m_syncOptions.folderA).canonicalFilePath() == QFileInfo(m_syncOptions.folderB).canonicalFilePath())
msg = tr("Folders are the same.");
setStatusText(msg, QtCriticalMsg);
ui_btnStartStop->setEnabled(msg.isEmpty());
return msg.isEmpty();
}
void FileSyncDialog::adjustSizeDelayed()
{
QTimer::singleShot(0, [=]() { adjustSize(); });
}
void FileSyncDialog::setSyncDirList()
{
const QString upArw = CPN_STR_SW_INDICATOR_UP % " ";
const QString downArw = CPN_STR_SW_INDICATOR_DN % " ";
ui_syncDir->clear();
ui_syncDir->addItem(tr("%1%2 Both directions, to %3 first").arg(downArw, upArw, m_folderNameB), SyncProcess::SYNC_A2B_B2A);
ui_syncDir->addItem(tr("%1%2 Both directions, to %3 first").arg(upArw, downArw, m_folderNameA), SyncProcess::SYNC_B2A_A2B);
ui_syncDir->addItem(tr(" %1 Only from %2 to %3").arg(downArw, m_folderNameA, m_folderNameB), SyncProcess::SYNC_A2B);
ui_syncDir->addItem(tr(" %1 Only from %2 to %3").arg(upArw, m_folderNameB, m_folderNameA), SyncProcess::SYNC_B2A);
ui_syncDir->updateValue();
}
+97
View File
@@ -0,0 +1,97 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "process_sync.h"
#include <QDialog>
#include <QDialogButtonBox>
#include <QGroupBox>
#include <QLabel>
#include <QToolButton>
class ProgressWidget;
class AutoComboBox;
class FileSyncDialog : public QDialog
{
Q_OBJECT
Q_PROPERTY(SyncProcess::SyncOptions syncOptions READ syncOptions WRITE setSyncOptions RESET resetOptions NOTIFY optionsWereReset)
Q_PROPERTY(bool extraOptionsVisible READ extraOptionsVisible WRITE toggleExtraOptions NOTIFY extraOptionsToggled)
Q_PROPERTY(QString folderNameA READ folderNameA WRITE setFolderNameA NOTIFY folderNameAChanged)
Q_PROPERTY(QString folderNameB READ folderNameB WRITE setFolderNameB NOTIFY folderNameBChanged)
public:
FileSyncDialog(QWidget * parent = nullptr, const SyncProcess::SyncOptions & syncOptions = SyncProcess::SyncOptions());
inline SyncProcess::SyncOptions syncOptions() const { return m_syncOptions; }
void setSyncOptions(const SyncProcess::SyncOptions & syncOptions);
void resetOptions();
inline bool extraOptionsVisible() const { return m_extraOptionsVisible; }
void toggleExtraOptions(bool show);
inline QString folderNameA() const { return m_folderNameA; }
void setFolderNameA(const QString & name);
inline QString folderNameB() const { return m_folderNameB; }
void setFolderNameB(const QString & name);
public slots:
virtual void reject() override;
void setStatusText(const QString & text, int type = QtWarningMsg);
bool validate();
signals:
void startSync();
void stopSync();
void folderNameAChanged(const QString & name);
void folderNameBChanged(const QString & name);
void extraOptionsToggled(bool show);
void optionsWereReset();
private slots:
void setupUi();
void toggleSync(bool);
void onSyncStarted();
void onSyncFinished();
void updateRunningState();
void onStatusUpdate(const SyncProcess::SyncStatus & stat);
void adjustSizeDelayed();
void setSyncDirList();
private:
SyncProcess::SyncOptions m_syncOptions;
QString m_folderNameA;
QString m_folderNameB;
bool m_running;
bool m_extraOptionsVisible;
QGroupBox * ui_optionsPanel;
QWidget * ui_extraOptions;
ProgressWidget * ui_progress;
QDialogButtonBox * ui_buttonBox;
QToolButton * ui_btnStartStop;
QToolButton * ui_btnClose;
QLabel * ui_statusLabel;
AutoComboBox * ui_syncDir;
};
+75
View File
@@ -0,0 +1,75 @@
project(firmwares)
set(${PROJECT_NAME}_NAMES
adjustmentreference
boardfactories
boardjson
boards
curvedata
curvereference
customfunctiondata
customisation_data
datahelpers
eeprominterface
generalsettings
gvardata
input_data
flightmodedata
heli_data
logicalswitchdata
mixdata
modeldata
moduledata
multiprotocols
output_data
radiodata
radiodataconversionstate
rawsource
rawswitch
sensordata
sourcenumref
telem_data
timerdata
opentx/opentxinterface
edgetx/edgetxinterface
edgetx/yaml_calibdata
edgetx/yaml_curvedata
edgetx/yaml_customfunctiondata
edgetx/yaml_expodata
edgetx/yaml_generalsettings
edgetx/yaml_logicalswitchdata
edgetx/yaml_mixdata
edgetx/yaml_modeldata
edgetx/yaml_moduledata
edgetx/yaml_ops
edgetx/yaml_rawsource
edgetx/yaml_rawswitch
edgetx/yaml_screendata
edgetx/yaml_sensordata
edgetx/yaml_switchconfig
edgetx/yaml_trainerdata
edgetx/yaml_usbjoystickdata
)
AddHeadersSources()
add_library(${PROJECT_NAME}
${${PROJECT_NAME}_HDRS}
${${PROJECT_NAME}_SRCS}
)
target_link_libraries(${PROJECT_NAME}
${CPN_COMMON_LIB}
yaml-cpp
)
target_include_directories(${PROJECT_NAME}
PRIVATE
${CMAKE_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
PUBLIC
"${CMAKE_CURRENT_LIST_DIR}"
"${CMAKE_CURRENT_LIST_DIR}/opentx"
"${CMAKE_CURRENT_LIST_DIR}/edgetx"
)
@@ -0,0 +1,90 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "adjustmentreference.h"
#include "modeldata.h"
#include "eeprominterface.h"
AdjustmentReference::AdjustmentReference(int value)
{
int val = value;
if (abs(val) > ADJUST_REF_GVAR_BASE && abs(val) <= ADJUST_REF_GVAR_BASE + CPN_MAX_GVARS) {
type = ADJUST_REF_GVAR;
val = val >= 0 ? val - ADJUST_REF_GVAR_BASE : val + ADJUST_REF_GVAR_BASE;
}
else
this->type = ADJUST_REF_VALUE;
this->value = val;
}
AdjustmentReference::AdjustmentReference(AdjustRefType type, int value)
{
this->type = type;
if (isValid(value))
this->value = value;
else
clear();
}
bool AdjustmentReference::isValid(const int value) const
{
switch (type) {
case ADJUST_REF_VALUE:
return abs(value) < ADJUST_REF_GVAR_BASE;
case ADJUST_REF_GVAR:
return abs(value) > 0 && abs(value) <= CPN_MAX_GVARS;
}
return false;
}
QString AdjustmentReference::toString(const ModelData * model, const bool sign) const
{
QString ret;
switch(type) {
case ADJUST_REF_GVAR:
ret = RawSource(SOURCE_TYPE_GVAR, abs(value)).toString(model);
if (value < 0)
ret.prepend("-");
else if (sign)
ret.prepend("+");
break;
default:
ret = "%1%";
if (sign && value > 0)
ret.prepend("+");
ret = ret.arg(value);
break;
}
return ret;
}
const bool AdjustmentReference::isAvailable() const
{
Firmware *firmware = getCurrentFirmware();
if (type == ADJUST_REF_GVAR && abs(value) > firmware->getCapability(Gvars))
return false;
return true;
}
@@ -0,0 +1,71 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef ADJUSTMENTREFERENCE_H
#define ADJUSTMENTREFERENCE_H
#include <QtCore>
class ModelData;
constexpr int ADJUST_REF_GVAR_BASE { 10000 };
class AdjustmentReference {
Q_DECLARE_TR_FUNCTIONS(AdjustmentReference)
public:
enum AdjustRefType {
ADJUST_REF_VALUE,
ADJUST_REF_GVAR,
};
AdjustmentReference() { clear(); }
explicit AdjustmentReference(int value);
AdjustmentReference(const AdjustRefType type, const int value);
void clear() { memset(this, 0, sizeof(AdjustmentReference)); }
bool isSet() const { return type != ADJUST_REF_VALUE || value != 0; }
bool isValid(const int value) const;
QString toString(const ModelData * model = nullptr, const bool sign = false) const;
const bool isAvailable() const;
inline const int toValue() const
{
if (type == ADJUST_REF_GVAR)
return value >= 0 ? value + ADJUST_REF_GVAR_BASE : value - ADJUST_REF_GVAR_BASE;
else
return value;
}
bool operator== ( const AdjustmentReference & other) const {
return (this->type == other.type) && (this->value == other.value);
}
bool operator!= ( const AdjustmentReference & other) const {
return (this->type != other.type) || (this->value != other.value);
}
AdjustRefType type;
int value;
};
#endif // ADJUSTMENTREFERENCE_H
@@ -0,0 +1,95 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "boardfactories.h"
BoardFactories* gBoardFactories = nullptr;
BoardFactories::BoardFactories() :
m_default(nullptr)
{
if (registerBoard(Board::BOARD_UNKNOWN, ""))
m_default = instance(Board::BOARD_UNKNOWN);
}
BoardFactories::~BoardFactories()
{
unregisterBoardFactories();
}
BoardJson* BoardFactories::instance(Board::Type board) const
{
for (auto *registeredFactory : registeredBoardFactories) {
if (registeredFactory->instance()->board() == board)
return registeredFactory->instance();
}
return m_default;
}
// Registering firmware triggers registering the associated board
bool BoardFactories::registerBoard(Board::Type board, QString hwdefn)
{
if (board < Board::BOARD_UNKNOWN || board >= Board::BOARD_TYPE_COUNT)
return false;
if (m_default || board != Board::BOARD_UNKNOWN) {
BoardJson* regboard = instance(board);
if (regboard->board() == board) {
if (regboard->hwdefn() == hwdefn) {
//qDebug() << "Warning - Board" << Boards::getBoardName(regboard->board()) << "already registered";
return true;
}
else {
qDebug() << "Error - Board" << Boards::getBoardName(regboard->board()) << "already registered with"
<< regboard->hwdefn() << "hwdefn!";
return false;
}
}
}
BoardFactory *bf = new BoardFactory(board, hwdefn);
if (bf->instance()->loadDefinition()) {
if (registerBoardFactory(bf)) {
qDebug() << "Registered board:" << (board != Board::BOARD_UNKNOWN ? Boards::getBoardName(board) : "UNKNOWN (default)");
return true;
}
else
delete bf;
}
else
delete bf;
return false;
}
bool BoardFactories::registerBoardFactory(BoardFactory * factory)
{
registeredBoardFactories.append(factory);
return true;
}
void BoardFactories::unregisterBoardFactories()
{
for (auto *registeredFactory : registeredBoardFactories)
delete registeredFactory;
}
+59
View File
@@ -0,0 +1,59 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "boardjson.h"
class BoardFactory
{
public:
explicit BoardFactory(Board::Type board, QString hwdefn) :
m_instance(new BoardJson(board, hwdefn))
{}
virtual ~BoardFactory() {}
BoardJson* instance() const { return m_instance; }
private:
BoardJson *m_instance;
};
class BoardFactories
{
public:
explicit BoardFactories();
virtual ~BoardFactories();
BoardJson* instance(Board::Type board) const;
bool registerBoard(Board::Type board, QString hwdefn);
bool registerBoardFactory(BoardFactory * factory);
void unregisterBoardFactories();
private:
QList<BoardFactory *> registeredBoardFactories;
BoardJson *m_default;
};
extern BoardFactories* gBoardFactories;
File diff suppressed because it is too large Load Diff
+284
View File
@@ -0,0 +1,284 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "boards.h"
#include <QtCore>
#include <string>
#include <vector>
using namespace Board;
class BoardJson
{
Q_DECLARE_TR_FUNCTIONS(BoardJson)
public:
enum YamlLookupType {
YLT_CONFIG,
YLT_REF
};
struct InputDefn {
Board::AnalogInputType type = AIT_NONE;
std::string tag = "";
std::string name = "";
std::string shortName = "";
Board::FlexType flexType = FLEX_NONE;
bool inverted = false;
Board::LookupValueType cfgYaml = Board::LVT_TAG;
Board::LookupValueType refYaml = Board::LVT_TAG;
InputDefn() = default;
};
typedef std::vector<InputDefn> InputsTable;
struct Display {
unsigned int x = 0;
unsigned int y = 0;
};
struct SwitchDefn {
std::string tag = "";
Board::SwitchType type = Board::SWITCH_NOT_AVAILABLE;
std::string name = "";
int flags = 0;
bool inverted = false;
Board::SwitchType dflt = Board::SWITCH_NOT_AVAILABLE;
Display display;
bool isCustomSwitch = false;
int customSwitchIdx = -1;
Board::LookupValueType cfgYaml = Board::LVT_TAG;
Board::LookupValueType refYaml = Board::LVT_NAME;
SwitchDefn() = default;
};
typedef std::vector<SwitchDefn> SwitchesTable;
struct KeyDefn {
std::string tag = "";
std::string name = "";
std::string key = "";
std::string label = "";
Board::LookupValueType cfgYaml = Board::LVT_TAG;
Board::LookupValueType refYaml = Board::LVT_NAME;
KeyDefn() = default;
};
typedef std::vector<KeyDefn> KeysTable;
struct TrimDefn {
std::string tag = "";
std::string name = "";
Board::LookupValueType cfgYaml = Board::LVT_TAG;
Board::LookupValueType refYaml = Board::LVT_NAME;
TrimDefn() = default;
};
struct DisplayDefn {
int w = 0;
int h = 0;
int phys_w = 0;
int phys_h = 0;
int depth = 0;
int color = 0;
int oled = 0;
int backlight_color = 0;
};
struct CustomSwitchesDefn {
int rgb_led = 0;
int groups = 0;
};
struct HardwareDefn {
int has_audio_mute = 0;
int has_bling_leds = 0;
int has_ext_module_support = 0;
int has_int_module_support = 0;
int sport_max_baudrate = 0;
int surface = 0;
std::string cpu = "";
std::string cpu_type = "";
};
typedef std::vector<TrimDefn> TrimsTable;
explicit BoardJson(Board::Type board, QString hwdefn);
virtual ~BoardJson();
Board::Type board() const { return m_board; }
QString hwdefn() const { return m_hwdefn; }
bool loadDefinition();
const int getCapability(const Board::Capability capability) const;
const QString getCapabilityStr(const Board::Capability capability) const;
const int getInputsCalibrated() const;
const int getInputIndex(const QString val, Board::LookupValueType lvt) const;
const Board::InputInfo getInputInfo(int index) const;
const QString getInputName(int index) const;
const int getInputExtIndex(int index);
const int getInputPotIndex(int index);
const int getInputSliderIndex(int index);
const QString getInputTag(int index) const;
const int getInputTagOffset(QString tag);
const int getInputThrottleIndex();
const int getInputTypeOffset(Board::AnalogInputType type);
const int getInputYamlIndex(const QString val, YamlLookupType ylt) const;
const QString getInputYamlName(int index, YamlLookupType ylt) const;
const bool isInputAvailable(int index) const;
const bool isInputCalibrated(int index) const;
const bool isInputConfigurable(int index) const;
const bool isInputIgnored(int index) const;
const bool isInputFlexGyroAxis(int index) const;
const bool isInputFlexJoystickAxis(int index) const;
const bool isInputFlexPot(int index) const;
const bool isInputFlexSwitch(int index) const;
const bool isInputStick(int index) const;
const bool isInputSwitch(int index) const;
const Board::KeyInfo getKeyInfo(int index) const;
const int getKeyIndex(const QString key) const;
const int getSwitchIndex(const QString val, Board::LookupValueType lvt) const;
const int getCFSIndexForSwitch(int sw) const;
const int getSwitchIndexForCFS(int customSwitchIdx) const;
const int getSwitchIndexForCFSOffset(int offset) const;
const int getCFSOffsetForCFSIndex(int index) const;
const Board::SwitchInfo getSwitchInfo(int index) const;
const QString getSwitchName(int index) const;
const QString getSwitchTag(int index) const;
const int getSwitchTagNum(int index) const;
const int getSwitchTypeOffset(Board::SwitchType type);
const int getSwitchYamlIndex(const QString val, YamlLookupType ylt) const;
const QString getSwitchYamlName(int index, YamlLookupType ylt) const;
const int getTrimIndex(const QString val, Board::LookupValueType lvt) const;
const QString getTrimName(int index) const;
const QString getTrimTag(int index) const;
const int getTrimYamlIndex(const QString val, YamlLookupType ylt) const;
const QString getTrimYamlName(int index, YamlLookupType ylt) const;
const bool isSwitchConfigurable(int index) const;
const bool isSwitchFlex(int index) const;
const bool isSwitchFunc(int index) const;
private:
Board::Type m_board;
QString m_hwdefn;
InputsTable *m_inputs;
SwitchesTable *m_switches;
TrimsTable *m_trims;
KeysTable *m_keys;
DisplayDefn m_display;
CustomSwitchesDefn m_cfs;
HardwareDefn m_hardware;
struct InputCounts {
unsigned int flexGyroAxes;
unsigned int flexJoystickAxes;
unsigned int flexPots;
unsigned int flexSliders;
unsigned int flexSwitches;
unsigned int rtcbat;
unsigned int sticks;
unsigned int switches;
unsigned int vbat;
};
InputCounts m_inputCnt;
struct SwitchCounts {
unsigned int std;
unsigned int flex;
unsigned int func;
};
SwitchCounts m_switchCnt;
static bool loadFile(Board::Type board, QString hwdefn, InputsTable * inputs, SwitchesTable * switches,
KeysTable * keys, TrimsTable * trims, DisplayDefn & lcd, CustomSwitchesDefn & cfs,
HardwareDefn & hardware);
static void afterLoadFixups(Board::Type board, InputsTable * inputs, SwitchesTable * switches,
KeysTable * keys, TrimsTable * trims);
static int getInputsCalibrated(const InputsTable * inputs);
static int getInputIndex(const InputsTable * inputs, QString val, Board::LookupValueType lvt);
static Board::InputInfo getInputInfo(const InputsTable * inputs, int index);
static QString getInputName(const InputsTable * inputs, int index);
static QString getInputTag(const InputsTable * inputs, int index);
static int getInputTagOffset(const InputsTable * inputs, QString tag);
static int getInputTypeOffset(const InputsTable * inputs, Board::AnalogInputType type);
static int getKeyIndex(const KeysTable * keys, QString key);
static Board::KeyInfo getKeyInfo(const KeysTable * keys, int index);
static int getSwitchIndex(const SwitchesTable * switches, QString val, Board::LookupValueType lvt);
static int getCFSIndexForSwitch(const SwitchesTable * switches, int sw);
static int getSwitchIndexForCFS(const SwitchesTable * switches, int customSwitchIdx);
static int getSwitchIndexForCFSOffset(const SwitchesTable * switches, int offset);
static int getCFSOffsetForCFSIndex(const SwitchesTable * switches, int index);
static Board::SwitchInfo getSwitchInfo(const SwitchesTable * switches, int index);
static QString getSwitchName(const SwitchesTable * switches, int index);
static QString getSwitchTag(const SwitchesTable * switches, int index);
static int getSwitchTagNum(const SwitchesTable * switches, int index);
static int getSwitchTypeOffset(const SwitchesTable * switches, Board::SwitchType type);
static int getTrimIndex(const TrimsTable * trims, QString val, Board::LookupValueType lvt);
static QString getTrimName(const TrimsTable * trims, int index);
static QString getTrimTag(const TrimsTable * trims, int index);
static bool isInputAvailable(const InputDefn & defn);
static bool isInputCalibrated(const InputDefn & defn);
static bool isInputConfigurable(const InputDefn & defn);
static bool isInputFlex(const InputDefn & defn);
static bool isInputFlexGyroAxis(const InputDefn & defn);
static bool isInputFlexJoystickAxis(const InputDefn & defn);
static bool isInputFlexPot(const InputDefn & defn);
static bool isInputFlexPotMultipos(const InputDefn & defn);
static bool isInputFlexSlider(const InputDefn & defn);
static bool isInputFlexSwitch(const InputDefn & defn);
static bool isInputIgnored(const InputDefn & defn);
static bool isInputRTCBat(const InputDefn & defn);
static bool isInputStick(const InputDefn & defn);
static bool isInputSwitch(const InputDefn & defn);
static bool isInputVBat(const InputDefn & defn);
static bool isSwitchStd(const SwitchDefn & defn);
static bool isSwitchFlex(const SwitchDefn & defn);
static bool isSwitchFunc(const SwitchDefn & defn);
static void setInputCounts(const InputsTable * inputs, InputCounts & inputCounts);
static void setSwitchCounts(const SwitchesTable * switches, SwitchCounts & switchCounts);
static int getNumericSuffix(const std::string str);
};
File diff suppressed because it is too large Load Diff
+854
View File
@@ -0,0 +1,854 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "datahelpers.h"
#include <QtCore>
#include <QObject>
#include <QString>
class AbstractStaticItemModel;
class SemanticVersion;
class BoardJson;
class GeneralSettings;
// identiying names of static abstract item models
constexpr char AIM_BOARDS_POT_TYPE[] {"boards.pottype"};
constexpr char AIM_BOARDS_SLIDER_TYPE[] {"boards.slidertype"};
constexpr char AIM_BOARDS_SWITCH_TYPE[] {"boards.switchtype"};
constexpr char AIM_BOARDS_MODULE_SIZE[] {"boards.extmodulesize"};
constexpr char AIM_BOARDS_FLEX_TYPE[] {"boards.flextype"};
// TODO create a Board class with all these functions
namespace Board {
enum Type
{
BOARD_UNKNOWN = -1,
BOARD_TARANIS_X7,
BOARD_TARANIS_X7_ACCESS,
BOARD_TARANIS_X9D,
BOARD_TARANIS_X9DP,
BOARD_TARANIS_X9DP_2019,
BOARD_TARANIS_X9E,
BOARD_HORUS_X12S,
BOARD_X10,
BOARD_X10_EXPRESS,
BOARD_TARANIS_XLITE,
BOARD_TARANIS_XLITES,
BOARD_TARANIS_X9LITE,
BOARD_TARANIS_X9LITES,
BOARD_JUMPER_T12,
BOARD_JUMPER_T12MAX,
BOARD_JUMPER_T14,
BOARD_JUMPER_T15,
BOARD_JUMPER_T15PRO,
BOARD_JUMPER_T16,
BOARD_RADIOMASTER_TX16S,
BOARD_RADIOMASTER_TX16SMK3,
BOARD_RADIOMASTER_TX15,
BOARD_JUMPER_T18,
BOARD_JUMPER_T20,
BOARD_RADIOMASTER_TX12,
BOARD_RADIOMASTER_TX12_MK2,
BOARD_RADIOMASTER_BOXER,
BOARD_RADIOMASTER_GX12,
BOARD_RADIOMASTER_T8,
BOARD_JUMPER_TLITE,
BOARD_JUMPER_TLITE_F4,
BOARD_FLYSKY_NV14,
BOARD_FLYSKY_PA01,
BOARD_FLYSKY_PL18,
BOARD_FLYSKY_PL18EV,
BOARD_FLYSKY_PL18U,
BOARD_FLYSKY_NB4P,
BOARD_FLYSKY_ST16,
BOARD_RADIOMASTER_ZORRO,
BOARD_JUMPER_TPRO,
BOARD_BETAFPV_LR3PRO,
BOARD_IFLIGHT_COMMANDO8,
BOARD_FLYSKY_EL18,
BOARD_JUMPER_TPROV2,
BOARD_JUMPER_TPROS,
BOARD_RADIOMASTER_POCKET,
BOARD_JUMPER_T20V2,
BOARD_JUMPER_BUMBLEBEE,
BOARD_FATFISH_F16,
BOARD_HELLORADIOSKY_V16,
BOARD_RADIOMASTER_MT12,
BOARD_HELLORADIOSKY_V14,
BOARD_TYPE_COUNT,
BOARD_TYPE_MAX = BOARD_TYPE_COUNT - 1
};
enum PotType
{
POT_NONE,
POT_WITH_DETENT,
POT_MULTIPOS_SWITCH,
POT_WITHOUT_DETENT,
POT_SLIDER_WITH_DETENT,
POT_TYPE_COUNT
};
enum SliderType
{
SLIDER_NONE,
SLIDER_WITH_DETENT,
SLIDER_TYPE_COUNT
};
enum SwitchType
{
SWITCH_NOT_AVAILABLE,
SWITCH_TOGGLE,
SWITCH_2POS,
SWITCH_3POS,
SWITCH_GLOBAL,
SWITCH_FUNC,
SWITCH_ADC,
SWITCH_TYPE_COUNT
};
enum StickAxes {
STICK_AXIS_LH = 0,
STICK_AXIS_LV,
STICK_AXIS_RV,
STICK_AXIS_RH,
STICK_AXIS_COUNT
};
enum TrimAxes {
TRIM_AXIS_LH = 0,
TRIM_AXIS_LV,
TRIM_AXIS_RV,
TRIM_AXIS_RH,
TRIM_AXIS_T5,
TRIM_AXIS_T6,
TRIM_AXIS_T7,
TRIM_AXIS_T8,
TRIM_AXIS_COUNT
};
enum TrimSwitches
{
TRIM_SW_LH_DEC,
TRIM_SW_LH_INC,
TRIM_SW_LV_DEC,
TRIM_SW_LV_INC,
TRIM_SW_RV_DEC,
TRIM_SW_RV_INC,
TRIM_SW_RH_DEC,
TRIM_SW_RH_INC,
TRIM_SW_T5_DEC,
TRIM_SW_T5_INC,
TRIM_SW_T6_DEC,
TRIM_SW_T6_INC,
TRIM_SW_T7_DEC,
TRIM_SW_T7_INC,
TRIM_SW_T8_DEC,
TRIM_SW_T8_INC,
TRIM_SW_COUNT
};
enum StickAxesSurface {
STICK_AXIS_SURFACE_RH = 0, // Steering
STICK_AXIS_SURFACE_LV, // Throttle
STICK_AXIS_SURFACE_COUNT
};
enum TrimAxesSurface {
TRIM_AXIS_SURFACE_RH = 0,
TRIM_AXIS_SURFACE_LH, // Throttle axis vertical but its trim horizontal in lcd
TRIM_AXIS_SURFACE_T3,
TRIM_AXIS_SURFACE_T4,
TRIM_AXIS_SURFACE_T5,
TRIM_AXIS_SURFACE_T6,
TRIM_AXIS_SURFACE_T7,
TRIM_AXIS_SURFACE_T8,
TRIM_AXIS_SURFACE_COUNT
};
enum TrimSwitchesSurface
{
TRIM_SW_SURFACE_RH_DEC,
TRIM_SW_SURFACE_RH_INC,
TRIM_SW_SURFACE_LH_DEC, // Throttle axis vertical but its trim horizontal in lcd
TRIM_SW_SURFACE_LH_INC, // Throttle axis vertical but its trim horizontal in lcd
TRIM_SW_SURFACE_T3_DEC,
TRIM_SW_SURFACE_T3_INC,
TRIM_SW_SURFACE_T4_DEC,
TRIM_SW_SURFACE_T4_INC,
TRIM_SW_SURFACE_T5_DEC,
TRIM_SW_SURFACE_T5_INC,
TRIM_SW_SURFACE_T6_DEC,
TRIM_SW_SURFACE_T6_INC,
TRIM_SW_SURFACE_T7_DEC,
TRIM_SW_SURFACE_T7_INC,
TRIM_SW_SURFACE_T8_DEC,
TRIM_SW_SURFACE_T8_INC,
TRIM_SW_SURFACE_COUNT
};
enum Capability {
Air,
BacklightLevelMin,
CPU,
CPUType,
FlexInputs,
FlexSwitches,
FunctionSwitchColors,
FunctionSwitches,
FunctionSwitchGroups,
GyroAxes,
Gyros,
HasAudioMuteGPIO,
HasAuxSerialMode,
HasAux2SerialMode,
HasBacklightColor,
HasBlingLEDS,
HasBluetooth,
HasColorLcd,
HasExternalModuleSupport,
HasIMU,
HasInternalGPS,
HasInternalModuleSupport,
HasIntModuleHeartbeatGPIO,
HasRTC,
HasSDCard,
HasSoftwareSerialPower,
HasSwitchableJack,
HasTrainerModuleCPPM,
HasTrainerModuleSBUS,
HasVBat,
HasVCPSerialMode,
Inputs,
InputSwitches,
IsF4,
IsH5,
IsH7,
JoystickAxes,
Joysticks,
Keys,
LcdDepth,
LcdHeight,
LcdOLED,
LcdWidth,
MaxContrast,
MaxVolume,
MinContrast,
MultiposPots,
MultiposPotsPositions,
NumFunctionSwitchesPositions,
NumTrims,
NumTrimSwitches,
Pots,
PwrButtonPress,
RotaryEncoderNavigation,
Sliders,
SportMaxBaudRate,
StandardSwitches,
Sticks,
Surface,
Switches,
SwitchesPositions,
};
struct SwitchPosition {
SwitchPosition(unsigned int index, unsigned int position):
index(index),
position(position)
{
}
unsigned int index;
unsigned int position;
};
enum SwitchTypeMasks {
SwitchTypeFlagNone = 1 << 1,
SwitchTypeFlag2Pos = 1 << 2,
SwitchTypeFlag3Pos = 1 << 3,
SwitchTypeContextNone = SwitchTypeFlagNone,
SwitchTypeContext2Pos = SwitchTypeContextNone | SwitchTypeFlag2Pos,
SwitchTypeContext3Pos = SwitchTypeContext2Pos | SwitchTypeFlag3Pos
};
enum ExternalModuleSizes {
EXTMODSIZE_NONE,
EXTMODSIZE_STD,
EXTMODSIZE_SMALL,
EXTMODSIZE_BOTH,
EXTMODSIZE_COUNT
};
enum AnalogInputType
{
AIT_NONE,
AIT_STICK,
AIT_FLEX,
AIT_VBAT,
AIT_RTC_BAT,
AIT_LUX,
AIT_SWITCH,
};
enum FlexType {
FLEX_NONE = 0,
FLEX_POT,
FLEX_POT_CENTER,
FLEX_SLIDER,
FLEX_MULTIPOS,
FLEX_AXIS_X,
FLEX_AXIS_Y,
FLEX_SWITCH,
FLEX_TYPE_COUNT
};
enum FlexTypeMasks {
FlexTypeFlagNotSwitch = 1 << 1,
FlexTypeFlagSwitch = 1 << 2,
FlexTypeContextNoSwitch = FlexTypeFlagNotSwitch,
FlexTypeContextSwitch = FlexTypeContextNoSwitch | FlexTypeFlagSwitch
};
enum LookupValueType {
LVT_TAG = 0,
LVT_NAME
};
struct InputInfo {
InputInfo() :
type(AIT_NONE),
tag(""),
name(""),
shortName(""),
flexType(FLEX_NONE),
inverted(false)
{}
AnalogInputType type;
std::string tag;
std::string name;
std::string label;
std::string shortName;
FlexType flexType;
bool inverted;
};
struct SwitchInfo {
SwitchInfo() :
type(SWITCH_NOT_AVAILABLE),
tag(""),
name(""),
dflt(SWITCH_NOT_AVAILABLE),
inverted(false)
{}
SwitchType type;
std::string tag;
std::string name;
SwitchType dflt;
bool inverted;
};
struct KeyInfo {
KeyInfo() :
key(""),
name(""),
label("")
{}
std::string key;
std::string name;
std::string label;
};
struct TrimInfo {
TrimInfo() :
name("")
{}
std::string name;
};
}
class Boards
{
Q_DECLARE_TR_FUNCTIONS(Boards)
public:
Boards(Board::Type board);
virtual ~Boards() {}
void setBoardType(const Board::Type & board);
Board::Type getBoardType() const { return m_boardType; }
const uint32_t getFourCC() const { return getFourCC(m_boardType); }
const int getEEpromSize() const { return getEEpromSize(m_boardType); }
const int getFlashSize() const { return getFlashSize(m_boardType); }
const int getCapability(Board::Capability capability) const { return getCapability(m_boardType, capability); }
const QString getCapabilityStr(Board::Capability capability) const { return getCapabilityStr(m_boardType, capability); }
const bool isBoardCompatible(Board::Type board2) const { return isBoardCompatible(m_boardType, board2); }
static uint32_t getFourCC(Board::Type board);
static int getEEpromSize(Board::Type board);
static int getFlashSize(Board::Type board);
static int getCapability(Board::Type board, Board::Capability capability);
static QString getCapabilityStr(Board::Type board, Board::Capability capability);
static QString getAxisName(int index);
static bool isBoardCompatible(Board::Type board1, Board::Type board2);
static QString getBoardName(Board::Type board);
static QString switchTypeToString(int value);
static AbstractStaticItemModel * switchTypeItemModel();
static AbstractStaticItemModel * intModuleTypeItemModel();
static QList<int> getSupportedInternalModules(Board::Type board);
static int getDefaultInternalModules(Board::Type board);
static int getDefaultExternalModuleSize(Board::Type board);
static void getBattRange(Board::Type board, int& vmin, int& vmax, unsigned int& vwarn);
static QString externalModuleSizeToString(int value);
static AbstractStaticItemModel * externalModuleSizeItemModel();
static BoardJson* getBoardJson(Board::Type board = Board::BOARD_UNKNOWN);
static int getInputsCalibrated(Board::Type board = Board::BOARD_UNKNOWN);
static Board::InputInfo getInputInfo(int index, Board::Type board = Board::BOARD_UNKNOWN);
static int getInputIndex(QString val, Board::LookupValueType lvt, Board::Type board = Board::BOARD_UNKNOWN);
static QString getInputName(int index, Board::Type board = Board::BOARD_UNKNOWN);
static int getInputExtIndex(int index, Board::Type board = Board::BOARD_UNKNOWN);
static int getInputPotIndex(int index, Board::Type board = Board::BOARD_UNKNOWN);
static int getInputSliderIndex(int index, Board::Type board = Board::BOARD_UNKNOWN);
static QString getInputTag(int index, Board::Type board = Board::BOARD_UNKNOWN);
static int getInputTagOffset(QString tag, Board::Type board = Board::BOARD_UNKNOWN);
static int getInputThrottleIndex(Board::Type board = Board::BOARD_UNKNOWN);
static int getInputTypeOffset(Board::AnalogInputType type, Board::Type board = Board::BOARD_UNKNOWN);
static int getInputYamlIndex(QString val, int ylt, Board::Type board = Board::BOARD_UNKNOWN);
static QString getInputYamlName(int index, int ylt, Board::Type board = Board::BOARD_UNKNOWN);
static Board::KeyInfo getKeyInfo(int index, Board::Type board = Board::BOARD_UNKNOWN);
static int getKeyIndex(QString key, Board::Type board = Board::BOARD_UNKNOWN);
static Board::SwitchInfo getSwitchInfo(int index, Board::Type board = Board::BOARD_UNKNOWN);
static int getSwitchIndex(QString val, Board::LookupValueType lvt, Board::Type board = Board::BOARD_UNKNOWN);
static int getCFSIndexForSwitch(int swIdx, Board::Type board = Board::BOARD_UNKNOWN);
static int getSwitchIndexForCFS(int cfsIdx, Board::Type board = Board::BOARD_UNKNOWN);
static int getSwitchIndexForCFSOffset(int offset, Board::Type board = Board::BOARD_UNKNOWN);
static int getCFSOffsetForCFSIndex(int index, Board::Type board = Board::BOARD_UNKNOWN);
static QString getSwitchName(int index, Board::Type board = Board::BOARD_UNKNOWN);
static QString getSwitchTag(int index, Board::Type board = Board::BOARD_UNKNOWN);
static int getSwitchTagNum(int index, Board::Type board = Board::BOARD_UNKNOWN);
static int getSwitchTypeOffset(Board::SwitchType type, Board::Type board = Board::BOARD_UNKNOWN);
static int getSwitchYamlIndex(QString val, int ylt, Board::Type board = Board::BOARD_UNKNOWN);
static QString getSwitchYamlName(int index, int ylt, Board::Type board = Board::BOARD_UNKNOWN);
static int getTrimYamlIndex(QString val, int ylt, Board::Type board = Board::BOARD_UNKNOWN);
static QString getTrimYamlName(int index, int ylt, Board::Type board = Board::BOARD_UNKNOWN);
STRINGTAGMAPPINGFUNCS(legacyTrimSourcesLookupTable, LegacyTrimSource);
STRINGTAGMAPPINGFUNCS(trimSwitchesLookupTable, TrimSwitch);
STRINGTAGMAPPINGFUNCS(rawSwitchTypesLookupTable, RawSwitchType);
STRINGTAGMAPPINGFUNCS(rawSourceSpecialTypesLookupTable, RawSourceSpecialType);
static bool isInputAvailable(int index, Board::Type board = Board::BOARD_UNKNOWN);
static bool isInputCalibrated(int index, Board::Type board = Board::BOARD_UNKNOWN);
static bool isInputConfigurable(int index, Board::Type board = Board::BOARD_UNKNOWN);
static bool isInputGyroAxis(int index, Board::Type board = Board::BOARD_UNKNOWN);
static bool isInputIgnored(int index, Board::Type board = Board::BOARD_UNKNOWN);
static bool isInputPot(int index, Board::Type board = Board::BOARD_UNKNOWN);
static bool isInputStick(int index, Board::Type board = Board::BOARD_UNKNOWN);
static bool isSwitchConfigurable(int index, Board::Type board = Board::BOARD_UNKNOWN);
static bool isSwitchFlex(int index, Board::Type board = Board::BOARD_UNKNOWN);
static bool isSwitchFunc(int index, Board::Type board = Board::BOARD_UNKNOWN);
static QString flexTypeToString(int value);
static AbstractStaticItemModel * flexTypeItemModel();
static std::string getLegacyAnalogMappedInputTag(const char * legacytag, Board::Type board = Board::BOARD_UNKNOWN);
static QString getRadioModeString(Board::Type board = Board::BOARD_UNKNOWN);
static bool isAir(Board::Type board = Board::BOARD_UNKNOWN);
static bool isSurface(Board::Type board = Board::BOARD_UNKNOWN);
static void tests();
private:
Board::Type m_boardType = Board::BOARD_UNKNOWN;
BoardJson* m_boardJson = nullptr;
const StringTagMappingTable legacyTrimSourcesLookupTable;
const StringTagMappingTable trimSwitchesLookupTable;
const StringTagMappingTable rawSwitchTypesLookupTable;
const StringTagMappingTable rawSourceSpecialTypesLookupTable;
static StringTagMappingTable getLegacyAnalogsLookupTable(Board::Type board = Board::BOARD_UNKNOWN);
};
// temporary aliases for transition period, use Boards class instead.
#define getBoardCapability(b__, c__) Boards::getCapability(b__, c__)
inline bool IS_BETAFPV_LR3PRO(Board::Type board)
{
return board == Board::BOARD_BETAFPV_LR3PRO;
}
inline bool IS_IFLIGHT_COMMANDO8(Board::Type board)
{
return board == Board::BOARD_IFLIGHT_COMMANDO8;
}
inline bool IS_JUMPER_T12(Board::Type board)
{
return board == Board::BOARD_JUMPER_T12;
}
inline bool IS_JUMPER_TLITE(Board::Type board)
{
return board == Board::BOARD_JUMPER_TLITE || board == Board::BOARD_JUMPER_TLITE_F4;
}
inline bool IS_JUMPER_TPRO(Board::Type board)
{
return board == Board::BOARD_JUMPER_TPRO || board == Board::BOARD_JUMPER_TPROV2 || board == Board::BOARD_JUMPER_TPROS;
}
inline bool IS_JUMPER_TPROV1(Board::Type board)
{
return board == Board::BOARD_JUMPER_TPRO;
}
inline bool IS_JUMPER_TPROV2(Board::Type board)
{
return board == Board::BOARD_JUMPER_TPROV2;
}
inline bool IS_JUMPER_BUMBLEBEE(Board::Type board)
{
return board == Board::BOARD_JUMPER_BUMBLEBEE;
}
inline bool IS_JUMPER_TPROS(Board::Type board)
{
return board == Board::BOARD_JUMPER_TPROS;
}
inline bool IS_JUMPER_T15(Board::Type board)
{
return board == Board::BOARD_JUMPER_T15;
}
inline bool IS_JUMPER_T15PRO(Board::Type board)
{
return board == Board::BOARD_JUMPER_T15PRO;
}
inline bool IS_JUMPER_T16(Board::Type board)
{
return board == Board::BOARD_JUMPER_T16;
}
inline bool IS_JUMPER_T12MAX(Board::Type board)
{
return board == Board::BOARD_JUMPER_T12MAX;
}
inline bool IS_JUMPER_T14(Board::Type board)
{
return board == Board::BOARD_JUMPER_T14;
}
inline bool IS_JUMPER_T18(Board::Type board)
{
return board == Board::BOARD_JUMPER_T18;
}
inline bool IS_JUMPER_T20(Board::Type board)
{
return board == Board::BOARD_JUMPER_T20 || board == Board::BOARD_JUMPER_T20V2;
}
inline bool IS_RADIOMASTER_TX16S(Board::Type board)
{
return board == Board::BOARD_RADIOMASTER_TX16S;
}
inline bool IS_RADIOMASTER_TX16SMK3(Board::Type board)
{
return board == Board::BOARD_RADIOMASTER_TX16SMK3;
}
inline bool IS_RADIOMASTER_TX15(Board::Type board)
{
return board == Board::BOARD_RADIOMASTER_TX15;
}
inline bool IS_RADIOMASTER_TX12(Board::Type board)
{
return board == Board::BOARD_RADIOMASTER_TX12;
}
inline bool IS_RADIOMASTER_TX12_MK2(Board::Type board)
{
return board == Board::BOARD_RADIOMASTER_TX12_MK2;
}
inline bool IS_RADIOMASTER_ZORRO(Board::Type board)
{
return board == Board::BOARD_RADIOMASTER_ZORRO;
}
inline bool IS_RADIOMASTER_BOXER(Board::Type board)
{
return board == Board::BOARD_RADIOMASTER_BOXER;
}
inline bool IS_RADIOMASTER_MT12(Board::Type board)
{
return board == Board::BOARD_RADIOMASTER_MT12;
}
inline bool IS_RADIOMASTER_POCKET(Board::Type board)
{
return board == Board::BOARD_RADIOMASTER_POCKET;
}
inline bool IS_RADIOMASTER_GX12(Board::Type board)
{
return board == Board::BOARD_RADIOMASTER_GX12;
}
inline bool IS_RADIOMASTER_T8(Board::Type board)
{
return board == Board::BOARD_RADIOMASTER_T8;
}
inline bool IS_FATFISH_F16(Board::Type board)
{
return board == Board::BOARD_FATFISH_F16;
}
inline bool IS_HELLORADIOSKY_V14(Board::Type board)
{
return board == Board::BOARD_HELLORADIOSKY_V14;
}
inline bool IS_HELLORADIOSKY_V16(Board::Type board)
{
return board == Board::BOARD_HELLORADIOSKY_V16;
}
inline bool IS_FAMILY_T16(Board::Type board)
{
return board == Board::BOARD_FATFISH_F16 ||
board == Board::BOARD_HELLORADIOSKY_V16 ||
board == Board::BOARD_JUMPER_T15 ||
board == Board::BOARD_JUMPER_T15PRO ||
board == Board::BOARD_JUMPER_T16 ||
board == Board::BOARD_JUMPER_T18 ||
board == Board::BOARD_RADIOMASTER_TX15 ||
board == Board::BOARD_RADIOMASTER_TX16S ||
board == Board::BOARD_RADIOMASTER_TX16SMK3;
}
inline bool IS_FAMILY_T12(Board::Type board)
{
return board == Board::BOARD_BETAFPV_LR3PRO ||
board == Board::BOARD_HELLORADIOSKY_V14 ||
board == Board::BOARD_IFLIGHT_COMMANDO8 ||
board == Board::BOARD_JUMPER_BUMBLEBEE ||
board == Board::BOARD_JUMPER_T12 ||
board == Board::BOARD_JUMPER_T12MAX ||
board == Board::BOARD_JUMPER_T14 ||
board == Board::BOARD_JUMPER_T20 ||
board == Board::BOARD_JUMPER_T20V2 ||
board == Board::BOARD_JUMPER_TLITE ||
board == Board::BOARD_JUMPER_TLITE_F4 ||
board == Board::BOARD_JUMPER_TPRO ||
board == Board::BOARD_JUMPER_TPROS ||
board == Board::BOARD_JUMPER_TPROV2 ||
board == Board::BOARD_RADIOMASTER_BOXER ||
board == Board::BOARD_RADIOMASTER_GX12 ||
board == Board::BOARD_RADIOMASTER_T8 ||
board == Board::BOARD_RADIOMASTER_TX12 ||
board == Board::BOARD_RADIOMASTER_TX12_MK2 ||
board == Board::BOARD_RADIOMASTER_MT12 ||
board == Board::BOARD_RADIOMASTER_POCKET ||
board == Board::BOARD_RADIOMASTER_ZORRO;
}
inline bool IS_FLYSKY_NV14(Board::Type board)
{
return (board == Board::BOARD_FLYSKY_NV14);
}
inline bool IS_FLYSKY_EL18(Board::Type board)
{
return (board == Board::BOARD_FLYSKY_EL18);
}
inline bool IS_FLYSKY_NB4P(Board::Type board)
{
return (board == Board::BOARD_FLYSKY_NB4P);
}
inline bool IS_FLYSKY_PA01(Board::Type board)
{
return (board == Board::BOARD_FLYSKY_PA01);
}
inline bool IS_FLYSKY_PL18(Board::Type board)
{
return (board == Board::BOARD_FLYSKY_PL18);
}
inline bool IS_FLYSKY_PL18EV(Board::Type board)
{
return (board == Board::BOARD_FLYSKY_PL18EV);
}
inline bool IS_FLYSKY_PL18U(Board::Type board)
{
return (board == Board::BOARD_FLYSKY_PL18U);
}
inline bool IS_FLYSKY_ST16(Board::Type board)
{
return (board == Board::BOARD_FLYSKY_ST16);
}
inline bool IS_FAMILY_PL18(Board::Type board)
{
return IS_FLYSKY_PL18(board) || IS_FLYSKY_PL18EV(board) || IS_FLYSKY_PL18U(board);
}
inline bool IS_TARANIS_XLITE(Board::Type board)
{
return board == Board::BOARD_TARANIS_XLITE || board == Board::BOARD_TARANIS_XLITES;
}
inline bool IS_TARANIS_XLITES(Board::Type board)
{
return board == Board::BOARD_TARANIS_XLITES;
}
inline bool IS_TARANIS_X7(Board::Type board)
{
return board == Board::BOARD_TARANIS_X7 || board == Board::BOARD_TARANIS_X7_ACCESS;
}
inline bool IS_TARANIS_X7_ACCESS(Board::Type board)
{
return board == Board::BOARD_TARANIS_X7_ACCESS;
}
inline bool IS_TARANIS_X9LITE(Board::Type board)
{
return board == Board::BOARD_TARANIS_X9LITE || board == Board::BOARD_TARANIS_X9LITES;
}
inline bool IS_TARANIS_X9LITES(Board::Type board)
{
return board == Board::BOARD_TARANIS_X9LITES;
}
inline bool IS_TARANIS_X9(Board::Type board)
{
return board==Board::BOARD_TARANIS_X9D || board==Board::BOARD_TARANIS_X9DP || board==Board::BOARD_TARANIS_X9DP_2019 || board==Board::BOARD_TARANIS_X9E;
}
inline bool IS_TARANIS_X9D(Board::Type board)
{
return board == Board::BOARD_TARANIS_X9D || board == Board::BOARD_TARANIS_X9DP || board == Board::BOARD_TARANIS_X9DP_2019;
}
inline bool IS_TARANIS_PLUS(Board::Type board)
{
return board == Board::BOARD_TARANIS_X9DP || board == Board::BOARD_TARANIS_X9E;
}
inline bool IS_TARANIS_X9E(Board::Type board)
{
return board == Board::BOARD_TARANIS_X9E;
}
inline bool IS_TARANIS_SMALL(Board::Type board)
{
return IS_TARANIS_X7(board) || IS_TARANIS_XLITE(board) || IS_TARANIS_X9LITE(board) || IS_FAMILY_T12(board);
}
inline bool IS_TARANIS(Board::Type board)
{
return IS_TARANIS_X9(board) || IS_TARANIS_SMALL(board);
}
inline bool IS_HORUS_X10(Board::Type board)
{
return board == Board::BOARD_X10 || board == Board::BOARD_X10_EXPRESS;
}
inline bool IS_HORUS_X12S(Board::Type board)
{
return board == Board::BOARD_HORUS_X12S;
}
inline bool IS_FAMILY_HORUS(Board::Type board)
{
return IS_HORUS_X12S(board) || IS_HORUS_X10(board);
}
inline bool IS_FAMILY_HORUS_OR_T16(Board::Type board)
{
return IS_FAMILY_HORUS(board) || IS_FAMILY_T16(board) ||
IS_FLYSKY_NV14(board)/*generally*/ || IS_FLYSKY_EL18(board)/*generally*/
|| IS_FAMILY_PL18(board) || IS_FLYSKY_ST16(board)/*generally*/ ||
IS_FLYSKY_PA01(board)/*generally*/ || IS_FLYSKY_NB4P(board)/*generally*/;
}
inline bool HAS_LARGE_LCD(Board::Type board)
{
return IS_FAMILY_HORUS_OR_T16(board) || IS_TARANIS_X9(board);
}
inline bool HAS_EXTERNAL_ANTENNA(Board::Type board)
{
return (IS_FAMILY_HORUS(board) || IS_FAMILY_T16(board) || (IS_TARANIS_XLITE(board) && !IS_TARANIS_XLITES(board)));
}
inline bool IS_TARANIS_X9DP_2019(Board::Type board)
{
return (board == Board::BOARD_TARANIS_X9DP_2019);
}
inline bool IS_ACCESS_RADIO(Board::Type board)
{
return IS_TARANIS_XLITES(board) || IS_TARANIS_X9LITE(board) ||
board == Board::BOARD_TARANIS_X9DP_2019 ||
board == Board::BOARD_X10_EXPRESS || IS_TARANIS_X7_ACCESS(board);
}
inline bool IS_ACCESS_RADIO(Board::Type board, const QString & id)
{
return IS_ACCESS_RADIO(board) ||
(IS_FAMILY_HORUS_OR_T16(board) && id.contains("internalaccess"));
}
+133
View File
@@ -0,0 +1,133 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "curvedata.h"
#include "datahelpers.h"
#include "compounditemmodels.h"
#include "eeprominterface.h"
CurveData::CurveData()
{
clear();
}
void CurveData::clear(int count)
{
memset(reinterpret_cast<void *>(this), 0, sizeof(CurveData));
this->count = count;
}
void CurveData::init()
{
memset(points, 0, sizeof(CurvePoint) * CPN_MAX_POINTS);
float incr = 200.0 / (float)(count - 1);
for (int i = 1; i < (count - 1); i++) {
points[i].x = -100 + (int)(incr * (float)i);
points[i].y = points[i].x;
}
points[0].x = -100;
points[0].y = -100;
points[count - 1].x = 100;
points[count - 1].y = 100;
}
bool CurveData::isEmpty() const
{
CurveData tmp;
return !memcmp(this, &tmp, sizeof(CurveData));
}
QString CurveData::nameToString(const int idx) const
{
return DataHelpers::getElementName(tr("CV"), idx + 1, name);
}
QString CurveData::typeToString() const
{
return typeToString(type);
}
// static
QString CurveData::typeToString(int value)
{
switch (value) {
case CURVE_TYPE_STANDARD:
return tr("Standard");
case CURVE_TYPE_CUSTOM:
return tr("Custom");
default:
return CPN_STR_UNKNOWN_ITEM;
}
}
QString CurveData::pointsToString() const
{
QString result = "[";
if (type == CURVE_TYPE_CUSTOM) {
for (int j = 0; j < count; j++) {
if (j != 0)
result.append(", ");
result.append(QString("(%1, %2)").arg(points[j].x).arg(points[j].y));
}
}
else {
for (int j = 0; j < count; j++) {
if (j != 0)
result.append(", ");
result.append(QString("%1").arg(points[j].y));
}
}
result.append("]");
return result;
}
// static
AbstractStaticItemModel * CurveData::typeItemModel()
{
AbstractStaticItemModel * mdl = new AbstractStaticItemModel();
mdl->setName("curvedata.type");
for (int i = 0; i <= CURVE_TYPE_LAST; i++) {
mdl->appendToItemList(typeToString(i), i);
}
mdl->loadItemList();
return mdl;
}
// static
AbstractStaticItemModel * CurveData::pointsItemModel()
{
AbstractStaticItemModel * mdl = new AbstractStaticItemModel();
mdl->setName("curvedata.points");
for (int i = 2; i <= CPN_MAX_POINTS; i++) {
mdl->appendToItemList(tr("%1 points").arg(i), i);
}
mdl->loadItemList();
return mdl;
}
+65
View File
@@ -0,0 +1,65 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "constants.h"
#include <QtCore>
class AbstractStaticItemModel;
class CurvePoint {
public:
int8_t x;
int8_t y;
};
#define CURVEDATA_NAME_LEN 3
class CurveData {
Q_DECLARE_TR_FUNCTIONS(CurveData)
public:
enum CurveType {
CURVE_TYPE_STANDARD,
CURVE_TYPE_CUSTOM,
CURVE_TYPE_LAST = CURVE_TYPE_CUSTOM
};
CurveData();
CurveType type;
bool smooth;
int count;
CurvePoint points[CPN_MAX_POINTS];
char name[CURVEDATA_NAME_LEN + 1];
void clear(int count = 5);
void init();
bool isEmpty() const;
QString nameToString(const int idx) const;
QString typeToString() const;
QString pointsToString() const;
static QString typeToString(int value);
static AbstractStaticItemModel * typeItemModel();
static AbstractStaticItemModel * pointsItemModel();
};
+304
View File
@@ -0,0 +1,304 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "curvereference.h"
#include "helpers.h"
#include "modeldata.h"
#include "generalsettings.h"
#include "filtereditemmodels.h"
#include "curveimagewidget.h"
#include "curvedialog.h"
#include "sourcenumref.h"
const QString CurveReference::toString(const ModelData * model, bool verbose, const GeneralSettings * const generalSettings,
Board::Type board, bool prefixCustomName) const
{
if (value == 0)
return CPN_STR_NONE_ITEM;
QString ret;
unsigned idx = abs(value) - 1;
switch(type) {
case CURVE_REF_DIFF:
case CURVE_REF_EXPO:
ret = SourceNumRef(value).toString(model, generalSettings, board, prefixCustomName);
break;
case CURVE_REF_FUNC:
ret = functionToString(value);
break;
case CURVE_REF_CUSTOM:
if (model)
ret = model->curves[idx].nameToString(idx);
else
ret = CurveData().nameToString(idx);
if (value < 0)
ret.prepend(CPN_STR_SRC_INDICATOR_NEG);
break;
default:
return CPN_STR_UNKNOWN_ITEM;
}
if (verbose)
ret = tr(qPrintable(QString(typeToString(type) + "(%1)").arg(ret)));
return ret;
}
const bool CurveReference::isValueNumber() const
{
return (type == CURVE_REF_DIFF || type == CURVE_REF_EXPO) && SourceNumRef(value).isNumber();
}
const bool CurveReference::isAvailable() const
{
return true;
}
// static
int CurveReference::getDefaultValue(const CurveRefType type, const bool isGVar)
{
if (isGVar && (type == CURVE_REF_DIFF || type == CURVE_REF_EXPO))
return SourceNumRef(SOURCE_TYPE_GVAR, 1).toValue();
else if (type == CURVE_REF_FUNC)
return 1;
else
return 0;
}
// static
QString CurveReference::typeToString(const CurveRefType type)
{
const QStringList strl = { tr("Diff"), tr("Expo") , tr("Func"), tr("Custom") };
int idx = (int)type;
if (idx < 0 || idx >= strl.count())
return CPN_STR_UNKNOWN_ITEM;
return strl.at(idx);
}
// static
QString CurveReference::functionToString(const int value)
{
const QStringList strl = { "x>0", "x<0", "|x|", "f>0", "f<0", "|f|" };
int idx = value - 1;
if (idx < 0 || idx >= strl.count())
return CPN_STR_UNKNOWN_ITEM;
return strl.at(idx);
}
// static
bool CurveReference::isTypeAvailable(const CurveRefType type)
{
return true;
}
// static
bool CurveReference::isFunctionAvailable(const int value)
{
return true;
}
// static
int CurveReference::functionCount()
{
return 6;
}
/*
* CurveReferenceUIManager
*/
CurveReferenceUIManager::CurveReferenceUIManager(QComboBox * cboType, QCheckBox * chkUseSource, QSpinBox * sbxValue,
QComboBox * cboSource, QComboBox * cboCurveFunc, CurveImageWidget * curveImage,
CurveReference & curveRef, ModelData & model, CompoundItemModelFactory * sharedItemModels,
CurveRefFilteredFactory * curveRefFilteredFactory, FilteredItemModel * sourceItemModel,
QObject * parent) :
QObject(parent),
cboType(cboType),
chkUseSource(chkUseSource),
sbxValue(sbxValue),
cboSource(cboSource),
cboCurveFunc(cboCurveFunc),
curveImage(curveImage),
curveRef(curveRef),
model(model),
filteredModelFactory(curveRefFilteredFactory),
lock(false),
srcNumRefEditor(nullptr)
{
connectItemModelEvents(filteredModelFactory->getItemModel(CurveRefFilteredFactory::CRFIM_CURVE));
if (chkUseSource || sbxValue || cboSource)
srcNumRefEditor = new SourceNumRefEditor(curveRef.value, chkUseSource, sbxValue, cboSource, 0, -100, 100, 1, model, sourceItemModel);
if (cboType) {
cboType->setSizeAdjustPolicy(QComboBox::AdjustToContents);
cboType->setModel(filteredModelFactory->getItemModel(CurveRefFilteredFactory::CRFIM_TYPE));
cboType->setCurrentIndex(cboType->findData((int)curveRef.type));
connect(cboType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &CurveReferenceUIManager::cboTypeChanged);
}
if (cboCurveFunc) {
if (!cboType) {
cboCurveFunc->setModel(filteredModelFactory->getItemModel(CurveRefFilteredFactory::CRFIM_CURVE));
cboCurveFunc->setCurrentIndex(cboCurveFunc->findData(curveRef.value));
}
cboCurveFunc->setSizeAdjustPolicy(QComboBox::AdjustToContents);
cboCurveFunc->setMaxVisibleItems(10);
connect(cboCurveFunc, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &CurveReferenceUIManager::cboCurveFuncChanged);
}
if (curveImage) {
curveImage->set(&model, getCurrentFirmware(), sharedItemModels, curveRef.value, Qt::black, 3);
curveImage->setGrid(Qt::gray, 2);
connect(curveImage, &CurveImageWidget::doubleClicked, this, &CurveReferenceUIManager::curveImageDoubleClicked);
}
update();
}
CurveReferenceUIManager::~CurveReferenceUIManager()
{
delete srcNumRefEditor;
}
#define CURVE_REF_UI_SRC_SHOW (1 << 0)
#define CURVE_REF_UI_VALUE_SHOW (1 << 1)
#define CURVE_REF_UI_REF_SHOW (1 << 2)
#define CURVE_REF_UI_CURVE_SHOW (1 << 3)
void CurveReferenceUIManager::update()
{
lock = true;
if (srcNumRefEditor)
srcNumRefEditor->setLock(true);
int widgetsMask = 0;
if (srcNumRefEditor) {
if (curveRef.type == CurveReference::CURVE_REF_DIFF || curveRef.type == CurveReference::CURVE_REF_EXPO) {
srcNumRefEditor->setVisible(true);
srcNumRefEditor->update();
}
else {
srcNumRefEditor->setVisible(false);
widgetsMask |= CURVE_REF_UI_REF_SHOW;
}
}
else
widgetsMask |= CURVE_REF_UI_REF_SHOW;
if (curveRef.type == CurveReference::CURVE_REF_CUSTOM && curveRef.value != 0)
widgetsMask |= CURVE_REF_UI_CURVE_SHOW;
if (cboType)
cboType->setCurrentIndex(cboType->findData(curveRef.type));
if (cboCurveFunc) {
if (curveRef.isValueReference())
populateValueCB(cboCurveFunc);
cboCurveFunc->setVisible(widgetsMask & CURVE_REF_UI_REF_SHOW);
}
if (curveImage) {
if (widgetsMask & CURVE_REF_UI_CURVE_SHOW) {
curveImage->setIndex(curveRef.value);
if (abs(curveRef.value) > 0 && abs(curveRef.value) <= CPN_MAX_CURVES)
curveImage->setPen(colors[abs(curveRef.value) - 1], 3);
else
curveImage->setPen(Qt::black, 3);
}
curveImage->draw();
curveImage->setVisible(widgetsMask & CURVE_REF_UI_CURVE_SHOW);
}
emit resized();
if (srcNumRefEditor)
srcNumRefEditor->setLock(false);
lock = false;
}
void CurveReferenceUIManager::cboTypeChanged(int index)
{
if (!lock) {
CurveReference::CurveRefType type = (CurveReference::CurveRefType)cboType->itemData(index).toInt();
curveRef = CurveReference(type, CurveReference::getDefaultValue(type));
update();
}
}
void CurveReferenceUIManager::cboCurveFuncChanged(int index)
{
if (!lock) {
if (!cboType)
curveRef.type = CurveReference::CURVE_REF_CUSTOM;
curveRef.value = cboCurveFunc->itemData(index).toInt();
update();
}
}
void CurveReferenceUIManager::populateValueCB(QComboBox * cb)
{
if (cb) {
switch (curveRef.type) {
case CurveReference::CURVE_REF_FUNC:
cb->setModel(filteredModelFactory->getItemModel(CurveRefFilteredFactory::CRFIM_FUNC));
break;
case CurveReference::CURVE_REF_CUSTOM:
cb->setModel(filteredModelFactory->getItemModel(CurveRefFilteredFactory::CRFIM_CURVE));
break;
default:
break;
}
cb->setCurrentIndex(cb->findData(curveRef.value));
}
}
void CurveReferenceUIManager::curveImageDoubleClicked()
{
if (curveRef.type == CurveReference::CURVE_REF_CUSTOM && abs(curveRef.value) > 0)
curveImage->edit();
}
void CurveReferenceUIManager::connectItemModelEvents(const FilteredItemModel * itemModel)
{
connect(itemModel, &FilteredItemModel::aboutToBeUpdated, this, &CurveReferenceUIManager::onItemModelAboutToBeUpdated);
connect(itemModel, &FilteredItemModel::updateComplete, this, &CurveReferenceUIManager::onItemModelUpdateComplete);
}
void CurveReferenceUIManager::onItemModelAboutToBeUpdated()
{
lock = true;
}
void CurveReferenceUIManager::onItemModelUpdateComplete()
{
update();
}
+140
View File
@@ -0,0 +1,140 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "rawsource.h"
#include <QtCore>
#include <QComboBox>
#include <QCheckBox>
#include <QLabel>
#include <QSpinBox>
#include <QStandardItemModel>
class GeneralSettings;
class ModelData;
class CurveRefFilteredFactory;
class CompoundItemModelFactory;
class FilteredItemModel;
class CurveImageWidget;
class SourceNumRefEditor;
class CurveReference {
Q_DECLARE_TR_FUNCTIONS(CurveReference)
public:
enum CurveRefType {
CURVE_REF_DIFF,
CURVE_REF_EXPO,
CURVE_REF_FUNC,
CURVE_REF_CUSTOM,
MAX_CURVE_REF_TYPE = CURVE_REF_CUSTOM
};
enum CurveRefGroups {
NoneGroup = 0x001,
NegativeGroup = 0x002,
PositiveGroup = 0x004,
AllCurveRefGroups = NoneGroup | NegativeGroup | PositiveGroup,
PositiveCurveRefGroups = AllCurveRefGroups &~ NegativeGroup
};
CurveReference() { clear(); }
CurveReference(CurveRefType type, int value) : type(type), value(value) { ; }
void clear() { memset(this, 0, sizeof(CurveReference)); }
const bool isEmpty() const { return type == CURVE_REF_DIFF && value == 0; }
const bool isSet() const { return !isEmpty(); }
const bool isValueNumber() const;
const bool isValueReference() const { return !isValueNumber(); }
const QString toString(const ModelData * model = nullptr, bool verbose = true, const GeneralSettings * const generalSettings = nullptr,
Board::Type board = Board::BOARD_UNKNOWN, bool prefixCustomName = true) const;
const bool isAvailable() const;
CurveRefType type;
int value;
bool operator == ( const CurveReference & other) const {
return (this->type == other.type) && (this->value == other.value);
}
bool operator != ( const CurveReference & other) const {
return (this->type != other.type) || (this->value != other.value);
}
static int getDefaultValue(const CurveRefType type, const bool isGVar = false);
static QString typeToString(const CurveRefType type);
static QString functionToString(const int value);
static bool isTypeAvailable(const CurveRefType type);
static bool isFunctionAvailable(const int value);
static int functionCount();
};
class CurveReferenceUIManager : public QObject {
Q_OBJECT
public:
explicit CurveReferenceUIManager(QComboBox * cboType, QCheckBox * chkUseSource, QSpinBox * sbxValue, QComboBox * cboSource,
QComboBox * cboCurveFunc, CurveImageWidget * curveImage, CurveReference & curveRef,
ModelData & model, CompoundItemModelFactory * sharedItemModels,
CurveRefFilteredFactory * curveRefFilteredFactory, FilteredItemModel * sourceItemModel,
QObject * parent = nullptr);
explicit CurveReferenceUIManager(QComboBox *cboCurveFunc, CurveImageWidget * curveImage, CurveReference & curveRef, ModelData & model,
CompoundItemModelFactory * sharedItemModels, CurveRefFilteredFactory * curveRefFilteredFactory,
QObject * parent = nullptr) :
CurveReferenceUIManager(nullptr, nullptr, nullptr, nullptr, cboCurveFunc, curveImage, curveRef, model, sharedItemModels,
curveRefFilteredFactory, nullptr, parent) {}
virtual ~CurveReferenceUIManager();
signals:
void resized();
protected slots:
void cboTypeChanged(int);
void cboCurveFuncChanged(int);
void update();
void curveImageDoubleClicked();
void onItemModelAboutToBeUpdated();
void onItemModelUpdateComplete();
private:
QComboBox *cboType;
QCheckBox *chkUseSource;
QSpinBox *sbxValue;
QComboBox *cboSource;
QComboBox *cboCurveFunc;
CurveImageWidget *curveImage;
CurveReference &curveRef;
ModelData &model;
CurveRefFilteredFactory *filteredModelFactory;
bool lock;
SourceNumRefEditor *srcNumRefEditor;
void connectItemModelEvents(const FilteredItemModel * itemModel);
void populateValueCB(QComboBox * cb);
};
@@ -0,0 +1,517 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "customfunctiondata.h"
#include "eeprominterface.h"
#include "radiodata.h"
#include "radiodataconversionstate.h"
#include "compounditemmodels.h"
void CustomFunctionData::convert(RadioDataConversionState & cstate)
{
cstate.setComponent(tr("CFN"), 8);
cstate.setSubComp(nameToString(cstate.subCompIdx, (cstate.toModel() ? false : true)));
swtch.convert(cstate);
if (func == FuncVolume || func == FuncBacklight || func == FuncPlayValue || (func >= FuncAdjustGV1 && func <= FuncAdjustGVLast && (adjustMode == FUNC_ADJUST_GVAR_SOURCE || adjustMode == FUNC_ADJUST_GVAR_SOURCERAW))) {
param = RawSource(param).convert(cstate.withComponentField("PARAM")).toValue();
}
}
void CustomFunctionData::clear()
{
memset(reinterpret_cast<void *>(this), 0, sizeof(CustomFunctionData));
if (!getCurrentFirmware()->getCapability(SafetyChannelCustomFunction)) {
func = FuncTrainer;
}
}
bool CustomFunctionData::isEmpty() const
{
return (swtch.type == SWITCH_TYPE_NONE);
}
// static
QString CustomFunctionData::nameToString(const int index, const bool globalContext)
{
return RadioData::getElementName((globalContext ? tr("GF") : tr("SF")), index + 1, 0, true);
}
QString CustomFunctionData::funcToString(const ModelData * model) const
{
return funcToString(func, model);
}
// static
QString CustomFunctionData::funcToString(const AssignFunc func, const ModelData * model)
{
if (func >= FuncOverrideCH1 && func <= FuncOverrideCHLast)
return tr("Override %1").arg(RawSource(SOURCE_TYPE_CH, func + 1).toString(model));
else if (func == FuncTrainer)
return tr("Trainer Axis");
else if (func == FuncTrainerRUD)
return tr("Trainer RUD");
else if (func == FuncTrainerELE)
return tr("Trainer ELE");
else if (func == FuncTrainerTHR)
return tr("Trainer THR");
else if (func == FuncTrainerAIL)
return tr("Trainer AIL");
else if (func == FuncTrainerChannels)
return tr("Trainer Channels");
else if (func == FuncInstantTrim)
return tr("Instant Trim");
else if (func == FuncPlaySound)
return tr("Play Sound");
else if (func == FuncPlayHaptic)
return tr("Haptic");
else if (func == FuncReset)
return tr("Reset");
else if (func >= FuncSetTimer1 && func <= FuncSetTimerLast)
return tr("Set %1").arg(RawSource(SOURCE_TYPE_TIMER, func - FuncSetTimer1 + 1).toString(model));
else if (func == FuncVario)
return tr("Vario");
else if (func == FuncPlayPrompt)
return tr("Play Track");
else if (func == FuncPlayValue)
return tr("Play Value");
else if (func == FuncPlayScript)
return tr("Lua Script");
else if (func == FuncLogs)
return tr("SD Logs");
else if (func == FuncVolume)
return tr("Volume");
else if (func == FuncBacklight)
return tr("Backlight");
else if (func == FuncScreenshot)
return tr("Screenshot");
else if (func == FuncBackgroundMusic)
return tr("Background Music");
else if (func == FuncBackgroundMusicPause)
return tr("Background Music Pause");
else if (func >= FuncAdjustGV1 && func <= FuncAdjustGVLast)
return tr("Adjust %1").arg(RawSource(SOURCE_TYPE_GVAR, func - FuncAdjustGV1 + 1).toString(model));
else if (func == FuncSetFailsafe)
return tr("Set Failsafe");
else if (func == FuncRangeCheckInternalModule)
return tr("Range Check Int. Module");
else if (func == FuncRangeCheckExternalModule)
return tr("Range Check Ext. Module");
else if (func == FuncBindInternalModule)
return tr("Bind Int. Module");
else if (func == FuncBindExternalModule)
return tr("Bind Ext. Module");
else if (func == FuncRacingMode)
return tr("Racing Mode");
else if (func == FuncDisableTouch)
return tr("Disable Touch");
else if (func == FuncSetScreen)
return tr("Set Main Screen");
else if (func == FuncDisableAudioAmp)
return tr("Audio Amp Off");
else if (func == FuncRGBLed)
return tr("RGB leds");
else if (func == FuncLCDtoVideo)
return tr("LCD to Video");
else if (func >= FuncPushCustomSwitch1 && func <= FuncPushCustomSwitchLast && Boards::getCapability(getCurrentBoard(), Board::FunctionSwitches)) {
const int idx = Boards::getSwitchIndexForCFSOffset(func - FuncPushCustomSwitch1) + 1;
return tr("Push Custom Switch %1").arg(RawSource(SOURCE_TYPE_SWITCH, idx).toString(model));
}
else {
return QString(CPN_STR_UNKNOWN_ITEM);
}
}
QString CustomFunctionData::paramToString(const ModelData * model) const
{
if (!isParamAvailable())
return "";
QStringList qs;
if ((func >= FuncOverrideCH1 && func <= FuncOverrideCHLast) || func == FuncSetScreen) {
return QString("%1").arg(param);
}
else if (func == FuncLogs || (func >= FuncPushCustomSwitch1 && func <= FuncPushCustomSwitchLast)) {
return QString("%1").arg(param / 10.0) + tr("s");
}
else if (func == FuncPlaySound) {
return playSoundToString(param);
}
else if (func == FuncPlayHaptic) {
return harpicToString(param);
}
else if (func == FuncReset) {
return resetToString(param, model);
}
else if (func >= FuncSetTimer1 && func <= FuncSetTimerLast) {
div_t qrM = div(abs(param), 60);
div_t qrH = div(qrM.quot, 60);
QString val = QString("%1").arg(qrH.rem, 2, 10, QChar('0'));
if (qrH.quot > 0)
val.prepend(QString("%1:").arg(qrH.quot));
val.append(QString(":%1").arg(qrM.rem, 2, 10, QChar('0')));
if (param < 0)
val.prepend("-");
return val;
}
else if (func == FuncVolume || func == FuncPlayValue || func == FuncBacklight) {
return RawSource(param).toString(model);
}
else if (func == FuncPlayPrompt) {
return paramarm;
}
else if (func >= FuncAdjustGV1 && func <= FuncAdjustGVLast) {
switch (adjustMode) {
case FUNC_ADJUST_GVAR_CONSTANT:
return QString("%1").arg(param);
case FUNC_ADJUST_GVAR_SOURCE:
case FUNC_ADJUST_GVAR_SOURCERAW:
case FUNC_ADJUST_GVAR_GVAR:
return RawSource(param).toString();
case FUNC_ADJUST_GVAR_INCDEC:
const float val = param * model->gvarData[func - FuncAdjustGV1].multiplierGet();
const QString unit = model->gvarData[func - FuncAdjustGV1].unitToString();
return QString("%1= %2%3").arg(val < 0 ? "-" : "+").arg(abs(val)).arg(unit);
}
}
else if (func == FuncPlayScript || func == FuncRGBLed) {
return paramarm;
}
return "";
}
QString CustomFunctionData::repeatToString(const bool abbrev) const
{
return repeatToString(repeatParam, func, abbrev);
}
// static
QString CustomFunctionData::repeatToString(const int value, const AssignFunc func, const bool abbrev)
{
if (!isRepeatParamAvailable(func))
return "";
return repeatToString(value, abbrev);
}
// static
QString CustomFunctionData::repeatToString(const int value, const bool abbrev)
{
if (value == -1) {
return abbrev ? tr("!1x") : tr("Played once, not during startup");
}
else if (value == 0) {
return abbrev ? tr("1x") : tr("No repeat");
}
else {
return abbrev ? tr("%1s").arg(value) : tr("Repeat %1s").arg(value);
}
}
QString CustomFunctionData::enabledToString() const
{
if (!enabled) {
return tr("DISABLED");
}
return "";
}
// static
bool CustomFunctionData::isFuncAvailable(const int index, const ModelData * model)
{
Firmware * fw = getCurrentFirmware();
bool ret = (((index >= FuncOverrideCH1 && index <= FuncOverrideCHLast) && !fw->getCapability(SafetyChannelCustomFunction)) ||
((index == FuncPlayHaptic) && !fw->getCapability(Haptic)) ||
((index >= FuncSetTimer1 && index <= FuncSetTimerLast) &&
(index > FuncSetTimer1 + fw->getCapability(Timers) ||
(model ? model->timers[index - FuncSetTimer1].isModeOff() : false))) ||
((index >= FuncRangeCheckInternalModule && index <= FuncBindExternalModule) && !fw->getCapability(DangerousFunctions)) ||
((index >= FuncAdjustGV1 && index <= FuncAdjustGVLast) && ((index - FuncAdjustGV1) >= fw->getCapability(Gvars))) ||
((index == FuncDisableAudioAmp && !Boards::getCapability(fw->getBoard(), Board::HasAudioMuteGPIO))) ||
((index == FuncRGBLed && !(Boards::getCapability(fw->getBoard(), Board::HasBlingLEDS) || Boards::getCapability(fw->getBoard(), Board::FunctionSwitchColors)))) ||
((index == FuncLCDtoVideo && !IS_FATFISH_F16(fw->getBoard()))) ||
((index >= FuncPushCustomSwitch1 && index <= FuncPushCustomSwitchLast) &&
!Boards::isSwitchFunc(Boards::getSwitchIndexForCFSOffset(index - FuncPushCustomSwitch1)))
);
return !ret;
}
// static
int CustomFunctionData::funcContext(const int index)
{
int ret = AllFunctionContexts;
if ((index >= FuncOverrideCH1 && index <= FuncOverrideCHLast) ||
(index >= FuncRangeCheckInternalModule && index <= FuncBindExternalModule) ||
(index >= FuncAdjustGV1 && index <= FuncAdjustGVLast) ||
(index >= FuncPushCustomSwitch1 && index <= FuncPushCustomSwitchLast))
ret &= ~GlobalFunctionsContext;
return ret;
}
// static
QString CustomFunctionData::resetToString(const int value, const ModelData * model)
{
Firmware * firmware = getCurrentFirmware();
int step = CPN_MAX_TIMERS;
if (value < step) {
if (value < firmware->getCapability(Timers))
return RawSource(SOURCE_TYPE_TIMER, value + 1).toString(model);
else
return QString(CPN_STR_UNKNOWN_ITEM);
}
if (value < ++step)
return tr("Session");
if (value < ++step)
return tr("Telemetry");
if (value < ++step)
return tr("Trims");
if (value < step + firmware->getCapability(Sensors))
return RawSource(SOURCE_TYPE_TELEMETRY, 3 * (value - step) + 1).toString(model);
return QString(CPN_STR_UNKNOWN_ITEM);
}
#define RESET_SENSORS_START CPN_MAX_TIMERS + 3 // keep in sync with above
// static
int CustomFunctionData::resetParamCount()
{
Firmware * firmware = getCurrentFirmware();
return RESET_SENSORS_START + firmware->getCapability(Sensors);
}
// static
bool CustomFunctionData::isResetParamAvailable(const int index, const ModelData * model)
{
Firmware * firmware = getCurrentFirmware();
if (index < CPN_MAX_TIMERS) {
if (index < firmware->getCapability(Timers) && (model ? !model->timers[index].isModeOff() : true))
return true;
else
return false;
}
else if (index < RESET_SENSORS_START)
return true;
else if (model && index < resetParamCount())
return model->sensorData[index - (RESET_SENSORS_START)].isAvailable();
return false;
}
QString CustomFunctionData::harpicToString() const
{
return harpicToString(param);
}
// static
QString CustomFunctionData::harpicToString(const int value)
{
return QString("%1").arg(value);
}
// static
QStringList CustomFunctionData::playSoundStringList()
{
return QStringList({ tr("Beep 1"), tr("Beep 2"), tr("Beep 3"), tr("Warn 1"), tr("Warn 2"), tr("Cheep"), tr("Ratata"), tr("Tick"),
tr("Siren"), tr("Ring"), tr("Sci Fi"), tr("Robot"), tr("Chirp"), tr("Tada"), tr("Cricket"), tr("Alarm Clock") });
}
QString CustomFunctionData::playSoundToString() const
{
return playSoundToString(param);
}
// static
QString CustomFunctionData::playSoundToString(const int value)
{
const QStringList strl = playSoundStringList();
if (value < strl.count())
return strl.at(value);
else
return QString(CPN_STR_UNKNOWN_ITEM);
}
QString CustomFunctionData::gvarAdjustModeToString() const
{
return gvarAdjustModeToString(adjustMode);
}
// static
QString CustomFunctionData::gvarAdjustModeToString(const int value)
{
switch (value) {
case FUNC_ADJUST_GVAR_CONSTANT:
return tr("Constant");
case FUNC_ADJUST_GVAR_SOURCE:
return tr("Source (%)");
case FUNC_ADJUST_GVAR_SOURCERAW:
return tr("Source (value)");
case FUNC_ADJUST_GVAR_GVAR:
return tr("Global Variable");
case FUNC_ADJUST_GVAR_INCDEC:
return tr("Inc/Decrement");
default:
return QString(CPN_STR_UNKNOWN_ITEM);
}
}
// static
AbstractStaticItemModel * CustomFunctionData::repeatItemModel()
{
AbstractStaticItemModel * mdl = new AbstractStaticItemModel();
mdl->setName("customfunctiondata.repeat");
for (int i = -1; i <= 60; i++) {
mdl->appendToItemList(repeatToString(i, false), i);
}
mdl->loadItemList();
return mdl;
}
// static
AbstractStaticItemModel * CustomFunctionData::repeatLuaItemModel()
{
AbstractStaticItemModel * mdl = new AbstractStaticItemModel();
mdl->setName("customfunctiondata.repeatLua");
mdl->appendToItemList(tr("On"), 0);
mdl->appendToItemList(tr("1x"), 1);
mdl->loadItemList();
return mdl;
}
// static
AbstractStaticItemModel * CustomFunctionData::repeatSetScreenItemModel()
{
AbstractStaticItemModel * mdl = new AbstractStaticItemModel();
mdl->setName("customfunctiondata.repeatSetScreen");
mdl->appendToItemList(repeatToString(-1, false), -1);
mdl->appendToItemList(repeatToString(0, false), 0);
mdl->loadItemList();
return mdl;
}
// static
AbstractStaticItemModel * CustomFunctionData::playSoundItemModel()
{
AbstractStaticItemModel * mdl = new AbstractStaticItemModel();
mdl->setName("customfunctiondata.playsound");
for (int i = 0; i < playSoundStringList().count(); i++) {
mdl->appendToItemList(playSoundToString(i), i);
}
mdl->loadItemList();
return mdl;
}
// static
AbstractStaticItemModel * CustomFunctionData::harpicItemModel()
{
AbstractStaticItemModel * mdl = new AbstractStaticItemModel();
mdl->setName("customfunctiondata.harpic");
for (int i = 0; i <= 3; i++) {
mdl->appendToItemList(harpicToString(i), i);
}
mdl->loadItemList();
return mdl;
}
// static
AbstractStaticItemModel * CustomFunctionData::gvarAdjustModeItemModel()
{
AbstractStaticItemModel * mdl = new AbstractStaticItemModel();
mdl->setName("customfunctiondata.gvaradjustmode");
for (int i = 0; i < FUNC_ADJUST_GVAR_COUNT; i++) {
mdl->appendToItemList(gvarAdjustModeToString(i), i);
}
mdl->loadItemList();
return mdl;
}
bool CustomFunctionData::isParamAvailable() const
{
// not available list
const QList<AssignFunc> funcList = {
FuncTrainer,
FuncTrainerRUD,
FuncTrainerELE,
FuncTrainerTHR,
FuncTrainerAIL,
FuncTrainerChannels,
FuncInstantTrim,
FuncVario,
FuncScreenshot,
FuncBackgroundMusicPause,
FuncSetFailsafe,
FuncRangeCheckInternalModule,
FuncRangeCheckExternalModule,
FuncBindInternalModule,
FuncBindExternalModule,
FuncRacingMode,
FuncDisableTouch,
FuncDisableAudioAmp
};
return funcList.contains(func) ? false : true;
}
bool CustomFunctionData::isRepeatParamAvailable() const
{
return isRepeatParamAvailable(func);
}
// static
bool CustomFunctionData::isRepeatParamAvailable(const AssignFunc func)
{
const QList<AssignFunc> funcList = {
FuncPlaySound,
FuncPlayHaptic,
FuncPlayValue,
FuncPlayPrompt,
FuncSetScreen
};
return funcList.contains(func) ? true : false;
}
@@ -0,0 +1,147 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "boards.h"
#include "constants.h"
#include "rawswitch.h"
#include <QtCore>
#include <QComboBox>
class Firmware;
class ModelData;
class GeneralSettings;
class RadioDataConversionState;
class AbstractStaticItemModel;
enum AssignFunc {
FuncOverrideCH1 = 0,
FuncOverrideCHLast = FuncOverrideCH1 + CPN_MAX_CHNOUT - 1,
FuncTrainer,
FuncTrainerRUD,
FuncTrainerELE,
FuncTrainerTHR,
FuncTrainerAIL,
FuncTrainerChannels,
FuncInstantTrim,
FuncPlaySound,
FuncPlayHaptic,
FuncReset,
FuncSetTimer1,
FuncSetTimerLast = FuncSetTimer1 + CPN_MAX_TIMERS - 1,
FuncVario,
FuncPlayPrompt,
FuncPlayValue,
FuncPlayScript,
FuncLogs,
FuncVolume,
FuncBacklight,
FuncScreenshot,
FuncBackgroundMusic,
FuncBackgroundMusicPause,
FuncAdjustGV1,
FuncAdjustGVLast = FuncAdjustGV1 + CPN_MAX_GVARS - 1,
FuncSetFailsafe,
FuncRangeCheckInternalModule,
FuncRangeCheckExternalModule,
FuncBindInternalModule,
FuncBindExternalModule,
FuncRacingMode,
FuncDisableTouch,
FuncSetScreen,
FuncDisableAudioAmp,
FuncRGBLed,
FuncLCDtoVideo,
FuncPushCustomSwitch1,
FuncPushCustomSwitchLast = FuncPushCustomSwitch1 + CPN_MAX_SWITCHES_FUNCTION - 1,
FuncCount,
FuncReserve = -1
};
enum GVarAdjustModes
{
FUNC_ADJUST_GVAR_CONSTANT,
FUNC_ADJUST_GVAR_SOURCE,
FUNC_ADJUST_GVAR_SOURCERAW,
FUNC_ADJUST_GVAR_GVAR,
FUNC_ADJUST_GVAR_INCDEC,
FUNC_ADJUST_GVAR_COUNT
};
class CustomFunctionData {
Q_DECLARE_TR_FUNCTIONS(CustomFunctionData)
public:
enum CustomFunctionContext
{
GlobalFunctionsContext = 0x01,
SpecialFunctionsContext = 0x02,
AllFunctionContexts = GlobalFunctionsContext | SpecialFunctionsContext
};
CustomFunctionData(AssignFunc func = FuncOverrideCH1) { clear(); this->func = func; }
RawSwitch swtch;
AssignFunc func;
int param;
char paramarm[10];
unsigned int enabled; // TODO perhaps not any more the right name
unsigned int adjustMode;
int repeatParam;
void convert(RadioDataConversionState & cstate);
void clear();
bool isEmpty() const;
QString funcToString(const ModelData * model = nullptr) const;
QString paramToString(const ModelData * model = nullptr) const;
QString repeatToString(const bool abbrev) const;
QString enabledToString() const;
QString playSoundToString() const;
QString harpicToString() const;
QString gvarAdjustModeToString() const;
bool isRepeatParamAvailable() const;
bool isParamAvailable() const;
static QString nameToString(const int index, const bool globalContext = false);
static QString funcToString(const AssignFunc func, const ModelData * model = nullptr);
static bool isFuncAvailable(const int index, const ModelData * model = nullptr);
static int funcContext(const int index);
static QString resetToString(const int value, const ModelData * model = nullptr);
static int resetParamCount();
static bool isResetParamAvailable(const int index, const ModelData * model = nullptr);
static QString repeatToString(const int value, const AssignFunc func, const bool abbrev);
static QString repeatToString(const int value, const bool abbrev);
static bool isRepeatParamAvailable(const AssignFunc func);
static QStringList playSoundStringList();
static QString playSoundToString(const int value);
static QString harpicToString(const int value);
static QStringList gvarAdjustModeStringList();
static QString gvarAdjustModeToString(const int value);
static AbstractStaticItemModel * repeatItemModel();
static AbstractStaticItemModel * repeatLuaItemModel();
static AbstractStaticItemModel * repeatSetScreenItemModel();
static AbstractStaticItemModel * playSoundItemModel();
static AbstractStaticItemModel * harpicItemModel();
static AbstractStaticItemModel * gvarAdjustModeItemModel();
};
@@ -0,0 +1,293 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "customisation_data.h"
#include "eeprominterface.h"
#include "helpers.h"
// cannot use QColor so use formula from libopenui_defines.h
#define RADIO_RGB(r, g, b) \
(uint16_t)((((r)&0xF8) << 8) + (((g)&0xFC) << 3) + (((b)&0xF8) >> 3))
#define WHITE RADIO_RGB(0xFF, 0xFF, 0xFF)
#define RED RADIO_RGB(229, 32, 30)
// based on radio/src/gui/colorlcd/libui/etx_lv_theme.h
int layoutValueScaled(int value)
{
Firmware *firmware = getCurrentFirmware();
Board::Type board = firmware->getBoard();
if (firmware->getCapability(IsLandscape)) {
if (Boards::getCapability(board, Board::LcdWidth) == 320)
return ((value * 8 + 5) / 10);
else if (Boards::getCapability(board, Board::LcdWidth) == 800)
return ((value * 11 + 4) / 8);
}
return value;
}
ZoneOptionValue::ZoneOptionValue()
{
clear();
}
ZoneOptionValue::ZoneOptionValue(const ZoneOptionValue & src)
{
copy(src);
}
ZoneOptionValue & ZoneOptionValue::operator=(const ZoneOptionValue & src)
{
copy(src);
return *this;
}
void ZoneOptionValue::copy(const ZoneOptionValue & src)
{
unsignedValue = src.unsignedValue;
signedValue = src.signedValue;
boolValue = src.boolValue;
stringValue = src.stringValue;
sourceValue = src.sourceValue;
colorValue = src.colorValue;
}
bool ZoneOptionValue::isEmpty() const
{
return unsignedValue == 0 && signedValue == 0 && boolValue == 0 && colorValue == 0 &&
stringValue.empty() && sourceValue.toValue() == 0;
}
void ZoneOptionValue::clear()
{
unsignedValue = 0;
signedValue = 0;
boolValue = 0;
stringValue.clear();
sourceValue.clear();
colorValue = 0;
}
inline void setZoneOptionValue(ZoneOptionValue& zov, bool value)
{
zov.boolValue = value;
}
inline void setZoneOptionValue(ZoneOptionValue& zov, int value)
{
zov.signedValue = value;
}
inline void setZoneOptionValue(ZoneOptionValue& zov, const char* value)
{
zov.stringValue = value;
}
inline void setZoneOptionValue(ZoneOptionValue& zov, unsigned int value)
{
zov.unsignedValue = value;
}
inline ZoneOptionValueEnum zoneValueEnumFromType(ZoneOption::Type type)
{
switch(type) {
case ZoneOption::String:
case ZoneOption::File:
return ZOV_String;
case ZoneOption::Integer:
return ZOV_Signed;
case ZoneOption::Bool:
return ZOV_Bool;
case ZoneOption::Color:
return ZOV_Color;
case ZoneOption::Source:
return ZOV_Source;
default:
return ZOV_Unsigned;
}
}
inline const char * zoneOptionValueEnumToString(ZoneOptionValueEnum zovenum) {
switch (zovenum) {
case ZOV_Unsigned:
return "unsigned";
case ZOV_Signed:
return "signed";
case ZOV_Bool:
return "bool";
case ZOV_String:
return "string";
default:
return "unknown";
}
}
ZoneOptionValueTyped::ZoneOptionValueTyped()
{
clear();
}
bool ZoneOptionValueTyped::isEmpty() const
{
return type == ZOV_Unsigned && value.isEmpty();
}
void ZoneOptionValueTyped::clear()
{
type = ZOV_Unsigned;
value.clear();
}
void WidgetPersistentData::clear()
{
for (int i = 0; i < MAX_WIDGET_OPTIONS; i += 1)
options[i].clear();
}
ZonePersistentData::ZonePersistentData(const ZonePersistentData & src)
{
copy(src);
}
ZonePersistentData & ZonePersistentData::operator=(const ZonePersistentData & src)
{
copy(src);
return *this;
}
void ZonePersistentData::copy(const ZonePersistentData & src)
{
widgetName = src.widgetName;
widgetData = src.widgetData;
}
bool ZonePersistentData::isEmpty() const
{
return widgetName.empty();
}
void ZonePersistentData::clear()
{
widgetName.clear();
widgetData.clear();
}
RadioLayout::CustomScreenData::CustomScreenData(const RadioLayout::CustomScreenData & src)
{
copy(src);
}
RadioLayout::CustomScreenData & RadioLayout::CustomScreenData::operator=(const RadioLayout::CustomScreenData & src)
{
copy(src);
return *this;
}
void RadioLayout::CustomScreenData::copy(const RadioLayout::CustomScreenData & src)
{
layoutId = src.layoutId;
layoutPersistentData = src.layoutPersistentData;
}
bool RadioLayout::CustomScreenData::isEmpty() const
{
return layoutId.empty();
}
void RadioLayout::CustomScreenData::clear()
{
layoutId.clear();
layoutPersistentData.clear();
}
RadioLayout::CustomScreens::CustomScreens(const RadioLayout::CustomScreens & src)
{
copy(src);
}
RadioLayout::CustomScreens & RadioLayout::CustomScreens::operator=(const RadioLayout::CustomScreens & src)
{
copy(src);
return *this;
}
void RadioLayout::CustomScreens::copy(const RadioLayout::CustomScreens & src)
{
for (int i = 0; i < MAX_CUSTOM_SCREENS; i++)
customScreenData[i] = src.customScreenData[i];
}
void RadioLayout::CustomScreens::clear()
{
for (int i = 0; i < MAX_CUSTOM_SCREENS; i++)
customScreenData[i].clear();
}
void RadioLayout::init(const std::string layoutId, CustomScreens& customScreens)
{
customScreens.clear();
for (int i = 0; i < MAX_CUSTOM_SCREENS; i++) {
if (i == 0)
customScreens.customScreenData[i].layoutId = layoutId;
LayoutPersistentData& persistentData =
customScreens.customScreenData[i].layoutPersistentData;
int j = 0;
persistentData.options[j].type =
zoneValueEnumFromType(ZoneOption::Type::Bool);
setZoneOptionValue(persistentData.options[j++].value, (bool)true);
persistentData.options[j].type =
zoneValueEnumFromType(ZoneOption::Type::Bool);
setZoneOptionValue(persistentData.options[j++].value, (bool)true);
persistentData.options[j].type =
zoneValueEnumFromType(ZoneOption::Type::Bool);
setZoneOptionValue(persistentData.options[j++].value, (bool)true);
persistentData.options[j].type =
zoneValueEnumFromType(ZoneOption::Type::Bool);
setZoneOptionValue(persistentData.options[j++].value, (bool)true);
persistentData.options[j].type =
zoneValueEnumFromType(ZoneOption::Type::Bool);
setZoneOptionValue(persistentData.options[j++].value, (bool)false);
}
}
// based on radio/src/gui/colorlcd/mainview/datastructs_screen.h
int RadioLayout::topBarZones()
{
Firmware *firmware = getCurrentFirmware();
Board::Type board = firmware->getBoard();
const int menuHeaderButtonsLeft = layoutValueScaled(47);
const int topBarZoneWidth = layoutValueScaled((firmware->getCapability(IsWideLayout) ? 74 : 70));
return rangeCheck(((Boards::getCapability(board, Board::LcdWidth) - menuHeaderButtonsLeft - 1 +
topBarZoneWidth / 2) / topBarZoneWidth), 0, MAX_TOPBAR_ZONES, 0);
}
@@ -0,0 +1,195 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "constants.h"
#include "rawsource.h"
#include <QtCore>
/*
Note: this code is consolidated from various radio/src files and modified if necessary to run on desktop
*/
constexpr int MAX_CUSTOM_SCREENS {10};
constexpr int MAX_THEME_OPTIONS {5};
constexpr int MAX_LAYOUT_ZONES {10};
constexpr int MAX_LAYOUT_OPTIONS {10};
constexpr int MAX_WIDGET_OPTIONS {50};
constexpr int MAX_TOPBAR_ZONES {7}; // 4 for portrait LCD, 6 for standard LCD, 7 for wide screen LCD
constexpr int MAX_TOPBAR_OPTIONS {1};
// Common 'ZoneOptionValue's among all layouts
enum {
LAYOUT_OPTION_TOPBAR = 0,
LAYOUT_OPTION_FM,
LAYOUT_OPTION_SLIDERS,
LAYOUT_OPTION_TRIMS,
LAYOUT_OPTION_MIRRORED,
LAYOUT_OPTION_LAST_DEFAULT=LAYOUT_OPTION_MIRRORED
};
struct ZoneOptionValue // union in radio/src/datastructs.h
{
unsigned int unsignedValue;
int signedValue;
unsigned int boolValue;
std::string stringValue;
RawSource sourceValue;
unsigned int colorValue;
ZoneOptionValue();
ZoneOptionValue(const ZoneOptionValue & src);
ZoneOptionValue & operator=(const ZoneOptionValue & src);
void clear();
void copy(const ZoneOptionValue & src);
bool isEmpty() const;
};
enum ZoneOptionValueEnum {
ZOV_Unsigned,
ZOV_Signed,
ZOV_Bool,
ZOV_String,
ZOV_Source,
ZOV_Color,
ZOV_LAST = ZOV_Color
};
enum ZoneOptionAlign
{
ALIGN_LEFT,
ALIGN_CENTER,
ALIGN_RIGHT,
ALIGN_COUNT
};
struct ZoneOption
{
enum Type {
Integer,
Source,
Bool,
String,
TextSize,
Timer,
Switch,
Color,
Align,
Slider,
Choice,
File,
};
const char * name;
Type type;
ZoneOptionValue deflt;
ZoneOptionValue min;
ZoneOptionValue max;
};
struct ZoneOptionValueTyped
{
ZoneOptionValueEnum type;
ZoneOptionValue value;
ZoneOptionValueTyped();
void clear();
bool isEmpty() const;
};
struct WidgetPersistentData {
ZoneOptionValueTyped options[MAX_WIDGET_OPTIONS];
WidgetPersistentData() {}
void clear();
};
struct ZonePersistentData {
std::string widgetName;
WidgetPersistentData widgetData;
ZonePersistentData() {}
ZonePersistentData(const ZonePersistentData & src);
ZonePersistentData & operator=(const ZonePersistentData & src);
void clear();
void copy(const ZonePersistentData & src);
bool isEmpty() const;
};
template<int N, int O>
struct WidgetsContainerPersistentData {
ZonePersistentData zones[N];
ZoneOptionValueTyped options[O];
WidgetsContainerPersistentData() {}
void clear() {
for (int i = 0; i < N; i++) {
zones[i].clear();
}
for (int i = 0; i < O; i++) {
options[i].clear();
}
}
};
typedef WidgetsContainerPersistentData<MAX_LAYOUT_ZONES, MAX_LAYOUT_OPTIONS>
LayoutPersistentData;
typedef WidgetsContainerPersistentData<MAX_TOPBAR_ZONES, MAX_TOPBAR_OPTIONS>
TopBarPersistentData;
class RadioLayout
{
Q_DECLARE_TR_FUNCTIONS(RadioLayout)
public:
struct CustomScreenData {
std::string layoutId;
LayoutPersistentData layoutPersistentData;
CustomScreenData() {}
CustomScreenData(const CustomScreenData & src);
CustomScreenData & operator=(const CustomScreenData & src);
void clear();
void copy(const CustomScreenData & src);
bool isEmpty() const;
};
struct CustomScreens {
CustomScreenData customScreenData[MAX_CUSTOM_SCREENS];
CustomScreens() {}
CustomScreens(const CustomScreens & src);
CustomScreens & operator=(const CustomScreens & src);
void clear();
void copy(const CustomScreens & src);
};
static void init(const std::string layoutId, CustomScreens & customScreens);
static int topBarZones();
};
+140
View File
@@ -0,0 +1,140 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "datahelpers.h"
QString DataHelpers::boolToString(const bool value, const BoolFormat format)
{
static const char *strings[] = {
QT_TRANSLATE_NOOP("DataHelpers", "Disabled"),
QT_TRANSLATE_NOOP("DataHelpers", "Enabled"),
QT_TRANSLATE_NOOP("DataHelpers", "OFF"),
QT_TRANSLATE_NOOP("DataHelpers", "ON"),
QT_TRANSLATE_NOOP("DataHelpers", "False"),
QT_TRANSLATE_NOOP("DataHelpers", "True"),
QT_TRANSLATE_NOOP("DataHelpers", "N"),
QT_TRANSLATE_NOOP("DataHelpers", "Y"),
QT_TRANSLATE_NOOP("DataHelpers", "No"),
QT_TRANSLATE_NOOP("DataHelpers", "Yes")
};
return QCoreApplication::translate("DataHelpers", strings[format * 2 + value]);
}
QString DataHelpers::getElementName(const QString & prefix, const unsigned int index, const char * name, const bool padding)
{
QString result = prefix;
result.append(padding ? QString("%1").arg(index, 2, 10, QChar('0')) : QString("%1").arg(index));
if (name && QString(name).trimmed().length() > 0)
result.append(":" + QString(name).trimmed());
return result;
}
QString DataHelpers::timeToString(const int value, const unsigned int mask)
{
bool negative = value < 0 ? true : false;
int val = abs(value);
QString result = QString("%1").arg(negative ? "-" : ((mask & TIMESTR_MASK_PADSIGN) ? " " : ""));
if (mask & TIMESTR_MASK_HRSMINS) {
int hours = val / 3600;
if (hours > 0 || (mask & TIMESTR_MASK_ZEROHRS)) {
val -= hours * 3600;
result.append(QString("%1:").arg(hours, 2, 10, QLatin1Char('0')));
}
}
int minutes = val / 60;
int seconds = val % 60;
result.append(QString("%1:%2").arg(minutes, 2, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0')));
return result;
}
int DataHelpers::getStringTagMappingIndex(const StringTagMappingTable& lut, const char * tag)
{
auto it =
find_if(lut.begin(), lut.end(), [=](const StringTagMapping& elmt) {
if (elmt.tag == tag) return true;
return false;
});
if (it != lut.end()) {
return it - lut.begin();
}
return -1;
}
std::string DataHelpers::getStringTagMappingTag(const StringTagMappingTable& lut, unsigned int index)
{
if (index < lut.size())
return lut[index].tag;
return std::string();
}
std::string DataHelpers::getStringNameMappingTag(const StringTagMappingTable& lut, const char * name)
{
auto it =
find_if(lut.begin(), lut.end(), [=](const StringTagMapping& elmt) {
if (elmt.name == name) return true;
return false;
});
if (it != lut.end()) {
return it->tag;
}
return std::string();
}
std::string DataHelpers::getStringTagMappingName(const StringTagMappingTable& lut, const char * tag)
{
auto it =
find_if(lut.begin(), lut.end(), [=](const StringTagMapping& elmt) {
if (elmt.tag == tag) return true;
return false;
});
if (it != lut.end()) {
return it->name;
}
return std::string();
}
QString DataHelpers::getCompositeName(const QString defaultName, const QString customName, const bool prefixCustom)
{
QString result;
if (customName.trimmed().isEmpty() || prefixCustom)
result = defaultName;
if (!customName.trimmed().isEmpty()) {
if (prefixCustom)
result.append(":");
result.append(customName.trimmed());
}
return result;
}
+119
View File
@@ -0,0 +1,119 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include <QtCore>
#include <string>
#include <vector>
struct StringTagMapping {
std::string name;
std::string tag;
StringTagMapping() = default;
StringTagMapping(const char* name) :
name(name), tag(name)
{
}
StringTagMapping(const std::string& name) :
name(name), tag(name)
{
}
StringTagMapping(const char* name, const char* tag) :
name(name), tag(tag)
{
}
StringTagMapping(const std::string& name, const std::string& tag) :
name(name), tag(tag)
{
}
};
typedef std::vector<StringTagMapping> StringTagMappingTable;
#define STRINGTAGMAPPINGFUNCS_HELPER(tbl, name) \
inline int name##Index (const char * tag) \
{ \
return DataHelpers::getStringTagMappingIndex(tbl, tag); \
} \
\
inline std::string name##Tag (unsigned int index) \
{ \
return DataHelpers::getStringTagMappingTag(tbl, index); \
} \
\
inline std::string name##Name (const char * tag) \
{ \
return DataHelpers::getStringTagMappingName(tbl, tag); \
}
#define STRINGTAGMAPPINGFUNCS(tbl, name) STRINGTAGMAPPINGFUNCS_HELPER(tbl, get##name)
class FieldRange
{
public:
FieldRange():
decimals(0),
min(0.0),
max(0.0),
step(1.0),
offset(0.0),
prefix(""),
unit("")
{
}
float getValue(int value) { return float(value) * step; }
int decimals;
double min;
double max;
double step;
double offset;
QString prefix;
QString unit;
};
constexpr unsigned int TIMESTR_MASK_HRSMINS { 1 << 1 };
constexpr unsigned int TIMESTR_MASK_ZEROHRS { 1 << 2 };
constexpr unsigned int TIMESTR_MASK_PADSIGN { 1 << 3 };
namespace DataHelpers
{
enum BoolFormat {
BOOL_FMT_ENABLEDISABLE,
BOOL_FMT_ONOFF,
BOOL_FMT_TRUEFALSE,
BOOL_FMT_YN,
BOOL_FMT_YESNO
};
QString boolToString(const bool value, const BoolFormat format);
QString getElementName(const QString & prefix, const unsigned int index, const char * name = 0, const bool padding = false);
QString timeToString(const int value, const unsigned int mask);
int getStringTagMappingIndex(const StringTagMappingTable& lut, const char * tag);
std::string getStringTagMappingTag(const StringTagMappingTable& lut, unsigned int index);
std::string getStringNameMappingTag(const StringTagMappingTable& lut, const char * name);
std::string getStringTagMappingName(const StringTagMappingTable& lut, const char * tag);
QString getCompositeName(const QString defaultName, const QString customName, const bool prefixCustom);
}
@@ -0,0 +1,223 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "edgetxinterface.h"
#include "crc.h"
#include <iostream>
#include <string>
#include "yaml_ops.h"
#include "yaml_generalsettings.h"
#include "yaml_modeldata.h"
#include "labelvalidator.h"
#include <QMessageBox>
static uint16_t calculateChecksum(const QByteArray& data, uint16_t checksum)
{
std::string fileContent = data.toStdString();
size_t startPos = 0;
size_t pos = 0;
uint8_t isCRLF = 0;
std::string newLine[2] = {"\n","\r\n"};
pos = fileContent.find("\n", 0);
if(pos == 0) {
return checksum;
}
if(fileContent[pos-1] == '\r') {
isCRLF = 1;
qDebug() << "** File has CRLF line endings";
} else {
isCRLF = 0;
qDebug() << "** File has LF line endings";
}
// The first line contains the "checksum" value - if present. Skip it
if ( fileContent.find("checksum:") == 0) {
startPos = fileContent.find(newLine[isCRLF]) + newLine[isCRLF].length();
}
checksum = crc16(0, (const uint8_t *) &fileContent[startPos], fileContent.length() - startPos, checksum);
return checksum;
}
static YAML::Node loadYamlFromByteArray(const QByteArray& data)
{
// TODO: use real streaming to avoid memory copies
std::istringstream data_istream(data.toStdString());
return YAML::Load(data_istream);
}
static void writeYamlToByteArray(const YAML::Node& node, QByteArray& data, bool addChecksum = false)
{
// TODO: use real streaming to avoid memory copies
std::stringstream data_ostream;
data_ostream << node;
data = QByteArray::fromStdString(data_ostream.str());
//qDebug() << "Saving YAML:";
if(addChecksum) {
uint16_t checksum = 0xFFFF;
checksum = calculateChecksum(data, checksum);
std::stringstream checksum_ostream;
checksum_ostream << "checksum: " << checksum << std::endl;
data.prepend(checksum_ostream.str().c_str());
}
//qDebug() << data.toStdString().c_str();
}
bool loadLabelsListFromYaml(RadioData::ModelLabels& labels,
int& sortOrder,
EtxModelfiles& modelFiles,
const QByteArray& data)
{
sortOrder = 0;
labels.clear();
if (data.size() == 0)
return true;
YAML::Node node = loadYamlFromByteArray(data);
if (!node.IsMap()) return false; // Root Map (Labels, Sort, Models)
if (node["Sort"].IsScalar()) {
bool ok = false;
sortOrder = QString::fromStdString(node["Sort"].Scalar()).toUInt(&ok);
if (!ok)
sortOrder = 0;
}
YAML::Node lbls = node["Labels"];
if (lbls.IsMap()) {
for (YAML::const_iterator it=lbls.begin(); it!=lbls.end(); ++it) {
std::string lbl = it->first.as<std::string>();
RadioData::LabelData ld;
ld.name = QString::fromStdString(lbl);
YamlValidateLabel(ld.name);
if (!ld.name.isEmpty()) {
if (lbls[lbl]["selected"])
ld.selected = lbls[lbl]["selected"].as<bool>();
else
ld.selected = false;
labels.append(ld);
}
}
}
return true;
}
bool loadModelFromYaml(ModelData& model, const QByteArray& data)
{
YAML::Node node = loadYamlFromByteArray(data);
node >> model;
return true;
}
bool loadRadioSettingsFromYaml(GeneralSettings& settings, const QByteArray& data)
{
if(data.indexOf("checksum:") == 0) {
int startPos = strlen("checksum:");
int endPos = data.indexOf("\n");
if (endPos > 0) {
while( ((const char)data[startPos] == ' ') && (startPos < data.size())) {
startPos++;
}
if (startPos < data.size()) {
QByteArray checksumStr = data.mid(startPos, endPos - startPos);
uint16_t fileChecksum = std::stoi(checksumStr.toStdString());
uint16_t calculatedChecksum = calculateChecksum(data, 0xFFFF);
qDebug() << "File checksum:" << fileChecksum;
qDebug() << "Calculated checksum:" << calculatedChecksum;
if (fileChecksum != calculatedChecksum) {
qDebug() << "File checksum mismatch! Got: " << fileChecksum << ", expected: " << calculatedChecksum;
QMessageBox::critical(NULL, CPN_STR_APP_NAME, QCoreApplication::translate("EdgeTXInterface", "Radio settings file checksum error. You are advised to review the settings"));
//return false;
}
}
}
}
YAML::Node node = loadYamlFromByteArray(data);
node >> settings;
if (settings.version < CPN_CURRENT_SETTINGS_VERSION) {
qDebug() << "Older version of settings detected:" << settings.version;
}
else if (settings.version > CPN_CURRENT_SETTINGS_VERSION) {
qDebug() << "Newer version of settings detected:" << settings.version;
QMessageBox::critical(NULL, CPN_STR_APP_NAME, QCoreApplication::translate("EdgeTXInterface", "Companion does not support settings version %1!").arg(settings.version));
return false;
}
return true;
}
bool writeLabelsListToYaml(const RadioData &radioData, QByteArray& data)
{
YAML::Node node;
foreach(RadioData::LabelData label, radioData.labels) {
node["Labels"][label.name.toStdString()] = YAML::Null;
if (label.selected)
node["Labels"][label.name.toStdString()]["selected"] = true;
}
node["Sort"] = radioData.sortOrder;
writeYamlToByteArray(node, data);
return true;
}
bool writeModelToYaml(const ModelData& model, QByteArray& data)
{
YAML::Node node;
node = model;
writeYamlToByteArray(node, data);
return true;
}
bool writeRadioSettingsToYaml(const GeneralSettings& settings, QByteArray& data)
{
YAML::Node node;
node = settings;
writeYamlToByteArray(node, data, true);
return true;
}
std::string patchFilenameToYaml(const std::string& str)
{
constexpr const char MODELS_EXT[] = ".bin";
constexpr const char YAML_EXT[] = ".yml";
constexpr unsigned bin_len = sizeof(MODELS_EXT) - 1;
// patch file extension
auto const dot_pos = str.find_last_of('.');
if ((dot_pos != std::string::npos) && (str.substr(dot_pos) == MODELS_EXT)) {
std::string tmp = str;
return tmp.replace(dot_pos, bin_len, YAML_EXT);
}
return str;
}
@@ -0,0 +1,54 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "radiodata.h"
#include <QByteArray>
#include <string>
#include <utility>
#include <vector>
constexpr unsigned int CPN_CURRENT_SETTINGS_VERSION = { 221 };
struct EtxModelMetadata {
std::string filename;
std::string name;
int modelIdx;
};
typedef std::list<EtxModelMetadata> EtxModelfiles;
bool loadLabelsListFromYaml(RadioData::ModelLabels& labels,
int& sortOrder,
EtxModelfiles& modelFiles,
const QByteArray& data);
bool loadModelFromYaml(ModelData& model, const QByteArray& data);
bool loadRadioSettingsFromYaml(GeneralSettings& settings, const QByteArray& data);
bool writeLabelsListToYaml(const RadioData &radioData, QByteArray& data);
bool writeModelToYaml(const ModelData& model, QByteArray& data);
bool writeRadioSettingsToYaml(const GeneralSettings& settings, QByteArray& data);
std::string patchFilenameToYaml(const std::string& str);
@@ -0,0 +1,109 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "yaml_calibdata.h"
#include "eeprominterface.h"
#include "boardjson.h"
YamlCalibData::YamlCalibData()
{
memset(calib, 0, sizeof(calib));
}
YamlCalibData::YamlCalibData(const GeneralSettings::InputConfig* rhs)
{
for (int i = 0; i < Boards::getCapability(getCurrentBoard(), Board::Inputs); i++) {
if (Boards::isInputCalibrated(i)) {
calib[i].mid = rhs[i].calib.mid;
calib[i].spanNeg = rhs[i].calib.spanNeg;
calib[i].spanPos = rhs[i].calib.spanPos;
}
}
}
void YamlCalibData::copy(GeneralSettings::InputConfig* rhs) const
{
for (int i = 0; i < Boards::getCapability(getCurrentBoard(), Board::Inputs); i++) {
if (Boards::isInputCalibrated(i)) {
rhs[i].calib.mid = calib[i].mid;
rhs[i].calib.spanNeg = calib[i].spanNeg;
rhs[i].calib.spanPos = calib[i].spanPos;
}
}
}
namespace YAML
{
Node convert<GeneralSettings::InputCalib>::encode(const GeneralSettings::InputCalib& rhs)
{
Node node;
node["mid"] = rhs.mid;
node["spanNeg"] = rhs.spanNeg;
node["spanPos"] = rhs.spanPos;
return node;
}
bool convert<GeneralSettings::InputCalib>::decode(const Node& node, GeneralSettings::InputCalib& rhs)
{
if (!node.IsMap()) return false;
node["mid"] >> rhs.mid;
node["spanNeg"] >> rhs.spanNeg;
node["spanPos"] >> rhs.spanPos;
return true;
}
Node convert<YamlCalibData>::encode(const YamlCalibData& rhs)
{
Node node;
const int analogs = Boards::getCapability(getCurrentBoard(), Board::Inputs);
for (int i = 0; i < analogs; i++) {
if (Boards::isInputCalibrated(i)) {
std::string tag = Boards::getInputYamlName(i, BoardJson::YLT_CONFIG).toStdString();
node[tag] = rhs.calib[i];
}
}
return node;
}
bool convert<YamlCalibData>::decode(const Node& node, YamlCalibData& rhs)
{
if (!node.IsMap()) return false;
for (const auto& kv : node) {
std::string tag;
kv.first >> tag;
if (radioSettingsVersion < SemanticVersion(QString(CPN_ADC_REFACTOR_VERSION)))
tag = Boards::getLegacyAnalogMappedInputTag(tag.c_str());
int idx = Boards::getInputYamlIndex(tag.c_str(), BoardJson::YLT_CONFIG);
if (idx >= 0)
kv.second >> rhs.calib[idx];
}
return true;
}
} // namespace YAML
@@ -0,0 +1,46 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "yaml_ops.h"
#include "generalsettings.h"
struct YamlCalibData {
GeneralSettings::InputCalib calib[CPN_MAX_ANALOGS];
YamlCalibData();
YamlCalibData(const GeneralSettings::InputConfig* rhs);
void copy(GeneralSettings::InputConfig* rhs) const;
};
namespace YAML
{
template <>
struct convert<GeneralSettings::InputCalib> {
static Node encode(const GeneralSettings::InputCalib& rhs);
static bool decode(const Node& node, GeneralSettings::InputCalib& rhs);
};
template <>
struct convert<YamlCalibData> {
static Node encode(const YamlCalibData& rhs);
static bool decode(const Node& node, YamlCalibData& rhs);
};
} // namespace YAML
@@ -0,0 +1,102 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "yaml_curvedata.h"
// CurveHeader curves[MAX_CURVES];
// int8_t points[MAX_CURVE_POINTS];
void YamlReadCurvePoints(const YAML::Node& node, CurveData* curves)
{
int pidx = 0;
for (int i = 0; i < CPN_MAX_CURVES; i++) {
CurveData& curve = curves[i];
for (int j = 0; j < curve.count; j++) {
std::string pidx_str = std::to_string(pidx++);
if (node[pidx_str]) {
int y = 0;
node[pidx_str]["val"] >> y;
curve.points[j].y = (int8_t)y;
}
}
if (curve.type == CurveData::CURVE_TYPE_CUSTOM) {
// Custom curve: X points are trailing Y points
curve.points[0].x = -100;
for (int k = 1; k < curve.count - 1; k++) {
std::string pidx_str = std::to_string(pidx++);
if (node[pidx_str]) {
int x = 0;
node[pidx_str]["val"] >> x;
curve.points[k].x = (int8_t)x;
}
}
curve.points[curve.count - 1].x = +100;
}
}
}
void YamlWriteCurvePoints(YAML::Node& node, const CurveData* curves)
{
int pidx = 0;
for (int i = 0; i < CPN_MAX_CURVES; i++) {
//TODO: assert max number of points
// and raise exception
const CurveData& curve = curves[i];
if (!curve.isEmpty()) {
for (int k = 0; k < curve.count; k++) {
node[std::to_string(pidx++)]["val"] = (int)curve.points[k].y;
}
if (curve.type == CurveData::CURVE_TYPE_CUSTOM) {
for (int k = 1; k < curve.count - 1; k++) {
node[std::to_string(pidx++)]["val"] = (int)curve.points[k].x;
}
}
} else {
pidx += 5;
}
}
}
namespace YAML
{
Node convert<CurveData>::encode(const CurveData& rhs)
{
Node node;
node["type"] = (int)rhs.type;
node["smooth"] = (int)rhs.smooth;
node["points"] = rhs.count - 5;
node["name"] = rhs.name;
return node;
}
bool convert<CurveData>::decode(const Node& node, CurveData& rhs)
{
int curve_type = 0;
node["type"] >> curve_type;
rhs.type = (CurveData::CurveType)curve_type;
node["smooth"] >> rhs.smooth;
node["points"] >> ioffset_int(rhs.count, -5);
node["name"] >> rhs.name;
return true;
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "yaml_ops.h"
#include "curvedata.h"
void YamlReadCurvePoints(const YAML::Node& node, CurveData* curves);
void YamlWriteCurvePoints(YAML::Node& node, const CurveData* curves);
namespace YAML
{
template <>
struct convert<CurveData> {
static Node encode(const CurveData& rhs);
static bool decode(const Node& node, CurveData& rhs);
};
} // namespace YAML
@@ -0,0 +1,448 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "yaml_customfunctiondata.h"
#include "yaml_rawswitch.h"
#include "yaml_rawsource.h"
#include "eeprominterface.h"
static bool fnHasRepeat(AssignFunc fn)
{
return (fn == FuncPlayPrompt)
|| (fn == FuncPlayValue)
|| (fn == FuncPlayHaptic)
|| (fn == FuncPlaySound)
|| (fn == FuncSetScreen)
|| (fn == FuncPlayScript)
|| (fn == FuncRGBLed);
}
static const YamlLookupTable customFnLut = {
{ FuncOverrideCH1, "OVERRIDE_CHANNEL" },
{ FuncTrainer, "TRAINER" },
{ FuncInstantTrim, "INSTANT_TRIM" },
{ FuncReset, "RESET" },
{ FuncSetTimer1, "SET_TIMER" },
{ FuncAdjustGV1, "ADJUST_GVAR" },
{ FuncVolume, "VOLUME" },
{ FuncSetFailsafe, "SET_FAILSAFE" },
{ FuncRangeCheckInternalModule, "RANGECHECK" },
{ FuncBindInternalModule, "BIND" },
{ FuncPlaySound, "PLAY_SOUND" },
{ FuncPlayPrompt, "PLAY_TRACK" },
{ FuncPlayValue, "PLAY_VALUE" },
{ FuncPlayScript, "PLAY_SCRIPT" },
{ FuncBackgroundMusic, "BACKGND_MUSIC" },
{ FuncBackgroundMusicPause, "BACKGND_MUSIC_PAUSE" },
{ FuncVario, "VARIO" },
{ FuncPlayHaptic, "HAPTIC" },
{ FuncLogs, "LOGS" },
{ FuncBacklight, "BACKLIGHT" },
{ FuncScreenshot, "SCREENSHOT" },
{ FuncRacingMode, "RACING_MODE" },
{ FuncDisableTouch, "DISABLE_TOUCH" },
{ FuncSetScreen, "SET_SCREEN"},
{ FuncDisableAudioAmp, "DISABLE_AUDIO_AMP" },
{ FuncRGBLed, "RGB_LED" },
{ FuncLCDtoVideo, "LCD_TO_VIDEO" },
{ FuncPushCustomSwitch1, "PUSH_CUST_SWITCH" },
};
static const YamlLookupTable trainerLut = {
{ 0, "sticks" },
{ 1, "Rud" },
{ 2, "Ele" },
{ 3, "Thr" },
{ 4, "Ail" },
{ 5, "chans" },
};
static const YamlLookupTable soundLut = {
{ 0, "Bp1" },
{ 1, "Bp2" },
{ 2, "Bp3" },
{ 3, "Wrn1" },
{ 4, "Wrn2" },
{ 5, "Chee" },
{ 6, "Rata" },
{ 7, "Tick" },
{ 8, "Sirn" },
{ 9, "Ring" },
{ 10, "SciF" },
{ 11, "Robt" },
{ 12, "Chrp" },
{ 13, "Tada" },
{ 14, "Crck" },
{ 15, "Alrm" },
};
static const YamlLookupTable resetLut = {
{ 0, "Tmr1" },
{ 1, "Tmr2" },
{ 2, "Tmr3" },
{ 3, "All" },
{ 4, "Tele" },
{ 5, "Trims" },
};
static const YamlLookupTable gvarModeLut = {
{ FUNC_ADJUST_GVAR_CONSTANT, "Cst" },
{ FUNC_ADJUST_GVAR_SOURCE, "Src" },
{ FUNC_ADJUST_GVAR_SOURCERAW, "SrcRaw" },
{ FUNC_ADJUST_GVAR_GVAR, "GVar" },
{ FUNC_ADJUST_GVAR_INCDEC, "IncDec" },
};
static const YamlLookupTable moduleLut = {
{ 0, "Int" },
{ 1, "Ext" },
};
namespace YAML
{
Node convert<CustomFunctionData>::encode(const CustomFunctionData& rhs)
{
Node node;
node["swtch"] = rhs.swtch;
int fn = rhs.func;
int p1 = 0;
if (fn >= FuncOverrideCH1 && fn <= FuncOverrideCHLast) {
p1 = fn - (int)FuncOverrideCH1;
fn = (int)FuncOverrideCH1;
} else if (fn >= FuncTrainer && fn <= FuncTrainerChannels) {
p1 = fn - (int)FuncTrainer;
fn = (int)FuncTrainer;
} else if (fn >= FuncSetTimer1 && fn <= FuncSetTimerLast) {
p1 = fn - (int)FuncSetTimer1;
fn = (int)FuncSetTimer1;
} else if (fn >= FuncAdjustGV1 && fn <= FuncAdjustGVLast) {
p1 = fn - (int)FuncAdjustGV1;
fn = (int)FuncAdjustGV1;
} else if (fn >= FuncRangeCheckInternalModule && fn <= FuncRangeCheckExternalModule) {
p1 = fn - (int)FuncRangeCheckInternalModule;
fn = (int)FuncRangeCheckInternalModule;
} else if (fn >= FuncBindInternalModule && fn <= FuncBindExternalModule) {
p1 = fn - (int)FuncBindInternalModule;
fn = (int)FuncBindInternalModule;
} else if (fn >= FuncPushCustomSwitch1 && fn <= FuncPushCustomSwitchLast) {
p1 = fn - (int)FuncPushCustomSwitch1;
fn = (int)FuncPushCustomSwitch1;
}
node["func"] = LookupValue(customFnLut, fn);
bool add_comma = true;
std::string def;
switch(fn) {
case FuncOverrideCH1:
def += std::to_string(p1);
def += ",";
def += std::to_string(rhs.param);
break;
case FuncTrainer:
def += LookupValue(trainerLut, p1);
break;
case FuncPlaySound:
def += LookupValue(soundLut, rhs.param);
break;
case FuncPlayHaptic:
def += std::to_string(rhs.param);
break;
case FuncPlayPrompt:
case FuncBackgroundMusic: {
std::string temp(rhs.paramarm);
temp.resize(getCurrentFirmware()->getCapability(VoicesMaxLength));
def += temp;
} break;
case FuncPlayScript:
case FuncRGBLed:
def += std::string(rhs.paramarm);
break;
case FuncReset:
def += LookupValue(resetLut, rhs.param);
if (def.empty()) {
def += std::to_string(rhs.param - resetLut.size());
}
break;
case FuncSetTimer1: {
def += LookupValue(resetLut, p1);
def += ",";
def += std::to_string(rhs.param);
} break;
case FuncPlayValue:
case FuncVolume:
case FuncBacklight: {
def += YamlRawSourceEncode(RawSource(rhs.param));
} break;
case FuncAdjustGV1: {
// + GV #
def += std::to_string(p1);
def += ",";
def += LookupValue(gvarModeLut, rhs.adjustMode);
def += ",";
switch(rhs.adjustMode) {
case FUNC_ADJUST_GVAR_CONSTANT:
case FUNC_ADJUST_GVAR_INCDEC:
def += std::to_string(rhs.param);
break;
case FUNC_ADJUST_GVAR_GVAR:
case FUNC_ADJUST_GVAR_SOURCE:
case FUNC_ADJUST_GVAR_SOURCERAW: {
def += YamlRawSourceEncode(RawSource(rhs.param));
} break;
}
} break;
case FuncRangeCheckInternalModule:
case FuncBindInternalModule: {
def += LookupValue(moduleLut, p1);
} break;
case FuncLogs:
def += std::to_string(rhs.param);
break;
case FuncSetScreen:
def += std::to_string(rhs.param);
break;
case FuncPushCustomSwitch1: {
int idx = Boards::getCFSIndexForSwitch(Boards::getSwitchIndexForCFSOffset(p1));
def += std::to_string(idx);
def += ",";
def += std::to_string(rhs.param);
} break;
default:
add_comma = false;
break;
}
if (add_comma) {
def += ",";
}
def += std::to_string((int)rhs.enabled);
if(fnHasRepeat(rhs.func)) {
def += ",";
if (rhs.func == FuncPlayScript || rhs.func == FuncRGBLed) {
def += ((rhs.repeatParam == 0) ? "On" : "1x");
} else if (rhs.repeatParam == 0) {
def += "1x";
} else if (rhs.repeatParam == -1) {
def += "!1x";
} else {
def += std::to_string(rhs.repeatParam);
}
}
if (!def.empty()) {
node["def"] = def;
}
return node;
}
bool convert<CustomFunctionData>::decode(const Node& node,
CustomFunctionData& rhs)
{
node["swtch"] >> rhs.swtch;
int func = 0;
node["func"] >> customFnLut >> func;
rhs.func = (AssignFunc)func;
std::string def_str;
node["def"] >> def_str;
std::stringstream def(def_str);
switch(rhs.func) {
case FuncOverrideCH1: {
int ch=0;
def >> ch;
rhs.func = (AssignFunc)((int)rhs.func + ch);
def.ignore();
def >> rhs.param;
} break;
case FuncTrainer: {
std::string value_str;
getline(def, value_str, ',');
int value=0;
Node(value_str) >> trainerLut >> value;
rhs.func = (AssignFunc)((int)rhs.func + value);
} break;
case FuncPlaySound: {
std::string snd;
getline(def, snd, ',');
Node(snd) >> soundLut >> rhs.param;
} break;
case FuncPlayHaptic: {
std::string haptic;
getline(def, haptic, ',');
try {
rhs.param = std::stoi(haptic);
} catch(...) {}
} break;
case FuncPlayPrompt:
case FuncBackgroundMusic: {
std::string file_str;
getline(def, file_str, ',');
file_str.resize(getCurrentFirmware()->getCapability(VoicesMaxLength));
strncpy(rhs.paramarm, file_str.c_str(), sizeof(rhs.paramarm) - 1);
} break;
case FuncRGBLed:
case FuncPlayScript: {
std::string file_str;
getline(def, file_str, ',');
strncpy(rhs.paramarm, file_str.c_str(), sizeof(rhs.paramarm) - 1);
} break;
case FuncReset: {
std::string rst_str;
getline(def, rst_str, ',');
try {
rhs.param = std::stoi(rst_str);
rhs.param += resetLut.size();
break;
} catch(...) {}
Node(rst_str.c_str()) >> resetLut >> rhs.param;
} break;
case FuncSetTimer1: {
// + timer #
std::string tmr_str;
getline(def, tmr_str, ',');
int tmr = 0;
Node(tmr_str) >> resetLut >> tmr;
rhs.func = (AssignFunc)((int)rhs.func + tmr);
def >> rhs.param;
} break;
case FuncPlayValue:
case FuncVolume:
case FuncBacklight: {
std::string src_str;
getline(def, src_str, ',');
if (def_str.size() >= 4 && def_str.substr(0, 4) == "lua(") {
std::string tmp_str;
getline(def, tmp_str, ',');
src_str += ("," + tmp_str);
}
rhs.param = YamlRawSourceDecode(src_str).toValue();
} break;
case FuncAdjustGV1: {
// + GV #
int gvar_idx=0;
def >> gvar_idx;
rhs.func = (AssignFunc)((int)rhs.func + gvar_idx);
def.ignore();
std::string mode;
getline(def, mode, ',');
Node(mode) >> gvarModeLut >> rhs.adjustMode;
switch(rhs.adjustMode) {
case FUNC_ADJUST_GVAR_CONSTANT:
case FUNC_ADJUST_GVAR_INCDEC:
def >> rhs.param;
break;
case FUNC_ADJUST_GVAR_GVAR:
case FUNC_ADJUST_GVAR_SOURCE:
case FUNC_ADJUST_GVAR_SOURCERAW: {
std::string src_str;
getline(def, src_str, ',');
RawSource src;
Node(src_str) >> src;
rhs.param = src.toValue();
} break;
}
} break;
case FuncRangeCheckInternalModule:
case FuncBindInternalModule: {
// DANGEROUS_FUNCTIONS...
// + module # ???
std::string mod;
getline(def, mod, ',');
int module=0;
Node(mod) >> moduleLut >> module;
rhs.func = (AssignFunc)((int)rhs.func + module);
} break;
case FuncLogs: {
int param = 0;
def >> param;
rhs.param = param;
} break;
case FuncSetScreen: {
int param = 0;
def >> param;
rhs.param = param;
} break;
case FuncPushCustomSwitch1: {
int sw = 0;
def >> sw;
int offset = Boards::getCFSOffsetForCFSIndex(sw);
rhs.func = (AssignFunc)((int)rhs.func + offset);
def.ignore();
int param = 0;
def >> param;
rhs.param = param;
} break;
default:
break;
}
if (def.peek() == ',') {
def.ignore();
}
// Need to handle older YAML files where only one of enabled/repeat was present
std::string en, repeat;
getline(def, en, ',');
getline(def, repeat);
if (repeat.empty()) {
// Only one value left to parse
if (fnHasRepeat(rhs.func)) {
// Assume it is repeat and set enabled to true
repeat = en;
rhs.enabled = 1;
} else {
// Func does not have repeat
rhs.enabled = en[0] == '1' ? 1 : 0;
}
} else {
// Two values - first is 'enabled' flag
rhs.enabled = en[0] == '1' ? 1 : 0;
}
if(fnHasRepeat(rhs.func)) {
if (rhs.func == FuncPlayScript || rhs.func == FuncRGBLed) {
rhs.repeatParam = (repeat == "1x") ? 1 : 0;
} else if (repeat == "1x") {
rhs.repeatParam = 0;
} else if (repeat == "!1x") {
rhs.repeatParam = -1;
} else {
try {
rhs.repeatParam = std::stoi(repeat);
} catch(...) {}
}
}
return true;
}
} // namespace YAML
@@ -0,0 +1,32 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "yaml_ops.h"
#include "customfunctiondata.h"
namespace YAML
{
template <>
struct convert<CustomFunctionData> {
static Node encode(const CustomFunctionData& rhs);
static bool decode(const Node& node, CustomFunctionData& rhs);
};
} // namespace YAML
@@ -0,0 +1,72 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "yaml_expodata.h"
#include "yaml_mixdata.h"
#include "yaml_rawswitch.h"
#include "yaml_rawsource.h"
#include "curvereference.h"
namespace YAML
{
Node convert<ExpoData>::encode(const ExpoData& rhs)
{
Node node;
node["srcRaw"] = rhs.srcRaw;
node["scale"] = rhs.scale;
node["mode"] = rhs.mode; // InputMode
node["chn"] = rhs.chn;
node["swtch"] = rhs.swtch;
node["flightModes"] = YamlWriteFlightModes(rhs.flightModes);
node["weight"] = YamlSourceNumRefEncode(rhs.weight);
node["offset"] = YamlSourceNumRefEncode(rhs.offset);
node["curve"] = rhs.curve;
node["trimSource"] = rhs.carryTrim; // temporary for 2.8.1, trimSource in 2.9
node["name"] = rhs.name;
return node;
}
bool convert<ExpoData>::decode(const Node& node, ExpoData& rhs)
{
node["srcRaw"] >> rhs.srcRaw;
node["scale"] >> rhs.scale;
node["mode"] >> rhs.mode; // InputMode
node["chn"] >> rhs.chn;
node["swtch"] >> rhs.swtch;
if (node["flightModes"]) {
rhs.flightModes = YamlReadFlightModes(node["flightModes"]);
}
if (node["weight"]) {
rhs.weight = YamlSourceNumRefDecode(node["weight"]);
}
if (node["offset"]) {
rhs.offset = YamlSourceNumRefDecode(node["offset"]);
}
node["curve"] >> rhs.curve;
if (node["carryTrim"]) { // 2.9 - change bugged carryTrim to trimSource
node["carryTrim"] >> rhs.carryTrim;
} else {
node["trimSource"] >> rhs.carryTrim;
}
node["name"] >> rhs.name;
return true;
}
} // namespace YAML
@@ -0,0 +1,32 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "yaml_ops.h"
#include "input_data.h"
namespace YAML
{
template <>
struct convert<ExpoData> {
static Node encode(const ExpoData& rhs);
static bool decode(const Node& node, ExpoData& rhs);
};
} // namespace YAML
@@ -0,0 +1,790 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "yaml_generalsettings.h"
#include "yaml_customfunctiondata.h"
#include "yaml_trainerdata.h"
#include "yaml_calibdata.h"
#include "yaml_switchconfig.h"
#include "yaml_moduledata.h"
#include "yaml_rawsource.h"
#include "eeprominterface.h"
#include "edgetxinterface.h"
#include "version.h"
#include "helpers.h"
#include <QMessageBox>
#include <QPushButton>
void YamlValidateNames(GeneralSettings& gs, Board::Type board)
{
for (int i = 0; i < CPN_MAX_INPUTS; i++) {
YamlValidateName(gs.inputConfig[i].name, board);
}
for (int i = 0; i < CPN_MAX_SWITCHES; i++) {
YamlValidateName(gs.switchConfig[i].name, board);
}
}
const YamlLookupTable beeperModeLut = {
{ GeneralSettings::BEEPER_QUIET, "mode_quiet" },
{ GeneralSettings::BEEPER_ALARMS_ONLY, "mode_alarms" },
{ GeneralSettings::BEEPER_NOKEYS, "mode_nokeys" },
{ GeneralSettings::BEEPER_ALL, "mode_all" },
};
enum BacklightMode {
e_backlight_mode_off = 0,
e_backlight_mode_keys = 1,
e_backlight_mode_sticks = 2,
e_backlight_mode_all = e_backlight_mode_keys + e_backlight_mode_sticks,
e_backlight_mode_on
};
const YamlLookupTable backlightModeLut = {
{ e_backlight_mode_off, "backlight_mode_off" },
{ e_backlight_mode_keys, "backlight_mode_keys" },
{ e_backlight_mode_sticks, "backlight_mode_sticks" },
{ e_backlight_mode_all, "backlight_mode_all" },
{ e_backlight_mode_on, "backlight_mode_on" },
};
const YamlLookupTable bluetoothModeLut = {
{ GeneralSettings::BLUETOOTH_MODE_OFF, "OFF" },
{ GeneralSettings::BLUETOOTH_MODE_TELEMETRY, "TELEMETRY" },
{ GeneralSettings::BLUETOOTH_MODE_TRAINER, "TRAINER" },
};
const YamlLookupTable oldUartModeLut = {
{ GeneralSettings::AUX_SERIAL_OFF, "MODE_NONE" },
{ GeneralSettings::AUX_SERIAL_TELE_MIRROR, "MODE_TELEMETRY_MIRROR" },
{ GeneralSettings::AUX_SERIAL_TELE_IN, "MODE_TELEMETRY" },
{ GeneralSettings::AUX_SERIAL_SBUS_TRAINER, "MODE_SBUS_TRAINER" },
{ GeneralSettings::AUX_SERIAL_LUA, "MODE_LUA" },
};
const YamlLookupTable serialPortLut = {
{ GeneralSettings::SP_AUX1, "AUX1" },
{ GeneralSettings::SP_AUX2, "AUX2" },
{ GeneralSettings::SP_VCP, "VCP" },
};
const YamlLookupTable uartModeLut = {
{ GeneralSettings::AUX_SERIAL_OFF, "NONE" },
{ GeneralSettings::AUX_SERIAL_TELE_MIRROR, "TELEMETRY_MIRROR" },
{ GeneralSettings::AUX_SERIAL_TELE_IN, "TELEMETRY_IN" },
{ GeneralSettings::AUX_SERIAL_SBUS_TRAINER, "SBUS_TRAINER" },
{ GeneralSettings::AUX_SERIAL_LUA, "LUA" },
{ GeneralSettings::AUX_SERIAL_CLI, "CLI" },
{ GeneralSettings::AUX_SERIAL_GPS, "GPS" },
{ GeneralSettings::AUX_SERIAL_DEBUG, "DEBUG" },
{ GeneralSettings::AUX_SERIAL_SPACEMOUSE, "SPACEMOUSE" },
{ GeneralSettings::AUX_SERIAL_EXT_MODULE, "EXT_MODULE" },
};
const YamlLookupTable antennaModeLut = {
{ GeneralSettings::ANTENNA_MODE_INTERNAL, "MODE_INTERNAL" },
{ GeneralSettings::ANTENNA_MODE_ASK, "MODE_ASK" },
{ GeneralSettings::ANTENNA_MODE_PER_MODEL, "MODE_PER_MODEL" },
{ GeneralSettings::ANTENNA_MODE_EXTERNAL, "MODE_EXTERNAL" },
{ GeneralSettings::ANTENNA_MODE_INTERNAL_EXTERNAL, "MODE_INTERNAL_EXTERNAL" },
};
const YamlLookupTable internalModuleLut = {
{ MODULE_TYPE_NONE, "TYPE_NONE" },
{ MODULE_TYPE_PPM, "TYPE_PPM" },
{ MODULE_TYPE_XJT_PXX1, "TYPE_XJT_PXX1" },
{ MODULE_TYPE_ISRM_PXX2, "TYPE_ISRM_PXX2" },
{ MODULE_TYPE_DSM2, "TYPE_DSM2" },
{ MODULE_TYPE_CROSSFIRE, "TYPE_CROSSFIRE" },
{ MODULE_TYPE_MULTIMODULE, "TYPE_MULTIMODULE" },
{ MODULE_TYPE_R9M_PXX1, "TYPE_R9M_PXX1" },
{ MODULE_TYPE_R9M_PXX2, "TYPE_R9M_PXX2" },
{ MODULE_TYPE_R9M_LITE_PXX1, "TYPE_R9M_LITE_PXX1" },
{ MODULE_TYPE_R9M_LITE_PXX2, "TYPE_R9M_LITE_PXX2" },
{ MODULE_TYPE_GHOST, "TYPE_GHOST" },
{ MODULE_TYPE_R9M_LITE_PRO_PXX2, "TYPE_R9M_LITE_PRO_PXX2" },
{ MODULE_TYPE_SBUS, "TYPE_SBUS" },
{ MODULE_TYPE_XJT_LITE_PXX2, "TYPE_XJT_LITE_PXX2" },
{ MODULE_TYPE_FLYSKY_AFHDS2A, "TYPE_FLYSKY_AFHDS2A" },
{ MODULE_TYPE_FLYSKY_AFHDS3, "TYPE_FLYSKY_AFHDS3" },
{ MODULE_TYPE_LEMON_DSMP, "TYPE_LEMON_DSMP" },
};
static const YamlLookupTable hatsModeLut = {
{ GeneralSettings::HATSMODE_TRIMS_ONLY, "TRIMS_ONLY" },
{ GeneralSettings::HATSMODE_KEYS_ONLY, "KEYS_ONLY" },
{ GeneralSettings::HATSMODE_SWITCHABLE, "SWITCHABLE" },
};
static const YamlLookupTable QMPageLut = {
{ GeneralSettings::QM_NONE, "NONE" },
{ GeneralSettings::QM_OPEN_QUICK_MENU, "OPEN_QUICK_MENU" },
{ GeneralSettings::QM_MANAGE_MODELS, "MANAGE_MODELS" },
{ GeneralSettings::QM_MODEL_SETUP, "MODEL_SETUP" },
{ GeneralSettings::QM_MODEL_FLIGHTMODES, "MODEL_FLIGHTMODES" },
{ GeneralSettings::QM_MODEL_INPUTS, "MODEL_INPUTS" },
{ GeneralSettings::QM_MODEL_MIXES, "MODEL_MIXES" },
{ GeneralSettings::QM_MODEL_OUTPUTS, "MODEL_OUTPUTS" },
{ GeneralSettings::QM_MODEL_CURVES, "MODEL_CURVES" },
{ GeneralSettings::QM_MODEL_GVARS, "MODEL_GVARS" },
{ GeneralSettings::QM_MODEL_LS, "MODEL_LS" },
{ GeneralSettings::QM_MODEL_SF, "MODEL_SF" },
{ GeneralSettings::QM_MODEL_SCRIPTS, "MODEL_SCRIPTS" },
{ GeneralSettings::QM_MODEL_TELEMETRY, "MODEL_TELEMETRY" },
{ GeneralSettings::QM_MODEL_NOTES, "MODEL_NOTES" },
{ GeneralSettings::QM_RADIO_SETUP, "RADIO_SETUP" },
{ GeneralSettings::QM_RADIO_GF, "RADIO_GF" },
{ GeneralSettings::QM_RADIO_TRAINER, "RADIO_TRAINER" },
{ GeneralSettings::QM_RADIO_HARDWARE, "RADIO_HARDWARE" },
{ GeneralSettings::QM_RADIO_VERSION, "RADIO_VERSION" },
{ GeneralSettings::QM_UI_THEMES, "UI_THEMES" },
{ GeneralSettings::QM_UI_SETUP, "UI_SETUP" },
{ GeneralSettings::QM_UI_SCREEN1, "UI_SCREEN1" },
{ GeneralSettings::QM_UI_SCREEN2, "UI_SCREEN2" },
{ GeneralSettings::QM_UI_SCREEN3, "UI_SCREEN3" },
{ GeneralSettings::QM_UI_SCREEN4, "UI_SCREEN4" },
{ GeneralSettings::QM_UI_SCREEN5, "UI_SCREEN5" },
{ GeneralSettings::QM_UI_SCREEN6, "UI_SCREEN6" },
{ GeneralSettings::QM_UI_SCREEN7, "UI_SCREEN7" },
{ GeneralSettings::QM_UI_SCREEN8, "UI_SCREEN8" },
{ GeneralSettings::QM_UI_SCREEN9, "UI_SCREEN9" },
{ GeneralSettings::QM_UI_SCREEN10, "UI_SCREEN10" },
{ GeneralSettings::QM_UI_ADD_PG, "UI_ADD_PG" },
{ GeneralSettings::QM_TOOLS_APPS, "TOOLS_APPS" },
{ GeneralSettings::QM_TOOLS_STORAGE, "TOOLS_STORAGE" },
{ GeneralSettings::QM_TOOLS_RESET, "TOOLS_RESET" },
{ GeneralSettings::QM_TOOLS_CHAN_MON, "TOOLS_CHAN_MON" },
{ GeneralSettings::QM_TOOLS_LS_MON, "TOOLS_LS_MON" },
{ GeneralSettings::QM_TOOLS_STATS, "TOOLS_STATS" },
{ GeneralSettings::QM_TOOLS_DEBUG, "TOOLS_DEBUG" },
{ GeneralSettings::QM_APP, "APP" },
};
YamlTelemetryBaudrate::YamlTelemetryBaudrate(
const unsigned int* moduleBaudrate)
{
if (Boards::getCapability(getCurrentFirmware()->getBoard(),
Board::SportMaxBaudRate) < 400000) {
value = *moduleBaudrate;
} else {
value = (*moduleBaudrate + moduleBaudratesList.size() - 1) %
moduleBaudratesList.size();
}
}
void YamlTelemetryBaudrate::toCpn(unsigned int* moduleBaudrate,
unsigned int variant)
{
if (Boards::getCapability((Board::Type)variant, Board::SportMaxBaudRate) <
400000) {
*moduleBaudrate = value;
} else {
*moduleBaudrate = (value + 1) % moduleBaudratesList.size();
}
}
namespace YAML
{
ENUM_CONVERTER(GeneralSettings::BeeperMode, beeperModeLut);
Node convert<GeneralSettings>::encode(const GeneralSettings& rhs)
{
Node node;
auto fw = getCurrentFirmware();
auto board = fw->getBoard();
bool hasColorLcd = Boards::getCapability(board, Board::HasColorLcd);
node["semver"] = VERSION;
std::string strboard = fw->getFlavour().toStdString();
node["board"] = strboard;
if (rhs.manuallyEdited)
node["manuallyEdited"] = (int)rhs.manuallyEdited;
YamlCalibData calib(rhs.inputConfig);
node["calib"] = calib;
node["currModelFilename"] = patchFilenameToYaml(rhs.currModelFilename);
node["vBatWarn"] = rhs.vBatWarn;
node["txVoltageCalibration"] = rhs.txVoltageCalibration;
node["vBatMin"] = rhs.vBatMin + 90;
node["vBatMax"] = rhs.vBatMax + 120;
if (!Boards::getCapability(board, Board::HasColorLcd)) {
node["backlightColor"] = rhs.backlightColor;
node["contrast"] = rhs.contrast;
node["currModel"] = rhs.currModelIndex;
}
node["backlightMode"] = backlightModeLut << std::abs(rhs.backlightMode);
node["trainer"] = rhs.trainer;
node["PPM_Multiplier"] = rhs.PPM_Multiplier;
node["view"] = rhs.view;
node["fai"] = (int)rhs.fai;
node["disableMemoryWarning"] = (int)rhs.disableMemoryWarning;
node["beepMode"] = rhs.beeperMode;
node["alarmsFlash"] = (int)rhs.alarmsFlash;
node["disableAlarmWarning"] = (int)rhs.disableAlarmWarning;
node["disableRssiPoweroffAlarm"] = (int)rhs.disableRssiPoweroffAlarm;
node["disableTrainerPoweroffAlarm"] = (int)rhs.disableTrainerPoweroffAlarm;
node["USBMode"] = rhs.usbMode;
node["hatsMode"] = hatsModeLut << rhs.hatsMode;
node["stickDeadZone"] = rhs.stickDeadZone;
node["jackMode"] = rhs.jackMode;
node["hapticMode"] = rhs.hapticMode;
node["stickMode"] = rhs.stickMode;
node["timezone"] = rhs.timezone;
node["timezoneMinutes"] = rhs.timezoneMinutes;
node["adjustRTC"] = (int)rhs.adjustRTC;
node["inactivityTimer"] = rhs.inactivityTimer;
YamlTelemetryBaudrate internalModuleBaudrate(&rhs.internalModuleBaudrate);
node["internalModuleBaudrate"] = internalModuleBaudrate.value;
node["internalModule"] = LookupValue(internalModuleLut, rhs.internalModule);
node["splashMode"] = rhs.splashMode;
node["lightAutoOff"] = rhs.backlightDelay;
node["templateSetup"] = rhs.templateSetup;
node["hapticLength"] = rhs.hapticLength + 2;
node["speakerPitch"] = rhs.speakerPitch * 15;
node["hapticStrength"] = rhs.hapticStrength + 2;
node["beepLength"] = rhs.beeperLength + 2;
node["gpsFormat"] = rhs.gpsFormat;
node["speakerVolume"] = rhs.speakerVolume + 12;
node["backlightBright"] = rhs.backlightBright;
node["blOffBright"] = rhs.backlightOffBright;
node["switchesDelay"] = rhs.switchesDelay;
node["globalTimer"] = rhs.globalTimer;
node["bluetoothName"] = rhs.bluetoothName;
node["bluetoothBaudrate"] = rhs.bluetoothBaudrate;
node["bluetoothMode"] = bluetoothModeLut << rhs.bluetoothMode;
node["countryCode"] = rhs.countryCode;
node["noJitterFilter"] = (int)rhs.noJitterFilter;
node["disableRtcWarning"] = (int)rhs.rtcCheckDisable; // TODO: verify
node["audioMuteEnable"] = (int)rhs.muteIfNoSound;
node["keysBacklight"] = (int)rhs.keysBacklight;
node["rotEncMode"] = (int)rhs.rotEncMode;
node["imperial"] = rhs.imperial;
node["ppmunit"] = rhs.ppmunit;
node["ttsLanguage"] = rhs.ttsLanguage;
node["uiLanguage"] = rhs.uiLanguage;
node["beepVolume"] = rhs.beepVolume + 2;
node["wavVolume"] = rhs.wavVolume + 2;
node["varioVolume"] = rhs.varioVolume + 2;
node["varioPitch"] = rhs.varioPitch * 10;
node["varioRange"] = rhs.varioRange * 15;
node["varioRepeat"] = rhs.varioRepeat;
node["backgroundVolume"] = rhs.backgroundVolume + 2;
node["dontPlayHello"] = (int)rhs.dontPlayHello;
node["modelQuickSelect"] = (int)rhs.modelQuickSelect;
if (hasColorLcd) {
node["modelSelectLayout"] = rhs.modelSelectLayout;
node["labelSingleSelect"] = rhs.labelSingleSelect;
node["labelMultiMode"] = rhs.labelMultiMode;
node["favMultiMode"] = rhs.favMultiMode;
} else if (Boards::getCapability(board, Board::LcdWidth) == 128) {
node["invertLCD"] = (int)rhs.invertLCD;
}
Node serialPort;
for (int i = 0; i < GeneralSettings::SP_COUNT; i++) {
if (rhs.serialPort[i] != GeneralSettings::AUX_SERIAL_OFF || rhs.serialPower[i]) {
Node mode = uartModeLut << rhs.serialPort[i];
serialPort[LookupValue(serialPortLut, i)]["mode"] = mode;
serialPort[LookupValue(serialPortLut, i)]["power"] = (int)rhs.serialPower[i];
}
}
if (serialPort && serialPort.IsMap())
node["serialPort"] = serialPort;
node["antennaMode"] = antennaModeLut << rhs.antennaMode;
node["pwrOnSpeed"] = rhs.pwrOnSpeed;
node["pwrOffSpeed"] = rhs.pwrOffSpeed;
node["pwrOffIfInactive"] = rhs.pwrOffIfInactive; // Power off after inactivity
node["disablePwrOnOffHaptic"] = (int)rhs.disablePwrOnOffHaptic;
for (int i = 0; i < CPN_MAX_SPECIAL_FUNCTIONS; i++) {
const CustomFunctionData& fn = rhs.customFn[i];
if (!fn.isEmpty()) {
node["customFn"][std::to_string(i)] = fn;
}
}
Node sticksConfig;
sticksConfig = YamlStickConfig(rhs.inputConfig);
if (sticksConfig && sticksConfig.IsMap()) {
node["sticksConfig"] = sticksConfig;
}
Node potsConfig;
potsConfig = YamlPotConfig(rhs.inputConfig);
if (potsConfig && potsConfig.IsMap()) {
node["potsConfig"] = potsConfig;
}
Node switchConfig;
switchConfig = YamlSwitchConfig(rhs.switchConfig);
if (switchConfig && switchConfig.IsMap()) {
node["switchConfig"] = switchConfig;
}
Node flexSwitches;
flexSwitches = YamlSwitchesFlex(rhs.switchConfig);
if (flexSwitches && flexSwitches.IsMap()) {
node["flexSwitches"] = flexSwitches;
}
node["ownerRegistrationID"] = rhs.registrationId;
if (Boards::getCapability(board, Board::HasIMU)) {
node["imuMax"] = rhs.imuMax;
node["imuOffset"] = rhs.imuOffset;
}
// OneBit sampling (X9D only?)
node["uartSampleMode"] = rhs.uartSampleMode;
if (hasColorLcd)
node["selectedTheme"] = rhs.selectedTheme;
node["backlightSrc"] = rhs.backlightSrc;
node["volumeSrc"] = rhs.volumeSrc;
// Radio level tabs control (global settings)
if (hasColorLcd)
node["radioThemesDisabled"] = (int)rhs.radioThemesDisabled;
node["radioGFDisabled"] = (int)rhs.radioGFDisabled;
node["radioTrainerDisabled"] = (int)rhs.radioTrainerDisabled;
// Model level tabs control (global setting)
node["modelHeliDisabled"] = (int)rhs.modelHeliDisabled;
node["modelFMDisabled"] = (int)rhs.modelFMDisabled;
node["modelCurvesDisabled"] = (int)rhs.modelCurvesDisabled;
node["modelGVDisabled"] = (int)rhs.modelGVDisabled;
node["modelLSDisabled"] = (int)rhs.modelLSDisabled;
node["modelSFDisabled"] = (int)rhs.modelSFDisabled;
node["modelCustomScriptsDisabled"] = (int)rhs.modelCustomScriptsDisabled;
node["modelTelemetryDisabled"] = (int)rhs.modelTelemetryDisabled;
if (hasColorLcd) {
for (int i = 0; i < MAX_KEYSHORTCUTS; i += 1)
if (rhs.keyShortcuts[i] != GeneralSettings::QM_NONE) {
if (rhs.keyShortcuts[i] != GeneralSettings::QM_APP) {
node["keyShortcuts"][std::to_string(i)]["shortcut"] = QMPageLut << rhs.keyShortcuts[i];
} else {
std::string s("APP,");
s += rhs.keyShortcutTools[i];
node["keyShortcuts"][std::to_string(i)]["shortcut"] = s;
}
}
for (int i = 0; i < MAX_QMFAVOURITES; i += 1)
if (rhs.qmFavorites[i] != GeneralSettings::QM_NONE) {
if (rhs.qmFavorites[i] != GeneralSettings::QM_APP) {
node["qmFavorites"][std::to_string(i)]["shortcut"] = QMPageLut << rhs.qmFavorites[i];
} else {
std::string s("APP,");
s += rhs.qmFavoritesTools[i];
node["qmFavorites"][std::to_string(i)]["shortcut"] = s;
}
}
}
node["oneLogPerDay"] = (int)rhs.oneLogPerDay;
return node;
}
bool convert<GeneralSettings>::decode(const Node& node, GeneralSettings& rhs)
{
if (!node.IsMap()) return false;
// for (const auto& n : node) {
// qDebug() << QString::fromStdString(n.first.Scalar());
// }
radioSettingsVersion = SemanticVersion();
if (node["semver"]) {
node["semver"] >> rhs.semver;
if (SemanticVersion().isValid(rhs.semver)) {
radioSettingsVersion = SemanticVersion(QString(rhs.semver));
}
else {
qDebug() << "Invalid settings version:" << rhs.semver;
memset(rhs.semver, 0, sizeof(rhs.semver));
}
}
qDebug() << "Settings version:" << radioSettingsVersion.toString();
if (radioSettingsVersion > SemanticVersion(VERSION)) {
// TODO remove this temporary 2.12 fix check for 3.0 release
if (radioSettingsVersion == SemanticVersion("3.0.0") &&
SemanticVersion(VERSION) >= SemanticVersion("2.12.0") &&
SemanticVersion(VERSION) < SemanticVersion("3.0.0")) {
qDebug() << "Version exception override: radio settings" << radioSettingsVersion.toString()
<< "Companion" << SemanticVersion(VERSION).toString();
} else {
QString prmpt = QCoreApplication::translate("YamlGeneralSettings", "Warning: File version %1 is not supported by Companion %2!\n\nModel and radio settings may be corrupted if you continue.");
prmpt = prmpt.arg(radioSettingsVersion.toString()).arg(SemanticVersion(VERSION).toString());
QMessageBox msgBox;
msgBox.setWindowTitle(QCoreApplication::translate("YamlGeneralSettings", "Read Radio Settings"));
msgBox.setText(prmpt);
msgBox.setIcon(QMessageBox::Warning);
QPushButton *pbAccept = new QPushButton(CPN_STR_TTL_ACCEPT);
QPushButton *pbDecline = new QPushButton(CPN_STR_TTL_DECLINE);
msgBox.addButton(pbAccept, QMessageBox::AcceptRole);
msgBox.addButton(pbDecline, QMessageBox::RejectRole);
msgBox.setDefaultButton(pbDecline);
msgBox.exec();
if (msgBox.clickedButton() == pbDecline)
return false;
}
}
rhs.version = CPN_CURRENT_SETTINGS_VERSION; // depreciated in EdgeTX however data conversions use
// Decoding uses profile firmare therefore all conversions are performed on the fly
// So set board to firmware board
// However we should alert users that conversions may have occured
// If conversion should rename file with _converted suffix as done for bin
// Need to pass back messages and flags like converted so they can be handled in a more suitable section
// Note: This is part of an interim workaround to ensure critical settings are set to profile board defaults
// TODO: remove and let board conversion occur as for eeprom and profile
// this will require yaml import of general and model settings to be refactored to not use current firmware
bool needsConversion = false;
//
rhs.variant = Board::BOARD_UNKNOWN;
std::string flavour;
node["board"] >> flavour;
auto fw = getCurrentFirmware();
qDebug() << "Settings version:" << rhs.semver << "File flavour:" << flavour.c_str() << "Firmware flavour:" << fw->getFlavour();
if (flavour.empty()) {
QString prmpt = QCoreApplication::translate("YamlGeneralSettings", "Warning: Radio settings file is missing the board entry!\n\nCurrent firmware profile board will be used.\n\nDo you wish to continue?");
if (QMessageBox::question(NULL, CPN_STR_APP_NAME, prmpt, (QMessageBox::Yes | QMessageBox::No), QMessageBox::No) != QMessageBox::Yes) {
// TODO: this triggers an error in the calling code so we need a graceful way to handle
return false;
}
flavour = fw->getFlavour().toStdString();
needsConversion = true;
}
else if (fw->getFlavour().toStdString() != flavour) {
auto msfw = Firmware::getFirmwareForFlavour(QString(flavour.c_str()));
QString prmpt = QCoreApplication::translate("YamlGeneralSettings", "Settings file board (%1) does not match current profile board (%2).\n\nDo you wish to continue?").arg(Boards::getBoardName(msfw->getBoard())).arg(Boards::getBoardName(fw->getBoard()));
if (QMessageBox::question(NULL, CPN_STR_APP_NAME, prmpt, (QMessageBox::Yes | QMessageBox::No), QMessageBox::No) != QMessageBox::Yes) {
// TODO: this triggers an error in the calling code so we need a graceful way to handle
return false;
}
needsConversion = true;
}
// TODO: do not override here
rhs.variant = fw->getBoard();
YamlCalibData calib;
node["calib"] >> calib;
calib.copy(rhs.inputConfig);
node["currModel"] >> rhs.currModelIndex;
node["currModelFilename"] >> rhs.currModelFilename;
node["contrast"] >> rhs.contrast;
node["vBatWarn"] >> rhs.vBatWarn;
node["txVoltageCalibration"] >> rhs.txVoltageCalibration;
node["vBatMin"] >> ioffset_int(rhs.vBatMin, 90);
node["vBatMax"] >> ioffset_int(rhs.vBatMax, 120);
node["backlightMode"] >> backlightModeLut >> rhs.backlightMode;
node["trainer"] >> rhs.trainer;
node["PPM_Multiplier"] >> rhs.PPM_Multiplier;
node["view"] >> rhs.view;
node["fai"] >> rhs.fai;
node["disableMemoryWarning"] >> rhs.disableMemoryWarning;
node["beepMode"] >> rhs.beeperMode;
node["alarmsFlash"] >> rhs.alarmsFlash;
node["disableAlarmWarning"] >> rhs.disableAlarmWarning;
node["disableRssiPoweroffAlarm"] >> rhs.disableRssiPoweroffAlarm;
node["disableTrainerPoweroffAlarm"] >> rhs.disableTrainerPoweroffAlarm;
node["USBMode"] >> rhs.usbMode;
node["hatsMode"] >> hatsModeLut >> rhs.hatsMode;
node["stickDeadZone"] >> rhs.stickDeadZone;
node["jackMode"] >> rhs.jackMode;
node["hapticMode"] >> rhs.hapticMode;
node["stickMode"] >> rhs.stickMode;
node["timezone"] >> rhs.timezone;
node["timezoneMinutes"] >> rhs.timezoneMinutes;
node["adjustRTC"] >> rhs.adjustRTC;
node["inactivityTimer"] >> rhs.inactivityTimer;
YamlTelemetryBaudrate internalModuleBaudrate;
if (node["telemetryBaudrate"]) {
node["telemetryBaudrate"] >> internalModuleBaudrate.value;
} else {
node["internalModuleBaudrate"] >> internalModuleBaudrate.value;
}
internalModuleBaudrate.toCpn(&rhs.internalModuleBaudrate, rhs.variant);
if (node["internalModule"]) {
node["internalModule"] >> internalModuleLut >> rhs.internalModule;
} else {
rhs.internalModule = Boards::getDefaultInternalModules(fw->getBoard());
}
node["splashMode"] >> rhs.splashMode;
node["lightAutoOff"] >> rhs.backlightDelay;
node["templateSetup"] >> rhs.templateSetup;
node["hapticLength"] >> ioffset_int(rhs.hapticLength, 2);
node["speakerPitch"] >> ifactor<unsigned int>(rhs.speakerPitch, 15);
node["hapticStrength"] >> ioffset_int(rhs.hapticStrength, 2);
node["beepLength"] >> ioffset_int(rhs.beeperLength, 2);
node["gpsFormat"] >> rhs.gpsFormat;
node["speakerVolume"] >> ioffset_int(rhs.speakerVolume, 12);
node["backlightBright"] >> rhs.backlightBright;
node["blOffBright"] >> rhs.backlightOffBright;
node["switchesDelay"] >> rhs.switchesDelay;
node["globalTimer"] >> rhs.globalTimer;
node["bluetoothName"] >> rhs.bluetoothName;
node["bluetoothBaudrate"] >> rhs.bluetoothBaudrate;
node["bluetoothMode"] >> bluetoothModeLut >> rhs.bluetoothMode;
node["countryCode"] >> rhs.countryCode;
node["jitterFilter"] >> rhs.noJitterFilter; // PR1363 : read old name and
node["noJitterFilter"] >> rhs.noJitterFilter; // new, but don't write old
node["disableRtcWarning"] >> rhs.rtcCheckDisable; // TODO: verify
node["audioMuteEnable"] >> rhs.muteIfNoSound;
node["keysBacklight"] >> rhs.keysBacklight;
node["rotEncDirection"] >> rhs.rotEncMode; // PR2045: read old name and
node["rotEncMode"] >> rhs.rotEncMode; // new, but don't write old
node["imperial"] >> rhs.imperial;
node["ppmunit"] >> rhs.ppmunit;
node["ttsLanguage"] >> rhs.ttsLanguage;
node["uiLanguage"] >> rhs.uiLanguage;
node["beepVolume"] >> ioffset_int(rhs.beepVolume, 2);
node["wavVolume"] >> ioffset_int(rhs.wavVolume, 2);
node["varioVolume"] >> ioffset_int(rhs.varioVolume, 2);
node["varioPitch"] >> ifactor<int>(rhs.varioPitch, 10);
node["varioRange"] >> ifactor<int>(rhs.varioRange, 15);
node["varioRepeat"] >> rhs.varioRepeat;
node["backgroundVolume"] >> ioffset_int(rhs.backgroundVolume, 2);
node["modelQuickSelect"] >> rhs.modelQuickSelect;
node["dontPlayHello"] >> rhs.dontPlayHello;
node["invertLCD"] >> rhs.invertLCD;
// depreciated v2.7 replaced by serialPort
if (node["auxSerialMode"]) {
node["auxSerialMode"] >> oldUartModeLut >>
rhs.serialPort[GeneralSettings::SP_AUX1];
}
// depreciated v2.7 replaced by serialPort
if (node["aux2SerialMode"]) {
node["aux2SerialMode"] >> oldUartModeLut >>
rhs.serialPort[GeneralSettings::SP_AUX2];
}
if (node["serialPort"]) {
Node serialPort = node["serialPort"];
if (serialPort.IsMap()) {
for (const auto& port : serialPort) {
YAML::Node port_nr = port.first >> serialPortLut;
if (port_nr) {
int p = port_nr.as<int>();
if (p >= 0 && p < GeneralSettings::SP_COUNT && port.second.IsMap()) {
port.second["mode"] >> uartModeLut >> rhs.serialPort[p];
// introduced v2.8
Node port_pwr = port.second["power"];
if (port_pwr && port_pwr.IsScalar()) {
try {
int pwr = port_pwr.as<int>();
if (pwr == 0 || pwr == 1)
rhs.serialPower[p] = pwr;
} catch(...) {}
}
}
}
}
}
}
node["antennaMode"] >> antennaModeLut >> rhs.antennaMode;
node["backlightColor"] >> rhs.backlightColor;
node["pwrOnSpeed"] >> rhs.pwrOnSpeed;
node["pwrOffSpeed"] >> rhs.pwrOffSpeed;
node["pwrOffIfInactive"] >> rhs.pwrOffIfInactive; // Power off after inactivity
node["disablePwrOnOffHaptic"] >> rhs.disablePwrOnOffHaptic;
if (node["customFn"]) {
// decode common for radio GF and model SF and conversion test assumes decoding a model
modelSettingsVersion = radioSettingsVersion;
node["customFn"] >> rhs.customFn;
}
// the GeneralSettings struct is initialised to hardware definition defaults which is fine for new settings
// however when parsing saved settings set all inputs to None and override with parsed values
// thus any inputs not parsed will be None rather than the default
// exceptions:
// preserve those hardware defaults never written to yaml
for (int i = 0; i < CPN_MAX_INPUTS; i++) {
if (Boards::isInputConfigurable(i))
rhs.inputConfig[i].flexType = (Board::FlexType)Board::FLEX_NONE;
}
if (node["sticksConfig"]) {
YamlStickConfig stickConfig;
node["sticksConfig"] >> stickConfig;
// merge
stickConfig.copy(rhs.inputConfig);
}
if (node["potsConfig"]) {
YamlPotConfig potsConfig;
node["potsConfig"] >> potsConfig;
// merge
potsConfig.copy(rhs.inputConfig);
}
// for parsing pre v2.10 config - this is not encoded
if (node["slidersConfig"]) {
YamlSliderConfig slidersConfig;
node["slidersConfig"] >> slidersConfig;
// merge
slidersConfig.copy(rhs.inputConfig);
}
// the GeneralSettings struct is initialised to hardware definition defaults which is fine for new settings
// however when parsing saved settings set all switches to None and override with parsed values
// thus any switches not parsed will be None rather than the default
for (int i = 0; i < CPN_MAX_SWITCHES; i++) {
if (Boards::getCFSIndexForSwitch(i) < 0)
rhs.switchConfig[i].type = Board::SWITCH_NOT_AVAILABLE;
}
if (node["switchConfig"]) {
YamlSwitchConfig switchConfig;
node["switchConfig"] >> switchConfig;
switchConfig.copy(rhs.switchConfig);
}
// MUST be parsed after switchConfig
if (node["flexSwitches"]) {
YamlSwitchesFlex flexSwitches;
node["flexSwitches"] >> flexSwitches;
// merge
flexSwitches.copy(rhs.switchConfig);
}
node["ownerRegistrationID"] >> rhs.registrationId;
// depreciated in 2.11
node["gyroMax"] >> rhs.imuMax;
node["gyroOffset"] >> rhs.imuOffset;
node["imuMax"] >> rhs.imuMax;
node["imuOffset"] >> rhs.imuOffset;
// OneBit sampling (X9D only?)
node["uartSampleMode"] >> rhs.uartSampleMode;
node["selectedTheme"] >> rhs.selectedTheme;
node["backlightSrc"] >> rhs.backlightSrc;
node["volumeSrc"] >> rhs.volumeSrc;
// Radio level tabs control (global settings)
node["radioThemesDisabled"] >> rhs.radioThemesDisabled;
node["radioGFDisabled"] >> rhs.radioGFDisabled;
node["radioTrainerDisabled"] >> rhs.radioTrainerDisabled;
// Model level tabs control (global setting)
node["modelHeliDisabled"] >> rhs.modelHeliDisabled;
node["modelFMDisabled"] >> rhs.modelFMDisabled;
node["modelCurvesDisabled"] >> rhs.modelCurvesDisabled;
node["modelGVDisabled"] >> rhs.modelGVDisabled;
node["modelLSDisabled"] >> rhs.modelLSDisabled;
node["modelSFDisabled"] >> rhs.modelSFDisabled;
node["modelCustomScriptsDisabled"] >> rhs.modelCustomScriptsDisabled;
node["modelTelemetryDisabled"] >> rhs.modelTelemetryDisabled;
node["modelSelectLayout"] >> rhs.modelSelectLayout;
node["labelSingleSelect"] >> rhs.labelSingleSelect;
node["labelMultiMode"] >> rhs.labelMultiMode;
node["favMultiMode"] >> rhs.favMultiMode;
if (node["keyShortcuts"]) {
for (int i = 0; i < MAX_KEYSHORTCUTS; i += 1) {
std::string val = node["keyShortcuts"][std::to_string(i)]["shortcut"].as<std::string>();
if (rhs.keyShortcutTools[i]) delete rhs.keyShortcutTools[i];
if (val.substr(0, 4) == "APP,") {
rhs.keyShortcuts[i] = GeneralSettings::QM_APP;
rhs.keyShortcutTools[i] = new char[val.size() - 3];
strncpy(rhs.keyShortcutTools[i], val.substr(4).c_str(), val.size() - 4);
rhs.keyShortcutTools[i][val.size() - 4] = 0;
} else {
node["keyShortcuts"][std::to_string(i)]["shortcut"] >> QMPageLut >> rhs.keyShortcuts[i];
rhs.keyShortcutTools[i] = nullptr;
}
}
}
if (node["qmFavorites"]) {
for (int i = 0; i < MAX_QMFAVOURITES; i += 1)
if (node["qmFavorites"][std::to_string(i)]) {
std::string val = node["qmFavorites"][std::to_string(i)]["shortcut"].as<std::string>();
if (rhs.qmFavoritesTools[i]) delete rhs.qmFavoritesTools[i];
if (val.substr(0, 4) == "APP,") {
rhs.qmFavorites[i] = GeneralSettings::QM_APP;
rhs.qmFavoritesTools[i] = new char[val.size() - 3];
strncpy(rhs.qmFavoritesTools[i], val.substr(4).c_str(), val.size() - 4);
rhs.qmFavoritesTools[i][val.size() - 4] = 0;
} else {
node["qmFavorites"][std::to_string(i)]["shortcut"] >> QMPageLut >> rhs.qmFavorites[i];
rhs.qmFavoritesTools[i] = nullptr;
}
}
}
node["oneLogPerDay"] >> rhs.oneLogPerDay;
// override critical settings after import
// TODO: for consistency move up call stack to use existing eeprom and profile conversions
if (needsConversion)
rhs.init();
// perform integrity checks and fix-ups
YamlValidateNames(rhs, fw->getBoard());
rhs.validateFlexSwitches();
return true;
}
} // namespace YAML
@@ -0,0 +1,41 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "yaml_ops.h"
#include "generalsettings.h"
struct YamlTelemetryBaudrate {
unsigned int value;
YamlTelemetryBaudrate() = default;
YamlTelemetryBaudrate(const unsigned int* moduleBaudrate);
void toCpn(unsigned int* moduleBaudrate, unsigned int variant);
};
namespace YAML {
template<>
struct convert<GeneralSettings> {
static Node encode(const GeneralSettings& rhs);
static bool decode(const Node& node, GeneralSettings& rhs);
};
}
@@ -0,0 +1,211 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "yaml_logicalswitchdata.h"
#include "yaml_rawswitch.h"
#include "yaml_rawsource.h"
static const YamlLookupTable funcLut = {
{LS_FN_OFF, "FUNC_NONE"},
{LS_FN_VEQUAL, "FUNC_VEQUAL"},
{LS_FN_VALMOSTEQUAL, "FUNC_VALMOSTEQUAL"},
{LS_FN_VPOS, "FUNC_VPOS"},
{LS_FN_VNEG, "FUNC_VNEG"},
{LS_FN_APOS, "FUNC_APOS"},
{LS_FN_ANEG, "FUNC_ANEG"},
{LS_FN_AND, "FUNC_AND"},
{LS_FN_OR, "FUNC_OR"},
{LS_FN_XOR, "FUNC_XOR"},
{LS_FN_EDGE, "FUNC_EDGE"},
{LS_FN_EQUAL, "FUNC_EQUAL"},
{LS_FN_GREATER, "FUNC_GREATER"},
{LS_FN_LESS, "FUNC_LESS"},
{LS_FN_DPOS, "FUNC_DIFFEGREATER"},
{LS_FN_DAPOS, "FUNC_ADIFFEGREATER"},
{LS_FN_TIMER, "FUNC_TIMER"},
{LS_FN_STICKY, "FUNC_STICKY"},
};
static int timerValue2lsw(uint32_t t)
{
if (t < 20) {
return t - 129;
} else if (t < 600) {
return t / 5 - 113;
} else {
return t / 10 - 53;
}
}
// val = [-129,-110] => [0,19] (step 1)
// val = [-109,6] => [20,595] (step 5)
// val = [7,122] => [600,1750] (step 10)
//
static int lswTimerValue(int val)
{
return (val < -109 ? 129+val : (val < 7 ? (113+val)*5 : (53+val)*10));
}
namespace YAML
{
Node convert<LogicalSwitchData>::encode(const LogicalSwitchData& rhs)
{
Node node;
node["func"] = funcLut << rhs.func;
std::string def;
switch (rhs.getFunctionFamily()) {
case LS_FAMILY_VBOOL:
case LS_FAMILY_STICKY: {
def += YamlRawSwitchEncode(RawSwitch(rhs.val1));
def += ",";
def += YamlRawSwitchEncode(RawSwitch(rhs.val2));
} break;
case LS_FAMILY_EDGE: {
def += YamlRawSwitchEncode(RawSwitch(rhs.val1));
def += ",";
def += std::to_string(lswTimerValue(rhs.val2));
def += ",";
if (rhs.val3 < 0) {
def += '<';
} else if (rhs.val3 == 0) {
def += '-';
} else {
def += std::to_string(lswTimerValue(rhs.val2 + rhs.val3));
}
} break;
case LS_FAMILY_VCOMP: {
def += YamlRawSourceEncode(RawSource(rhs.val1));
def += ",";
def += YamlRawSourceEncode(RawSource(rhs.val2));
} break;
case LS_FAMILY_TIMER: {
def += std::to_string(lswTimerValue(rhs.val1));
def += ",";
def += std::to_string(lswTimerValue(rhs.val2));
} break;
default: {
def += YamlRawSourceEncode(RawSource(rhs.val1));
def += ",";
def += std::to_string(rhs.val2);
} break;
}
node["def"] = def;
node["delay"] = rhs.delay;
node["duration"] = rhs.duration;
node["andsw"] = YamlRawSwitchEncode(RawSwitch(rhs.andsw));
node["lsPersist"] = (int)rhs.lsPersist;
node["lsState"] = (int)rhs.lsState;
return node;
}
bool convert<LogicalSwitchData>::decode(const Node& node,
LogicalSwitchData& rhs)
{
node["func"] >> funcLut >> rhs.func;
std::string def_str;
node["def"] >> def_str;
std::stringstream def(def_str);
switch (rhs.getFunctionFamily()) {
case LS_FAMILY_VBOOL:
case LS_FAMILY_STICKY: {
std::string sw_str;
getline(def, sw_str, ',');
rhs.val1 = YamlRawSwitchDecode(sw_str).toValue();
getline(def, sw_str);
rhs.val2 = YamlRawSwitchDecode(sw_str).toValue();
} break;
case LS_FAMILY_EDGE: {
std::string sw_str;
getline(def, sw_str, ',');
rhs.val1 = YamlRawSwitchDecode(sw_str).toValue();
int v2=0, v3=0;
def >> v2;
rhs.val2 = timerValue2lsw(v2);
def.ignore();
if (def.peek() == '<') {
rhs.val3 = -1;
} else if (def.peek() == '-') {
rhs.val3 = 0;
} else {
def >> v3;
v3 = timerValue2lsw(v3);
rhs.val3 = v3 - rhs.val2;
}
} break;
case LS_FAMILY_VCOMP: {
std::string src_str;
getline(def, src_str, ',');
if (def_str.size() >= 4 && def_str.substr(0, 4) == "lua(") {
std::string tmp_str;
getline(def, tmp_str, ',');
src_str += ("," + tmp_str);
}
rhs.val1 = YamlRawSourceDecode(src_str).toValue();
getline(def, src_str);
rhs.val2 = YamlRawSourceDecode(src_str).toValue();
} break;
case LS_FAMILY_TIMER: {
int v1=0, v2=0;
def >> v1;
rhs.val1 = timerValue2lsw(v1);
def.ignore();
def >> v2;
rhs.val2 = timerValue2lsw(v2);
} break;
default: {
std::string src_str;
getline(def, src_str, ',');
if (def_str.size() >= 4 && def_str.substr(0, 4) == "lua(") {
std::string tmp_str;
getline(def, tmp_str, ',');
src_str += ("," + tmp_str);
}
rhs.val1 = YamlRawSourceDecode(src_str).toValue();
def >> rhs.val2;
} break;
}
node["delay"] >> rhs.delay;
node["duration"] >> rhs.duration;
RawSwitch andsw;
node["andsw"] >> andsw;
rhs.andsw = andsw.toValue();
node["lsPersist"] >> rhs.lsPersist;
node["lsState"] >> rhs.lsState;
return true;
}
} // namespace YAML

Some files were not shown because too many files have changed in this diff Show More