Sync edgetx to Gitea
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
BreakBeforeBraces: Linux
|
||||
BreakConstructorInitializers: AfterColon
|
||||
...
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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:
|
||||
@@ -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 🚪'
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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})
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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.
|
||||
@@ -0,0 +1,51 @@
|
||||
[](https://github.com/EdgeTX/edgetx/releases/latest)
|
||||
[](https://github.com/EdgeTX/edgetx/releases)
|
||||
[](https://github.com/EdgeTX/edgetx/blob/main/LICENSE)
|
||||
[](https://github.com/EdgeTX/edgetx/actions/workflows/build_fw.yml)
|
||||
[](https://codespaces.new/EdgeTX/edgetx)
|
||||
[](https://conventionalcommits.org)
|
||||
[](https://discord.gg/wF9wUKnZ6H)
|
||||
[](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
@@ -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" },
|
||||
]
|
||||
@@ -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()
|
||||
@@ -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")
|
||||
@@ -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
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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}
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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}")
|
||||
@@ -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()
|
||||
@@ -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})
|
||||
@@ -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}")
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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}")
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
@@ -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
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
Mon Jan 26 18:05:25 AEST 2026
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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"));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user