mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-09 23:27:24 -05:00
Compare commits
197 Commits
v0.4.0-bet
...
v0.5.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4a85a4ed7 | ||
|
|
f61a415b43 | ||
|
|
29ed3b4553 | ||
|
|
a87309e792 | ||
|
|
ddb5b5ef87 | ||
|
|
7a58bf85b6 | ||
|
|
8707eb2c88 | ||
|
|
08499ac73f | ||
|
|
70170ff70f | ||
|
|
bad5589c0d | ||
|
|
7de28519a1 | ||
|
|
bbd907514e | ||
|
|
4a68cd9407 | ||
|
|
dc327a2855 | ||
|
|
9af593a785 | ||
|
|
24f4c22cdd | ||
|
|
bc462b9652 | ||
|
|
047dec6da1 | ||
|
|
bf935555bb | ||
|
|
efe36b0682 | ||
|
|
a18d4498aa | ||
|
|
803dc2531c | ||
|
|
1b062bcd06 | ||
|
|
a24c3736f4 | ||
|
|
ac61b681e6 | ||
|
|
0866bfd555 | ||
|
|
79fed701d9 | ||
|
|
48a93990e8 | ||
|
|
414ba25d61 | ||
|
|
f67f90e8b0 | ||
|
|
5751c70212 | ||
|
|
4e15c426a8 | ||
|
|
ceda189260 | ||
|
|
fce0ae33e7 | ||
|
|
fc5c6ef841 | ||
|
|
d0033578d5 | ||
|
|
670ec3a097 | ||
|
|
8bfb1d5470 | ||
|
|
46a916a945 | ||
|
|
459e77390a | ||
|
|
f4ab46b79d | ||
|
|
3c58e98788 | ||
|
|
36de81248c | ||
|
|
925353d515 | ||
|
|
7c32897321 | ||
|
|
cbeda19d27 | ||
|
|
8148ca48af | ||
|
|
adcb55ffbc | ||
|
|
1152b13220 | ||
|
|
166383e1ed | ||
|
|
0d5d1b20e3 | ||
|
|
77ea43f61a | ||
|
|
f988344222 | ||
|
|
60489e1fef | ||
|
|
ac8aa17a80 | ||
|
|
25a42126df | ||
|
|
1b90e0b50c | ||
|
|
7f5ad7eb20 | ||
|
|
0e6713b6e2 | ||
|
|
96d3996bec | ||
|
|
0fb148d31e | ||
|
|
cebca28b19 | ||
|
|
af8cc0255e | ||
|
|
775ae00861 | ||
|
|
1b80f5f1b3 | ||
|
|
f9a7bbeddf | ||
|
|
58a0264565 | ||
|
|
6d2af95e9a | ||
|
|
bf46b19dc9 | ||
|
|
7749fd10f1 | ||
|
|
90bc321aca | ||
|
|
bad4fae1b8 | ||
|
|
c3fc435fdc | ||
|
|
2ab0c78728 | ||
|
|
187989f6b8 | ||
|
|
3b83412bcb | ||
|
|
968ea458da | ||
|
|
a9f4fbbdd4 | ||
|
|
6b3b45e9af | ||
|
|
2355dfce07 | ||
|
|
0e8c9591e4 | ||
|
|
54e4771f36 | ||
|
|
b2eb245feb | ||
|
|
e826314d06 | ||
|
|
0eec2e7ab8 | ||
|
|
5e9c239ea3 | ||
|
|
1fd9b0fbcb | ||
|
|
8413343e76 | ||
|
|
2a0427c559 | ||
|
|
5d9d232585 | ||
|
|
f0f0329f9b | ||
|
|
2a45849e6d | ||
|
|
aa89079e28 | ||
|
|
31b9ee5910 | ||
|
|
6a54fe101c | ||
|
|
87b3d34024 | ||
|
|
b8bbe9264a | ||
|
|
0c7dd192b0 | ||
|
|
8b487d79e4 | ||
|
|
cb83a8e870 | ||
|
|
0bc11a07cf | ||
|
|
560cbad5d7 | ||
|
|
87e9bf8749 | ||
|
|
770f58c668 | ||
|
|
4b806ecfde | ||
|
|
7303e06943 | ||
|
|
846b249fb3 | ||
|
|
67f23494e2 | ||
|
|
d7efb627d7 | ||
|
|
08bc0b9963 | ||
|
|
d1ed46860c | ||
|
|
38f694d262 | ||
|
|
75ef3217ac | ||
|
|
2847dff64a | ||
|
|
f0d9ef99ea | ||
|
|
67702096d7 | ||
|
|
88f124af9d | ||
|
|
d34460c4e3 | ||
|
|
f4f3db7436 | ||
|
|
3ebfa90ca1 | ||
|
|
733fdd0d7a | ||
|
|
09893605eb | ||
|
|
57468ef932 | ||
|
|
c6afb1c133 | ||
|
|
05c0b51fde | ||
|
|
de8cfa24f9 | ||
|
|
fe714c69a7 | ||
|
|
ac219cf2ba | ||
|
|
77b1102100 | ||
|
|
5117e6cbe8 | ||
|
|
61c4e13c3e | ||
|
|
f1190b7be3 | ||
|
|
49e7087b9b | ||
|
|
c397b5a601 | ||
|
|
c809dcc609 | ||
|
|
7eec82deee | ||
|
|
b061db1189 | ||
|
|
8715dd2d6b | ||
|
|
63b0aaaa7f | ||
|
|
d20dd1af8c | ||
|
|
e1f32d3d0b | ||
|
|
eaa3433034 | ||
|
|
838016c89f | ||
|
|
030a88f5a3 | ||
|
|
de3ba5529a | ||
|
|
7962758e05 | ||
|
|
0f9d1c0792 | ||
|
|
48be38a2d8 | ||
|
|
207893e5a8 | ||
|
|
e07d6fa854 | ||
|
|
e26698d0d8 | ||
|
|
a8837c8e2a | ||
|
|
7395f02fb8 | ||
|
|
706ada2940 | ||
|
|
d957846ab0 | ||
|
|
bbe90ca74b | ||
|
|
96611fa2c2 | ||
|
|
c94edf05ef | ||
|
|
74adb6f30c | ||
|
|
421abe5768 | ||
|
|
1d003c561e | ||
|
|
b22177ee17 | ||
|
|
a1e8c761ef | ||
|
|
ad194d1bab | ||
|
|
2bb5082943 | ||
|
|
2c3648f8c2 | ||
|
|
3d816c3570 | ||
|
|
e1194d26bd | ||
|
|
8167e9e9f2 | ||
|
|
3b357c4d40 | ||
|
|
89cb54676d | ||
|
|
73c251ff4d | ||
|
|
0d51b39604 | ||
|
|
af0dbf1566 | ||
|
|
01146a5624 | ||
|
|
316cdb4068 | ||
|
|
91cae70fa1 | ||
|
|
2723926f83 | ||
|
|
2aff9ee817 | ||
|
|
c87e6553dc | ||
|
|
5d580544e7 | ||
|
|
08ea0e0f18 | ||
|
|
c8b441dbd4 | ||
|
|
1aa22789e5 | ||
|
|
cff5494db4 | ||
|
|
9bd6507342 | ||
|
|
379461e649 | ||
|
|
cc52ae5bd7 | ||
|
|
39f6d6c831 | ||
|
|
649258bd2b | ||
|
|
de9431f649 | ||
|
|
18f9e38090 | ||
|
|
2e37f1e687 | ||
|
|
5e8f2d0523 | ||
|
|
9fb6c33319 | ||
|
|
fdecac4230 | ||
|
|
85c27a6cca |
34
.github/workflows/gradle.yml
vendored
Normal file
34
.github/workflows/gradle.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
|
||||
|
||||
name: Java CI with Gradle
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'liberica'
|
||||
java-package: 'jdk+fx'
|
||||
- name: Build with Gradle
|
||||
uses: gradle/gradle-build-action@937999e9cc2425eddc7fd62d1053baf041147db7
|
||||
with:
|
||||
arguments: clean build
|
||||
4
.idea/dictionaries/denny.xml
generated
4
.idea/dictionaries/denny.xml
generated
@@ -2,8 +2,11 @@
|
||||
<dictionary name="denny">
|
||||
<words>
|
||||
<w>bahroo</w>
|
||||
<w>basebanded</w>
|
||||
<w>broadcastify</w>
|
||||
<w>correlator</w>
|
||||
<w>deinterleave</w>
|
||||
<w>descrambled</w>
|
||||
<w>dinf</w>
|
||||
<w>esnid</w>
|
||||
<w>fleetsync</w>
|
||||
@@ -23,6 +26,7 @@
|
||||
<w>shoutcast</w>
|
||||
<w>streamable</w>
|
||||
<w>talkgroup</w>
|
||||
<w>talkgroups</w>
|
||||
<w>tgid</w>
|
||||
<w>ultravox</w>
|
||||
<w>wacn</w>
|
||||
|
||||
5
.idea/misc.xml
generated
5
.idea/misc.xml
generated
@@ -1,7 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_13" default="true" project-jdk-name="13" project-jdk-type="JavaSDK">
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="liberica-19.0.1.fx" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
15
CHANGELOG
15
CHANGELOG
@@ -1,3 +1,18 @@
|
||||
0.4.0 20200404
|
||||
- Release includes embedded Java 14 & JavaFX 13 runtime
|
||||
- Adds Polyphase Channelizer with 2x channel oversampling and redesigned source management
|
||||
- Adds P25 Phase 2 decoder
|
||||
- Adds P25 radio identifier aliasing
|
||||
- JMBE library 1.0.4 with support for AMBE tone metadata
|
||||
- New JavaFX User Preferences dialog
|
||||
- HackRF tuner enhanced (sample rates and saturation issues)
|
||||
- Adds support for Airspy mini tuner
|
||||
- Redesigned audio sub-system that uses AudioSegments
|
||||
- USB tuner bus saturation management/monitoring
|
||||
- Bitstream (.bits) recording
|
||||
- MP3 Audio Recording
|
||||
- Numerous bug fixes
|
||||
|
||||
0.3.4 20180418
|
||||
- New P25 C4FM decoder that uses Differential QPSK decoder instead of FM demodulation with 4FSK.
|
||||
- Adds channel auto-start and ordering capability with startup dialog allowing user to invoke or bypass channel
|
||||
|
||||
60
README.md
60
README.md
@@ -1,66 +1,24 @@
|
||||

|
||||
|
||||
# sdrtrunk
|
||||
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
|
||||
|
||||
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted_V0.3.0)
|
||||
* [User's Manual Version 0.3.0 and 0.4.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual_V0.3.0)
|
||||
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/Getting-Started)
|
||||
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/User-Manual)
|
||||
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
|
||||
* [Support Group](https://groups.google.com/forum/#!forum/sdrtrunk)
|
||||
* [Discord Support Channel](https://discord.gg/HJQaKYE)
|
||||
* [Support](https://github.com/DSheirer/sdrtrunk/wiki/Support)
|
||||
|
||||

|
||||
**Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot
|
||||

|
||||
**Figure 1:** sdrtrunk Application Screenshot
|
||||
|
||||
# End User Instructions:
|
||||
|
||||
If you simply want to download and run the program, please follow these instructions.
|
||||
|
||||
## Download the latest sdrtrunk release for your operating system
|
||||
|
||||
## Download the Latest Release
|
||||
All release versions of sdrtrunk are available from the [releases](https://github.com/DSheirer/sdrtrunk/releases) tab.
|
||||
|
||||
* **(alpha)** These versions are under development feature previews and likely to contain bugs and unexpected behavior.
|
||||
* **(beta)** These versions are currently being tested for bugs and functionality prior to final release.
|
||||
* **(final)** These versions have been tested and are the current release version.
|
||||
|
||||
## Unzip the release
|
||||
|
||||
Use 7-zip or any zip utility to unzip the release file
|
||||
|
||||
## Start the application
|
||||
|
||||
Once unzipped, open a command prompt to where you unzipped the release. Change to the **/bin** directory and use the launch script to start the application:
|
||||
* **Windows** sdr-trunk.bat
|
||||
* **Linux/OSX** ./sdr-trunk
|
||||
|
||||
## Optional - P25 Audio
|
||||
If you're using sdrtrunk with a P25 trunked radio system, the [JMBE](https://github.com/DSheirer/sdrtrunk/wiki/JMBE) wiki page contains instructions for downloading the JMBE audio library source code and compiling the JMBE library. Once you have compiled the library, launch the sdrtrunk application. From the menu bar, choose **View >> Preferences**. In the **JMBE Audio Codec** section, update the path to where your compiled JMBE library is located. Any channels that are started after you set the path will be able to produce P25 audio.
|
||||
|
||||
## Minimum System Requirements
|
||||
* **Operating System:** Windows (32 or 64-bit), Linux (32 or 64-bit) or Mac/Linux (64-bit, 10.14 or higher)
|
||||
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac/Linux (64-bit, 10.14 or higher)
|
||||
* **CPU:** 4-core
|
||||
* **RAM:** 8GB or more (preferred). Depending on usage, 4GB may be sufficient.
|
||||
|
||||
# Developer Instructions:
|
||||
|
||||
If you're interested in modifying and/or compiling the source code, please follow these instructions to use gradle to compile the code.
|
||||
|
||||
## Build the project
|
||||
sdrtrunk uses the gradle build system. This requires OpenJDK 11 or higher installed on your local compuber. Use the gradle wrapper to build the source code:
|
||||
|
||||
### Linux
|
||||
```
|
||||
./gradlew clean build
|
||||
```
|
||||
### Windows
|
||||
```
|
||||
gradlew.bat clean build
|
||||
```
|
||||
|
||||
The **/build/distributions** folder will contain the zip file of the compiled program. Unzip it and launch the program from the scripts in the **/bin** directory.
|
||||
|
||||
## Development
|
||||
All dependencies/versions are controlled from build.gradle.
|
||||
To change the new release version tag of artifact - change property:
|
||||
```
|
||||
version = '0.4.0'
|
||||
```
|
||||
|
||||
318
build.gradle
318
build.gradle
@@ -1,101 +1,121 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
plugins {
|
||||
id 'application'
|
||||
id 'java'
|
||||
id 'idea'
|
||||
id 'org.openjfx.javafxplugin' version '0.0.8'
|
||||
id 'org.beryx.runtime' version '1.8.0'
|
||||
id 'org.beryx.runtime' version '1.12.7'
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
||||
version = '0.4.0-beta.8'
|
||||
sourceCompatibility = '13'
|
||||
version = '0.5.0'
|
||||
sourceCompatibility = '17'
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs 'src/main'
|
||||
test.java.srcDirs 'src/test'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.assertj:assertj-core:3.8.0'
|
||||
|
||||
// JUnit Tests
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.assertj:assertj-core:3.23.1'
|
||||
testImplementation('org.junit.jupiter:junit-jupiter-api:5.9.0')
|
||||
testImplementation 'org.mockito:mockito-core:4.8.0'
|
||||
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
|
||||
|
||||
//Jitpack imports
|
||||
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
|
||||
implementation 'com.github.dnault:libresample4j:master'
|
||||
implementation 'com.github.DSheirer.jmbe:jmbe-api:1.0.0'
|
||||
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
|
||||
|
||||
//mavenCentral/jcenter imports
|
||||
implementation 'org.apache.commons:commons-math3:3.6.1'
|
||||
implementation 'ch.qos.logback:logback-core:1.2.3'
|
||||
implementation 'ch.qos.logback:logback-classic:1.2.3'
|
||||
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.8'
|
||||
implementation 'com.fazecast:jSerialComm:2.5.0'
|
||||
implementation 'ch.qos.logback:logback-classic:1.4.5'
|
||||
implementation 'ch.qos.logback:logback-core:1.4.5'
|
||||
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.0'
|
||||
implementation 'com.fazecast:jSerialComm:2.9.3'
|
||||
implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
|
||||
implementation 'com.github.jiconfont:jiconfont-javafx:1.0.0'
|
||||
implementation 'com.github.jiconfont:jiconfont-swing:1.0.1'
|
||||
implementation 'com.github.wendykierp:JTransforms:3.1'
|
||||
implementation 'com.google.guava:guava:27.0.1-jre'
|
||||
implementation 'com.google.code.gson:gson:2.10'
|
||||
implementation 'com.google.guava:guava:31.1-jre'
|
||||
implementation 'com.jidesoft:jide-oss:3.6.18'
|
||||
implementation 'com.miglayout:miglayout-swing:5.2'
|
||||
implementation 'com.miglayout:miglayout-swing:11.0'
|
||||
implementation 'com.mpatric:mp3agic:0.9.1'
|
||||
implementation 'commons-io:commons-io:2.11.0'
|
||||
implementation 'eu.hansolo:charts:1.0.5'
|
||||
implementation 'io.github.dsheirer:radio-reference-api:15.1.2'
|
||||
implementation 'io.github.dsheirer:radio-reference-api:15.1.9'
|
||||
implementation 'javax.usb:usb-api:1.0.2'
|
||||
implementation 'net.coderazzi:tablefilter-swing:5.4.0'
|
||||
implementation 'org.apache.commons:commons-lang3:3.8.1'
|
||||
implementation 'org.apache.commons:commons-io:1.3.2'
|
||||
implementation 'org.apache.mina:mina-core:2.0.19'
|
||||
implementation 'org.apache.mina:mina-http:2.0.19'
|
||||
implementation 'org.controlsfx:controlsfx:11.0.1'
|
||||
implementation 'org.slf4j:slf4j-api:1.7.25'
|
||||
implementation 'net.coderazzi:tablefilter-swing:5.5.4'
|
||||
implementation 'org.apache.commons:commons-compress:1.21'
|
||||
implementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||
implementation 'org.apache.commons:commons-math3:3.6.1'
|
||||
implementation 'org.apache.commons:commons-csv:1.9.0'
|
||||
implementation 'org.apache.mina:mina-core:2.2.1'
|
||||
implementation 'org.apache.mina:mina-http:2.2.1'
|
||||
implementation 'org.controlsfx:controlsfx:11.1.2'
|
||||
implementation 'org.rauschig:jarchivelib:1.2.0'
|
||||
implementation 'org.slf4j:slf4j-api:2.0.5'
|
||||
implementation 'org.usb4java:libusb4java:1.3.0'
|
||||
implementation 'org.usb4java:usb4java:1.3.0'
|
||||
implementation 'org.usb4java:usb4java-javax:1.3.0'
|
||||
implementation 'io.github.dsheirer:libusb4java-darwin-aarch64:1.3.1' //usb4java native lib for OSX M1 cpu
|
||||
implementation 'pl.edu.icm:JLargeArrays:1.6'
|
||||
}
|
||||
|
||||
//The must remain as compile options to ensure they are included in the build to produce multi-OS images
|
||||
compile "org.openjfx:javafx-base:13:win"
|
||||
compile "org.openjfx:javafx-base:13:linux"
|
||||
compile "org.openjfx:javafx-base:13:mac"
|
||||
compile "org.openjfx:javafx-controls:13.0.1:win"
|
||||
compile "org.openjfx:javafx-controls:13.0.1:linux"
|
||||
compile "org.openjfx:javafx-controls:13.0.1:mac"
|
||||
compile "org.openjfx:javafx-graphics:13.0.1:win"
|
||||
compile "org.openjfx:javafx-graphics:13.0.1:linux"
|
||||
compile "org.openjfx:javafx-graphics:13.0.1:mac"
|
||||
compile "org.openjfx:javafx-swing:13.0.1:win"
|
||||
compile "org.openjfx:javafx-swing:13.0.1:linux"
|
||||
compile "org.openjfx:javafx-swing:13.0.1:mac"
|
||||
/**
|
||||
* This is needed for the JDK17 vector API ... until it moves out of incubation
|
||||
*/
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs.add("--add-modules=jdk.incubator.vector") //Needed while Panama Vector API remains in incubator
|
||||
}
|
||||
|
||||
application {
|
||||
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
|
||||
applicationDefaultJvmArgs =
|
||||
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
|
||||
'--add-modules=jdk.incubator.vector', //Needed while Panama Vector API remains in incubator
|
||||
'--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED'] //Windows Swing - jide-oss library
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes (
|
||||
'Implementation-Title' : 'sdrtrunk project',
|
||||
'Implementation-Version' : archiveVersion,
|
||||
'Created-By' : "Gradle ${gradle.gradleVersion}",
|
||||
'Build-Timestamp' : new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
|
||||
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
|
||||
'Build-OS' : "${System.properties['os.name']} (${System.properties['os.arch']} ${System.properties['os.version']}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
idea {
|
||||
@@ -105,124 +125,102 @@ idea {
|
||||
}
|
||||
}
|
||||
|
||||
javafx {
|
||||
modules = ['javafx.base', 'javafx.controls', 'javafx.graphics', 'javafx.swing']
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes (
|
||||
'Implementation-Title' : 'sdrtrunk project',
|
||||
'Implementation-Version' : archiveVersion,
|
||||
'Class-Path' : 'jmbe-1.0.1.jar',
|
||||
'Created-By' : "Gradle ${gradle.gradleVersion}",
|
||||
'Build-Timestamp' : new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
|
||||
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
|
||||
'Build-OS' : "${System.properties['os.name']} (${System.properties['os.arch']} ${System.properties['os.version']}"
|
||||
)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Runtime target OS and CPU image names. We append version here so that version details are at end of zip filename.
|
||||
*/
|
||||
String targetLinuxAarch64 = 'linux-aarch64-v' + version
|
||||
String targetLinuxX86_64 = 'linux-x86_64-v' + version
|
||||
String targetOsxAarch64 = 'osx-aarch64-v' + version
|
||||
String targetOsxX86_64 = 'osx-x86_64-v' + version
|
||||
String targetWindowsX86_64 = 'windows-x86_64-v' + version
|
||||
|
||||
/**
|
||||
* Java Development Kit (JDK) locations. In order to build OS-specific images, these paths must point to the 'bin'
|
||||
* directory within a JDK for each of the specified architectures. These JDKs are used by the runtime and runtimeZip
|
||||
* tasks to produce platform-specific builds. If none of these paths exists, then an image will be created for the
|
||||
* host OS using the installed JDK.
|
||||
* Optional pre-downloaded Java Development Kit (JDK) for each target OS and CPU. These locations are hard-coded to the
|
||||
* author's development environment, but can be changed to your environment if you want to use this build pattern.
|
||||
*/
|
||||
def jdk_linux_x86_64 = '/media/denny/WD250GB/java/bellsoft/linux-x64/jdk-13.0.1'
|
||||
def jdk_linux_x86_32 = '/media/denny/WD250GB/java/bellsoft/linux-x86/jdk-13.0.1'
|
||||
def jdk_osx_x86_64 = '/media/denny/WD250GB/java/bellsoft/osx-x64/jdk-13.0.1.jdk'
|
||||
def jdk_windows_x86_64 = '/media/denny/WD250GB/java/bellsoft/windows-x64/jdk-13.0.1'
|
||||
def jdk_windows_x86_32 = '/media/denny/WD250GB/java/bellsoft/windows-x86/jdk-13.0.1'
|
||||
String jdk_base = '/home/denny/java_jdks/'
|
||||
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-19.0.1-full'
|
||||
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-19.0.1-full'
|
||||
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-19.0.1-full.jdk'
|
||||
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-19.0.1-full.jdk'
|
||||
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-19.0.1-full'
|
||||
|
||||
def file_jdk_linux_x86_64 = new File(jdk_linux_x86_64)
|
||||
def file_jdk_linux_x86_32 = new File(jdk_linux_x86_32)
|
||||
def file_jdk_osx_x86_64 = new File(jdk_osx_x86_64)
|
||||
def file_jdk_windows_x86_64 = new File(jdk_windows_x86_64)
|
||||
def file_jdk_windows_x86_32 = new File(jdk_windows_x86_32)
|
||||
boolean hasTargetJdks = file(jdk_linux_x86_64).exists() || file(jdk_linux_aarch64).exists() ||
|
||||
file(jdk_osx_x86_64).exists() || file(jdk_osx_aarch64).exists() || file(jdk_windows_x86_64).exists()
|
||||
|
||||
def hasTargetJdk = file_jdk_linux_x86_32.exists() ||
|
||||
file_jdk_linux_x86_64.exists() ||
|
||||
file_jdk_osx_x86_64.exists() ||
|
||||
file_jdk_windows_x86_32.exists() ||
|
||||
file_jdk_windows_x86_64.exists()
|
||||
/**
|
||||
* Auto-download target JDKs. Defaults to false, or the user can override via the gradle command line by
|
||||
* including this option: '-PdownloadJdks=true'
|
||||
*/
|
||||
boolean downloadJdks = project.hasProperty('downloadJdks') ? project.getProperty('downloadJdks') : false
|
||||
|
||||
/**
|
||||
* Beryx Runtime plugin for creating OS and CPU targeted build(s) of the software.
|
||||
*
|
||||
* Option 1: Create a single target build using the locally installed JDK, or optionally create target builds for
|
||||
* all OS and CPU targets when pre-downloaded target JDKs are available (see 'jdk_*' properties above).
|
||||
*
|
||||
* >: gradle runtimeZip
|
||||
*
|
||||
* Option 2: Create all OS and CPU target builds by automatically downloading all JDKs needed to build the target images
|
||||
*
|
||||
* >: gradle runtimeZip -PdownloadJdks=true
|
||||
*/
|
||||
runtime {
|
||||
if(file_jdk_linux_x86_64.exists())
|
||||
{
|
||||
targetPlatform('linux-x86_64-v' + version, jdk_linux_x86_64)
|
||||
//Build by automatically downloading JDKs for the OS and CPU targets
|
||||
if(downloadJdks) {
|
||||
println("Building all runtime images")
|
||||
println("The [ > jre: ] build phase downloads several target JDKs and this may take a while ... please wait")
|
||||
targetPlatform(targetLinuxAarch64) {
|
||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-aarch64-full.tar.gz")
|
||||
}
|
||||
targetPlatform(targetLinuxX86_64) {
|
||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-amd64-full.tar.gz")
|
||||
}
|
||||
targetPlatform(targetOsxAarch64) {
|
||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-aarch64-full.tar.gz")
|
||||
}
|
||||
targetPlatform(targetOsxX86_64) {
|
||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-amd64-full.tar.gz")
|
||||
}
|
||||
targetPlatform(targetWindowsX86_64) {
|
||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-windows-amd64-full.zip")
|
||||
}
|
||||
}
|
||||
//Build using pre-downloaded JDKs
|
||||
else if(hasTargetJdks) {
|
||||
println("Building all runtime images using pre-downloaded JDKs")
|
||||
if(file(jdk_linux_aarch64).exists()) {
|
||||
targetPlatform(targetLinuxAarch64, jdk_linux_aarch64)
|
||||
}
|
||||
if(file(jdk_linux_x86_64).exists()) {
|
||||
targetPlatform(targetLinuxX86_64, jdk_linux_x86_64)
|
||||
}
|
||||
if(file(jdk_osx_aarch64).exists()) {
|
||||
targetPlatform(targetOsxAarch64, jdk_osx_aarch64)
|
||||
}
|
||||
if(file(jdk_osx_x86_64).exists()) {
|
||||
targetPlatform(targetOsxX86_64, jdk_osx_x86_64)
|
||||
}
|
||||
if(file(jdk_windows_x86_64).exists()) {
|
||||
targetPlatform(targetWindowsX86_64, jdk_windows_x86_64)
|
||||
}
|
||||
}
|
||||
//Build using locally installed JDK only
|
||||
else {
|
||||
println("Building a single runtime image using locally installed JDK")
|
||||
}
|
||||
|
||||
if(file_jdk_linux_x86_32.exists())
|
||||
{
|
||||
targetPlatform('linux-x86_32-v' + version, jdk_linux_x86_32)
|
||||
}
|
||||
//Additional modules to include with auto-detected modules.
|
||||
modules = ['jdk.crypto.ec'] //jdk.crypto.ec needed for HTTPS connections (broadcastify calls & map tile server)
|
||||
|
||||
if(file_jdk_osx_x86_64.exists())
|
||||
{
|
||||
targetPlatform('osx-x86_64-v' + version, jdk_osx_x86_64)
|
||||
}
|
||||
|
||||
if(file_jdk_windows_x86_64.exists())
|
||||
{
|
||||
targetPlatform('windows-x86_64-v' + version, jdk_windows_x86_64)
|
||||
}
|
||||
|
||||
if(file_jdk_windows_x86_32.exists())
|
||||
{
|
||||
targetPlatform('windows-x86_32-v' + version, jdk_windows_x86_32)
|
||||
}
|
||||
//Use auto-detected modules and 'add' any specified modules.
|
||||
additive = true
|
||||
|
||||
//Recommended options for reducing/shrinking runtime target image size
|
||||
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
|
||||
modules = ['java.desktop', 'java.naming', 'jdk.unsupported', 'jdk.unsupported.desktop', 'java.net.http']
|
||||
imageZip = hasTargetJdk ? file("$buildDir/image/sdr-trunk.zip") : file("$buildDir/image/sdr-trunk-" + version + ".zip")
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for existence of JDK folders for each supported platform before creating runtime images
|
||||
*/
|
||||
tasks.runtime.doFirst {
|
||||
|
||||
if(!file_jdk_linux_x86_64.exists())
|
||||
{
|
||||
println("Skipping OS Image - Linux x86 64-bit JDK was not found at " + jdk_linux_x86_64)
|
||||
}
|
||||
|
||||
if(!file_jdk_linux_x86_32.exists())
|
||||
{
|
||||
println("Skipping OS Image - Linux x86 32-bit JDK was not found at " + jdk_linux_x86_32)
|
||||
}
|
||||
|
||||
if(!file_jdk_osx_x86_64.exists())
|
||||
{
|
||||
println("Skipping OS Image - OSX x86 64-bit JDK was not found at " + jdk_osx_x86_64);
|
||||
}
|
||||
|
||||
if(!file_jdk_windows_x86_64.exists())
|
||||
{
|
||||
println("Skipping OS Image - Windows x86 64-bit JDK was not found at " + jdk_windows_x86_64)
|
||||
}
|
||||
|
||||
if(!file_jdk_windows_x86_32.exists())
|
||||
{
|
||||
println("Skipping OS Image - Windows x86 32-bit JDK was not found at " + jdk_windows_x86_32)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unused JavaFX OS-specific libraries from each platform target directory
|
||||
*/
|
||||
tasks.runtime.doLast {
|
||||
delete(fileTree('build/image/sdr-trunk-linux-x86_64-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-linux-x86_32-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-osx-x86_64-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|linux)\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-windows-x86_64-v' + version + '/lib').include { it.name ==~ /javafx.*-(linux|mac)\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-windows-x86_32-v' + version + '/lib').include { it.name ==~ /javafx.*-(linux|mac)\.jar/ })
|
||||
|
||||
//JavaFX web and media modules are transitive dependencies of ControlsFX but are not required for this application
|
||||
delete(fileTree('build/image/sdr-trunk-linux-x86_64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-linux-x86_32-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-osx-x86_64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-windows-x86_64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-windows-x86_32-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
|
||||
//Runtime images have auto appended version in image zip name. Override to include version in single target image.
|
||||
imageZip = (hasTargetJdks | downloadJdks) ? file("$buildDir/image/sdr-trunk.zip") :
|
||||
file("$buildDir/image/sdr-trunk-" + version + ".zip")
|
||||
}
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
36
gradle/wrapper/gradle-wrapper.properties
vendored
36
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,27 +1,25 @@
|
||||
#
|
||||
# /*
|
||||
# * ******************************************************************************
|
||||
# * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
# *
|
||||
# * 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
# * *****************************************************************************
|
||||
# */
|
||||
# ******************************************************************************
|
||||
# Copyright (C) 2014-2022 Dennis Sheirer
|
||||
#
|
||||
# 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
# *****************************************************************************
|
||||
#
|
||||
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
305
gradlew
vendored
305
gradlew
vendored
@@ -1,78 +1,132 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# ******************************************************************************
|
||||
# Copyright (C) 2014-2022 Dennis Sheirer
|
||||
#
|
||||
# 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
# *****************************************************************************
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m"'
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -81,7 +135,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
@@ -89,84 +143,105 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
56
gradlew.bat
vendored
56
gradlew.bat
vendored
@@ -1,4 +1,20 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -9,19 +25,23 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m"
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -35,7 +55,7 @@ goto fail
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
@@ -45,38 +65,26 @@ echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
131
sdr-trunk.ipr
131
sdr-trunk.ipr
@@ -34,6 +34,8 @@
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="delegatedBuild" value="false" />
|
||||
<option name="testRunner" value="PLATFORM" />
|
||||
<option name="distributionType" value="WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleHome" value="$USER_HOME$/gradle/gradle-2.12" />
|
||||
@@ -199,9 +201,36 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/sdr-trunk_test.iml" filepath="$PROJECT_DIR$/sdr-trunk_test.iml" group="sdr-trunk" />
|
||||
</modules>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="13" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="MavenRepo" />
|
||||
<option name="name" value="MavenRepo" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="BintrayJCenter" />
|
||||
<option name="name" value="BintrayJCenter" />
|
||||
<option name="url" value="https://jcenter.bintray.com/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="maven" />
|
||||
<option name="name" value="maven" />
|
||||
<option name="url" value="https://jitpack.io" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
<component name="SvnBranchConfigurationManager">
|
||||
<option name="mySupportsUserInfoFilter" value="true" />
|
||||
</component>
|
||||
@@ -309,6 +338,22 @@
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fazecast/jSerialComm/2.5.0/b9e6222384a17cb10474b7f25768edf4b742ec16/jSerialComm-2.5.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.github.dnault:libresample4j:master">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.dnault/libresample4j/master/188fc23592420c3848916d34e1df327d45e5ccd3/libresample4j-master.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.dnault/libresample4j/master/169939a76de61aee15dc6a1b230569e82d115bb7/libresample4j-master-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.github.DSheirer.jmbe:jmbe-api:1.0.0">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.DSheirer.jmbe/jmbe-api/1.0.0/b914e8f914b7b3d535d1e7bbbbb09ddd37bef726/jmbe-api-1.0.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
<library name="Gradle: com.github.jiconfont:jiconfont-font_awesome:4.7.0.1">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-font_awesome/4.7.0.1/121d019023f631af04a1357e637dd05a2cc91f7b/jiconfont-font_awesome-4.7.0.1.jar!/" />
|
||||
@@ -353,6 +398,13 @@
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont/1.0.0/e79316fba9458423ce944fba6da101262549464/jiconfont-1.0.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.github.nwaldispuehl:java-lame:v3.98.4">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.nwaldispuehl/java-lame/v3.98.4/b4917736b83335704dc5af44671ea27a339cc496/java-lame-v3.98.4.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
<library name="Gradle: com.github.wendykierp:JTransforms:3.1">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.wendykierp/JTransforms/3.1/f1f6d3c9c9bc04f848e38bdb77dea01e8764a01f/JTransforms-3.1.jar!/" />
|
||||
@@ -417,13 +469,13 @@
|
||||
</library>
|
||||
<library name="Gradle: com.google.j2objc:j2objc-annotations:1.1">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/1.1/976d8d30bebc251db406f2bdb3eb01962b5685b3/j2objc-annotations-1.1.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/1.1/ed28ded51a8b1c6b112568def5f4b455e6809019/j2objc-annotations-1.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/1.1/2e1e0c8db5be95ec85d19d456186f8989ea263fa/j2objc-annotations-1.1-javadoc.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/1.1/5d415cbe46a293283658196abdb9e6d06dc68ccf/j2objc-annotations-1.1-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/1.1/dcd31de68a90e41e336f6b7afce0d39285c433d7/j2objc-annotations-1.1-sources.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/1.1/1efdf5b737b02f9b72ebdec4f72c37ec411302ff/j2objc-annotations-1.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.jidesoft:jide-oss:3.6.18">
|
||||
@@ -481,15 +533,15 @@
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/eu.hansolo/charts/1.0.5/aa26011a2c525ddf86cb92ed23558b18c0174fa4/charts-1.0.5-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: io.github.dsheirer:radio-reference-api:15.0.1">
|
||||
<library name="Gradle: io.github.dsheirer:radio-reference-api:15.1.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/io.github.dsheirer/radio-reference-api/15.0.1/8af16506c26025517f0bc971ba35485e96130c9a/radio-reference-api-15.0.1.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/io.github.dsheirer/radio-reference-api/15.1.2/5af53224d83af52ba68bd690c8101e5fe2a492ff/radio-reference-api-15.1.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/io.github.dsheirer/radio-reference-api/15.0.1/219528429b3ed73f285a8c2c4ded40d8eae61fc0/radio-reference-api-15.0.1-javadoc.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/io.github.dsheirer/radio-reference-api/15.1.2/75cfe3736f421290f6796d943c1a4b05b32d8526/radio-reference-api-15.1.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/io.github.dsheirer/radio-reference-api/15.0.1/b699a9821030b54fbb7f472f97fcc634afb114fb/radio-reference-api-15.0.1-sources.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/io.github.dsheirer/radio-reference-api/15.1.2/250df667ec2cea0219a9951d3b2144d87d0cf755/radio-reference-api-15.1.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: javax.usb:usb-api:1.0.2">
|
||||
@@ -536,15 +588,15 @@
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.8.1/e9748d7783594da3735ad9724390a10e7cebc01d/commons-lang3-3.8.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.apache.commons:commons-math3:3.5">
|
||||
<library name="Gradle: org.apache.commons:commons-math3:3.6.1">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-math3/3.5/13af68e9a11576953f5c4b09436f8207be9d3a27/commons-math3-3.5.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-math3/3.6.1/e4ba98f1d4b3c80ec46392f25e094a6a2e58fcbf/commons-math3-3.6.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-math3/3.5/e6106a9927c5ee66d0e647ad116af26c9b6b5f06/commons-math3-3.5-javadoc.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-math3/3.6.1/2e894b7acae199b5dff08bb596334594b5f23c28/commons-math3-3.6.1-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-math3/3.5/1d3f412d28c8bff0e8f048240b08bcd21cfa432e/commons-math3-3.5-sources.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-math3/3.6.1/8fab23986ea8886af34818daf32a718e81dc98ba/commons-math3-3.6.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.apache.mina:mina-core:2.0.19">
|
||||
@@ -613,6 +665,17 @@
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.codehaus.woodstox/stax2-api/3.1.4/b40604d61c01cc081a4a7bdfc562aa9847ee8857/stax2-api-3.1.4-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.controlsfx:controlsfx:11.0.1">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.controlsfx/controlsfx/11.0.1/66df6f3526734d37226ea36cc32940083e6fb697/controlsfx-11.0.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.controlsfx/controlsfx/11.0.1/79c5c32f926c8315a344a04eafa8dd250b545927/controlsfx-11.0.1-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.controlsfx/controlsfx/11.0.1/5d8b9a51a5b463f583e114e95fdecc0071564a0b/controlsfx-11.0.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.hamcrest:hamcrest-core:1.3">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/42a25dc3219429f0e5d060061f71acb49bf010a0/hamcrest-core-1.3.jar!/" />
|
||||
@@ -668,6 +731,17 @@
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/c082cdf645f9833408dcd960bf68344c9384f569/javafx-base-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.openjfx:javafx-controls:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/4ab633cf1eea60f76e2ae9905aedac862da88b08/javafx-controls-11.0.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/baa088249ee757119688e78d9c3a256928189902/javafx-controls-11.0.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/1535a8b57f01a712405a739a0d69e09ab99dbb6b/javafx-controls-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.openjfx:javafx-controls:linux:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/5f6929050a744aab39ebfc1e8e5dd03bcd2ad47b/javafx-controls-11.0.2-linux.jar!/" />
|
||||
@@ -745,6 +819,28 @@
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/15eadef01f327a851a6d3af0ac89d56292076b13/javafx-graphics-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.openjfx:javafx-media:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-media/11.0.2/30b3246c67f308de4d7aefbf99acef8d1dce32ad/javafx-media-11.0.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-media/11.0.2/20c892c5aa52ce3d87e3c37c9f60be46e328e342/javafx-media-11.0.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-media/11.0.2/b189a6ab9f7abdd2ede840e5a90b7002c521f370/javafx-media-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.openjfx:javafx-media:linux:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-media/11.0.2/8bc5daaa19f2ff103cd85f751ad021eb71ba7db7/javafx-media-11.0.2-linux.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-media/11.0.2/20c892c5aa52ce3d87e3c37c9f60be46e328e342/javafx-media-11.0.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-media/11.0.2/b189a6ab9f7abdd2ede840e5a90b7002c521f370/javafx-media-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.openjfx:javafx-swing:linux:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/cb57db3e5bd1e2b62b17c8a504858bfc26fa54b1/javafx-swing-11.0.2-linux.jar!/" />
|
||||
@@ -778,6 +874,17 @@
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/bcc8c0a402ff01a89a50888c9dc26824a9f3f2cc/javafx-swing-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.openjfx:javafx-web:linux:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-web/11.0.2/49ca03e2fd58cbb0ad94f6a6bd11e0c043e541a7/javafx-web-11.0.2-linux.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-web/11.0.2/851a1c3ecb65f69d69e25cb63958b9a607f665f2/javafx-web-11.0.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-web/11.0.2/b33e1bfa75ebf5a6816525a4c77d2a8aea0915c4/javafx-web-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.slf4j:slf4j-api:1.7.25">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.25/da76ca59f6a57ee3102f8f9bd9cee742973efa8a/slf4j-api-1.7.25.jar!/" />
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias;
|
||||
|
||||
@@ -28,161 +30,465 @@ import io.github.dsheirer.alias.id.AliasIDType;
|
||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||
import io.github.dsheirer.alias.id.priority.Priority;
|
||||
import io.github.dsheirer.alias.id.record.Record;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import javafx.util.Callback;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Alias provides an aliasing (e.g. name, color, etc) container that is linked to multiple alias identifiers and
|
||||
* provides a corresponding set of actions related to the alias.
|
||||
*/
|
||||
@JacksonXmlRootElement(localName = "alias")
|
||||
public class Alias
|
||||
{
|
||||
private String mList;
|
||||
private String mGroup;
|
||||
private String mName;
|
||||
private int mColor;
|
||||
private String mIconName;
|
||||
private List<AliasID> mAliasIDs = new ArrayList<AliasID>();
|
||||
private List<AliasAction> mAliasActions = new ArrayList<AliasAction>();
|
||||
private final static Logger mLog = LoggerFactory.getLogger(Alias.class);
|
||||
|
||||
public Alias()
|
||||
{
|
||||
}
|
||||
private BooleanProperty mRecordable = new SimpleBooleanProperty();
|
||||
private BooleanProperty mStreamable = new SimpleBooleanProperty();
|
||||
private BooleanProperty mOverlap = new SimpleBooleanProperty();
|
||||
private IntegerProperty mColor = new SimpleIntegerProperty();
|
||||
private IntegerProperty mPriority = new SimpleIntegerProperty(Priority.DEFAULT_PRIORITY);
|
||||
private IntegerProperty mNonAudioIdentifierCount = new SimpleIntegerProperty();
|
||||
private StringProperty mAliasListName = new SimpleStringProperty();
|
||||
private StringProperty mGroup = new SimpleStringProperty();
|
||||
private StringProperty mIconName = new SimpleStringProperty();
|
||||
private StringProperty mName = new SimpleStringProperty();
|
||||
private ObservableList<AliasID> mAliasIDs = FXCollections.observableArrayList();
|
||||
private ObservableList<AliasAction> mAliasActions = FXCollections.observableArrayList();
|
||||
|
||||
/**
|
||||
* Constructs an instance and sets the specified name.
|
||||
* @param name for the alias
|
||||
*/
|
||||
public Alias(String name)
|
||||
{
|
||||
mName = name;
|
||||
mName.set(name);
|
||||
|
||||
//Bind the non-audio identifier count property to the size of a filtered list of alias identifiers
|
||||
mNonAudioIdentifierCount.bind(Bindings.size(new FilteredList<>(mAliasIDs, aliasID -> {
|
||||
return aliasID != null && !aliasID.isAudioIdentifier();
|
||||
})));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public Alias()
|
||||
{
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of non-audio identifiers for this alias
|
||||
*/
|
||||
@JsonIgnore
|
||||
public IntegerProperty nonAudioIdentifierCountProperty()
|
||||
{
|
||||
return mNonAudioIdentifierCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio playback priority
|
||||
*/
|
||||
@JsonIgnore
|
||||
public IntegerProperty priorityProperty()
|
||||
{
|
||||
return mPriority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if one or more of the identifiers for this alias overlap with identifiers from another alias
|
||||
* within the same alias list.
|
||||
*/
|
||||
@JsonIgnore
|
||||
public BooleanProperty overlapProperty()
|
||||
{
|
||||
return mOverlap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recordable property
|
||||
*/
|
||||
@JsonIgnore
|
||||
public BooleanProperty recordableProperty()
|
||||
{
|
||||
return mRecordable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streamable property
|
||||
*/
|
||||
@JsonIgnore
|
||||
public BooleanProperty streamableProperty()
|
||||
{
|
||||
return mStreamable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias list name property
|
||||
*/
|
||||
@JsonIgnore
|
||||
public StringProperty aliasListNameProperty()
|
||||
{
|
||||
return mAliasListName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group property
|
||||
*/
|
||||
@JsonIgnore
|
||||
public StringProperty groupProperty()
|
||||
{
|
||||
return mGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias name property
|
||||
* @return
|
||||
*/
|
||||
@JsonIgnore
|
||||
public StringProperty nameProperty()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias color value property
|
||||
*/
|
||||
@JsonIgnore
|
||||
public IntegerProperty colorProperty()
|
||||
{
|
||||
return mColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon name property
|
||||
*/
|
||||
@JsonIgnore
|
||||
public StringProperty iconNameProperty()
|
||||
{
|
||||
return mIconName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias identifiers list property
|
||||
*/
|
||||
@JsonIgnore
|
||||
public ObservableList<AliasID> aliasIds()
|
||||
{
|
||||
return mAliasIDs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias actions list property
|
||||
*/
|
||||
@JsonIgnore
|
||||
public ObservableList<AliasAction> aliasActions()
|
||||
{
|
||||
return mAliasActions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses name value for string representation
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
return getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias name
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "name")
|
||||
public String getName()
|
||||
{
|
||||
return mName;
|
||||
return mName.get();
|
||||
}
|
||||
|
||||
public void setName(String name)
|
||||
{
|
||||
mName = name;
|
||||
mName.set(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias list name for the alias list that this alias belongs to
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "list")
|
||||
public String getList()
|
||||
public String getAliasListName()
|
||||
{
|
||||
return mList;
|
||||
return mAliasListName.get();
|
||||
}
|
||||
|
||||
public void setList(String list)
|
||||
public void setAliasListName(String aliasListName)
|
||||
{
|
||||
mList = list;
|
||||
mAliasListName.set(aliasListName);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean matchesAliasList(String aliasList)
|
||||
{
|
||||
boolean isEmptyArgument = aliasList == null || aliasList.isEmpty() || aliasList.contentEquals(AliasModel.NO_ALIAS_LIST);
|
||||
boolean isEmptyThis = getAliasListName() == null || getAliasListName().isEmpty();
|
||||
|
||||
if(isEmptyArgument && isEmptyThis)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if(!isEmptyArgument && !isEmptyThis && aliasList.contentEquals(getAliasListName()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this alias has a specified alias list name
|
||||
*/
|
||||
public boolean hasList()
|
||||
{
|
||||
return mList != null;
|
||||
return mAliasListName.get() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouping tag for this alias.
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "group")
|
||||
public String getGroup()
|
||||
{
|
||||
return mGroup;
|
||||
return mGroup.get();
|
||||
}
|
||||
|
||||
public void setGroup(String group)
|
||||
{
|
||||
mGroup = group;
|
||||
mGroup.set(group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this alias has a grouping tag
|
||||
*/
|
||||
public boolean hasGroup()
|
||||
{
|
||||
return mGroup != null;
|
||||
return mGroup.get() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Color (RGBA) value to use for this alias
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "color")
|
||||
public int getColor()
|
||||
{
|
||||
return mColor;
|
||||
return mColor.get();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getColorHex()
|
||||
{
|
||||
return String.format("#%06X", (0xFFFFFF & mColor.get()));
|
||||
}
|
||||
|
||||
public void setColor(int color)
|
||||
{
|
||||
mColor = color;
|
||||
mColor.set(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display color for this alias.
|
||||
*/
|
||||
@JsonIgnore
|
||||
public Color getDisplayColor()
|
||||
{
|
||||
return new Color(getColor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon name of the icon to use for this alias.
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "iconName")
|
||||
public String getIconName()
|
||||
{
|
||||
return mIconName;
|
||||
return mIconName.get();
|
||||
}
|
||||
|
||||
public void setIconName(String iconName)
|
||||
{
|
||||
mIconName = iconName;
|
||||
mIconName.set(iconName);
|
||||
}
|
||||
|
||||
/**
|
||||
* List of alias identifiers for this alias.
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = false, localName = "id")
|
||||
public List<AliasID> getId()
|
||||
public List<AliasID> getAliasIdentifiers()
|
||||
{
|
||||
return mAliasIDs;
|
||||
}
|
||||
|
||||
public void setId(ArrayList<AliasID> id)
|
||||
public void setAliasIdentifiers(List<AliasID> id)
|
||||
{
|
||||
mAliasIDs = id;
|
||||
mAliasIDs.clear();
|
||||
|
||||
for(AliasID aliasID : id)
|
||||
{
|
||||
addAliasID(aliasID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an alias identifier to this alias and updates the recordable and streamable attributes.
|
||||
*/
|
||||
public void addAliasID(AliasID id)
|
||||
{
|
||||
mAliasIDs.add(id);
|
||||
validate();
|
||||
updateOverlapBinding();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an alias identifier from this alias and updates the recordable and streamable attributes.
|
||||
*/
|
||||
public void removeAliasID(AliasID id)
|
||||
{
|
||||
mAliasIDs.remove(id);
|
||||
if(id != null)
|
||||
{
|
||||
mAliasIDs.remove(id);
|
||||
validate();
|
||||
updateOverlapBinding();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List of alias actions associated with this alias.
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = false, localName = "action")
|
||||
public List<AliasAction> getAction()
|
||||
public List<AliasAction> getAliasActions()
|
||||
{
|
||||
return mAliasActions;
|
||||
}
|
||||
|
||||
public void setAction(List<AliasAction> actions)
|
||||
public void setAliasActions(List<AliasAction> actions)
|
||||
{
|
||||
mAliasActions = actions;
|
||||
mAliasActions.setAll(actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified alias action to this alias
|
||||
*/
|
||||
public void addAliasAction(AliasAction action)
|
||||
{
|
||||
mAliasActions.add(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified alias action from this alias
|
||||
*/
|
||||
public void removeAliasAction(AliasAction action)
|
||||
{
|
||||
mAliasActions.remove(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this alias has any associated
|
||||
* @return
|
||||
*/
|
||||
public boolean hasActions()
|
||||
{
|
||||
return !mAliasActions.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the playback priority this alias
|
||||
*/
|
||||
private void updatePriority()
|
||||
{
|
||||
for(AliasID aliasID: mAliasIDs)
|
||||
{
|
||||
if(aliasID instanceof Priority)
|
||||
{
|
||||
mPriority.set(((Priority)aliasID).getPriority());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mPriority.set(Priority.DEFAULT_PRIORITY);
|
||||
}
|
||||
|
||||
private void updateOverlapBinding()
|
||||
{
|
||||
mOverlap.unbind();
|
||||
|
||||
BooleanBinding binding = new SimpleBooleanProperty(true).not();
|
||||
|
||||
for(AliasID aliasID: mAliasIDs)
|
||||
{
|
||||
binding = binding.or(aliasID.overlapProperty());
|
||||
}
|
||||
|
||||
mOverlap.bind(binding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the recordable status for this alias
|
||||
*/
|
||||
private void updateRecordable()
|
||||
{
|
||||
for(AliasID aliasID: mAliasIDs)
|
||||
{
|
||||
if(aliasID instanceof Record)
|
||||
{
|
||||
mRecordable.set(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mRecordable.set(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the streamable status for this alias
|
||||
*/
|
||||
private void updateStreamable()
|
||||
{
|
||||
for(AliasID aliasID: mAliasIDs)
|
||||
{
|
||||
if(aliasID instanceof BroadcastChannel)
|
||||
{
|
||||
mStreamable.set(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mStreamable.set(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform any validation/cleanup actions on this alias.
|
||||
*/
|
||||
public void validate()
|
||||
{
|
||||
updatePriority();
|
||||
updateRecordable();
|
||||
updateStreamable();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,22 +526,23 @@ public class Alias
|
||||
*/
|
||||
public void setCallPriority(int priority)
|
||||
{
|
||||
if(priority == Priority.DO_NOT_MONITOR ||
|
||||
(Priority.MIN_PRIORITY <= priority && priority <= Priority.MAX_PRIORITY))
|
||||
{
|
||||
for(AliasID id : mAliasIDs)
|
||||
{
|
||||
if(id.getType() == AliasIDType.PRIORITY)
|
||||
{
|
||||
((Priority)id).setPriority(priority);
|
||||
return;
|
||||
}
|
||||
}
|
||||
AliasID priorityID = null;
|
||||
|
||||
//If we don't find a priority id, create one
|
||||
Priority p = new Priority();
|
||||
p.setPriority(priority);
|
||||
addAliasID(p);
|
||||
for(AliasID aliasID: mAliasIDs)
|
||||
{
|
||||
if(aliasID instanceof Priority)
|
||||
{
|
||||
priorityID = aliasID;
|
||||
}
|
||||
}
|
||||
|
||||
removeAliasID(priorityID);
|
||||
|
||||
//Don't add a priority alias id if the priority is MAX_PRIORITY or DEFAULT_PRIORITY since the getCallPriority()
|
||||
//will return that as the default value
|
||||
if(priority == Priority.DO_NOT_MONITOR || (Priority.MIN_PRIORITY <= priority && priority < Priority.MAX_PRIORITY))
|
||||
{
|
||||
addAliasID(new Priority(priority));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,15 +552,7 @@ public class Alias
|
||||
@JsonIgnore
|
||||
public boolean isRecordable()
|
||||
{
|
||||
for(AliasID id : getId())
|
||||
{
|
||||
if(id.getType() == AliasIDType.RECORD)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return mRecordable.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,36 +560,22 @@ public class Alias
|
||||
*/
|
||||
public void setRecordable(boolean recordable)
|
||||
{
|
||||
AliasID recordID = null;
|
||||
|
||||
for(AliasID aliasID: mAliasIDs)
|
||||
{
|
||||
if(aliasID instanceof Record)
|
||||
{
|
||||
recordID = aliasID;
|
||||
}
|
||||
}
|
||||
|
||||
removeAliasID(recordID);
|
||||
|
||||
if(recordable)
|
||||
{
|
||||
for(AliasID id : getId())
|
||||
{
|
||||
if(id.getType() == AliasIDType.RECORD)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
addAliasID(new Record());
|
||||
}
|
||||
else
|
||||
{
|
||||
AliasID toRemove = null;
|
||||
|
||||
for(AliasID id : getId())
|
||||
{
|
||||
if(id.getType() == AliasIDType.RECORD)
|
||||
{
|
||||
toRemove = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(toRemove != null)
|
||||
{
|
||||
removeAliasID(toRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -299,15 +584,7 @@ public class Alias
|
||||
@JsonIgnore
|
||||
public boolean isStreamable()
|
||||
{
|
||||
for(AliasID id : getId())
|
||||
{
|
||||
if(id.getType() == AliasIDType.BROADCAST_CHANNEL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return mStreamable.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -318,7 +595,7 @@ public class Alias
|
||||
{
|
||||
Set<BroadcastChannel> broadcastChannels = new TreeSet<>();
|
||||
|
||||
for(AliasID id : getId())
|
||||
for(AliasID id : getAliasIdentifiers())
|
||||
{
|
||||
if(id.getType() == AliasIDType.BROADCAST_CHANNEL)
|
||||
{
|
||||
@@ -339,15 +616,100 @@ public class Alias
|
||||
return false;
|
||||
}
|
||||
|
||||
for(AliasID id : getId())
|
||||
for(AliasID id : getAliasIdentifiers())
|
||||
{
|
||||
if(id.getType() == AliasIDType.BROADCAST_CHANNEL &&
|
||||
((BroadcastChannel)id).getChannelName().contentEquals(channel))
|
||||
if(id instanceof BroadcastChannel)
|
||||
{
|
||||
return true;
|
||||
BroadcastChannel broadcastChannel = (BroadcastChannel)id;
|
||||
|
||||
if(broadcastChannel.getChannelName() != null && broadcastChannel.getChannelName().contentEquals(channel))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any broadcast channel(s) that match the argument
|
||||
*/
|
||||
public void removeBroadcastChannel(String channel)
|
||||
{
|
||||
if(channel == null || channel.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<AliasID> toRemove = new ArrayList<>();
|
||||
|
||||
for(AliasID aliasID: getAliasIdentifiers())
|
||||
{
|
||||
if(aliasID instanceof BroadcastChannel &&
|
||||
((BroadcastChannel)aliasID).getChannelName().contentEquals(channel))
|
||||
{
|
||||
toRemove.add(aliasID);
|
||||
}
|
||||
}
|
||||
|
||||
for(AliasID aliasID: toRemove)
|
||||
{
|
||||
removeAliasID(aliasID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes (clears) all broadcast channels from this alias
|
||||
*/
|
||||
public void removeAllBroadcastChannels()
|
||||
{
|
||||
List<AliasID> toRemove = new ArrayList<>();
|
||||
for(AliasID aliasID: mAliasIDs)
|
||||
{
|
||||
if(aliasID instanceof BroadcastChannel)
|
||||
{
|
||||
toRemove.add(aliasID);
|
||||
}
|
||||
}
|
||||
|
||||
for(AliasID aliasID: toRemove)
|
||||
{
|
||||
removeAliasID(aliasID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all non audio identifiers to prepare for replacing them from an editor.
|
||||
*/
|
||||
public void removeNonAudioIdentifiers()
|
||||
{
|
||||
Iterator<AliasID> it = mAliasIDs.iterator();
|
||||
|
||||
while(it.hasNext())
|
||||
{
|
||||
if(!it.next().isAudioIdentifier())
|
||||
{
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all alias actions from this alias
|
||||
*/
|
||||
public void removeAllActions()
|
||||
{
|
||||
mAliasActions.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an observable property extractor for use with observable lists to detect changes internal to this object.
|
||||
*/
|
||||
public static Callback<Alias,Observable[]> extractor()
|
||||
{
|
||||
return (Alias a) -> new Observable[] {a.recordableProperty(), a.streamableProperty(), a.colorProperty(),
|
||||
a.aliasListNameProperty(), a.groupProperty(), a.iconNameProperty(), a.nameProperty(), a.aliasIds(),
|
||||
a.aliasActions(), a.nonAudioIdentifierCountProperty(), a.overlapProperty()};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,381 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.alias;
|
||||
|
||||
import com.jidesoft.swing.JideSplitPane;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastModel;
|
||||
import io.github.dsheirer.gui.editor.Editor;
|
||||
import io.github.dsheirer.icon.IconManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||
import net.coderazzi.filters.gui.AutoChoices;
|
||||
import net.coderazzi.filters.gui.TableFilterHeader;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AliasController extends JPanel implements ActionListener, ListSelectionListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String TABLE_PREFERENCE_KEY = "alias.controller";
|
||||
private AliasModel mAliasModel;
|
||||
private JTable mAliasTable;
|
||||
private TableFilterHeader mTableFilterHeader;
|
||||
private AliasEditor mAliasEditor;
|
||||
private MultipleAliasEditor mMultipleAliasEditor;
|
||||
private JideSplitPane mSplitPane;
|
||||
private UserPreferences mUserPreferences;
|
||||
|
||||
private static final String NEW_ALIAS = "New";
|
||||
private static final String COPY_ALIAS = "Copy";
|
||||
private static final String DELETE_ALIAS = "Delete";
|
||||
|
||||
private JButton mNewButton = new JButton(NEW_ALIAS);
|
||||
private JButton mCopyButton = new JButton(COPY_ALIAS);
|
||||
private JButton mDeleteButton = new JButton(DELETE_ALIAS);
|
||||
|
||||
private IconCellRenderer mIconCellRenderer;
|
||||
private JTableColumnWidthMonitor mColumnWidthMonitor;
|
||||
|
||||
public AliasController(AliasModel aliasModel, BroadcastModel broadcastModel, IconManager iconManager,
|
||||
UserPreferences userPreferences)
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
mAliasEditor = new AliasEditor(mAliasModel, broadcastModel, iconManager);
|
||||
mMultipleAliasEditor = new MultipleAliasEditor(mAliasModel, broadcastModel, iconManager);
|
||||
mIconCellRenderer = new IconCellRenderer(iconManager);
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
init();
|
||||
|
||||
mAliasModel.addListener(mAliasEditor);
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
|
||||
//System Configuration View and Editor
|
||||
mAliasTable = new JTable(mAliasModel);
|
||||
mAliasTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||
mAliasTable.getSelectionModel().addListSelectionListener(this);
|
||||
mAliasTable.setAutoCreateRowSorter(true);
|
||||
|
||||
mAliasTable.getColumnModel().getColumn(AliasModel.COLUMN_COLOR).setCellRenderer(new ColorCellRenderer());
|
||||
|
||||
mAliasTable.getColumnModel().getColumn(AliasModel.COLUMN_ICON).setCellRenderer(mIconCellRenderer);
|
||||
|
||||
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mAliasTable, TABLE_PREFERENCE_KEY);
|
||||
|
||||
mTableFilterHeader = new TableFilterHeader(mAliasTable, AutoChoices.ENABLED);
|
||||
mTableFilterHeader.setFilterOnUpdates(true);
|
||||
|
||||
JScrollPane tableScroller = new JScrollPane(mAliasTable);
|
||||
|
||||
JPanel buttonsPanel = new JPanel();
|
||||
|
||||
buttonsPanel.setLayout(new MigLayout("insets 0 0 0 0",
|
||||
"[grow,fill][grow,fill][grow,fill]", "[]"));
|
||||
|
||||
mNewButton.addActionListener(this);
|
||||
mNewButton.setToolTipText("Adds a new alias");
|
||||
buttonsPanel.add(mNewButton);
|
||||
|
||||
mCopyButton.addActionListener(this);
|
||||
mCopyButton.setEnabled(false);
|
||||
mCopyButton.setToolTipText("Creates a copy of the currently selected alias and adds it");
|
||||
buttonsPanel.add(mCopyButton);
|
||||
|
||||
mDeleteButton.addActionListener(this);
|
||||
mDeleteButton.setEnabled(false);
|
||||
mDeleteButton.setToolTipText("Deletes the currently selected alias");
|
||||
buttonsPanel.add(mDeleteButton);
|
||||
|
||||
JPanel listAndButtonsPanel = new JPanel();
|
||||
|
||||
listAndButtonsPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]",
|
||||
"[grow,fill][]"));
|
||||
|
||||
listAndButtonsPanel.add(tableScroller, "wrap");
|
||||
listAndButtonsPanel.add(buttonsPanel);
|
||||
|
||||
mSplitPane = new JideSplitPane(JideSplitPane.HORIZONTAL_SPLIT);
|
||||
mSplitPane.setDividerSize(5);
|
||||
mSplitPane.setShowGripper(true);
|
||||
mSplitPane.add(listAndButtonsPanel);
|
||||
mSplitPane.add(mAliasEditor);
|
||||
|
||||
add(mSplitPane);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the editor argument as the currently visible alias editor
|
||||
*/
|
||||
private void setEditor(Editor<?> editor)
|
||||
{
|
||||
if(mSplitPane.getComponentCount() == 3)
|
||||
{
|
||||
Component component = mSplitPane.getComponent(2);
|
||||
|
||||
if(component instanceof Editor<?> && component != editor)
|
||||
{
|
||||
//Get current divider location so that we can reapply it after
|
||||
//changing out the editors, so that the interface doesn't move
|
||||
int location = mSplitPane.getDividerLocation(0);
|
||||
|
||||
((Editor<?>) component).setItem(null);
|
||||
|
||||
mSplitPane.remove(component);
|
||||
editor.setPreferredSize(new Dimension(100, 100));
|
||||
mSplitPane.add(editor);
|
||||
|
||||
mSplitPane.validate();
|
||||
mSplitPane.setDividerLocation(0, location);
|
||||
mSplitPane.repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Alias getAlias(int selectedRow)
|
||||
{
|
||||
if(selectedRow >= 0)
|
||||
{
|
||||
int index = mAliasTable.convertRowIndexToModel(selectedRow);
|
||||
|
||||
return mAliasModel.getAliasAtIndex(index);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent event)
|
||||
{
|
||||
//Limits event firing to only when selection is complete
|
||||
if(!event.getValueIsAdjusting())
|
||||
{
|
||||
int[] selectedRows = mAliasTable.getSelectedRows();
|
||||
|
||||
if(selectedRows.length == 0)
|
||||
{
|
||||
mAliasEditor.setItem(null);
|
||||
mCopyButton.setEnabled(false);
|
||||
mDeleteButton.setEnabled(false);
|
||||
}
|
||||
else if(selectedRows.length == 1)
|
||||
{
|
||||
setEditor(mAliasEditor);
|
||||
|
||||
Alias selectedAlias = getAlias(selectedRows[0]);
|
||||
|
||||
mAliasEditor.setItem(selectedAlias);
|
||||
|
||||
if(selectedAlias != null)
|
||||
{
|
||||
mCopyButton.setEnabled(true);
|
||||
mDeleteButton.setEnabled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
mCopyButton.setEnabled(false);
|
||||
mDeleteButton.setEnabled(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
setEditor(mMultipleAliasEditor);
|
||||
|
||||
mCopyButton.setEnabled(true);
|
||||
mDeleteButton.setEnabled(true);
|
||||
|
||||
List<Alias> selectedAliases = new ArrayList<Alias>();
|
||||
|
||||
for(int selectedRow : selectedRows)
|
||||
{
|
||||
Alias alias = getAlias(selectedRow);
|
||||
|
||||
selectedAliases.add(alias);
|
||||
}
|
||||
|
||||
mMultipleAliasEditor.setItem(selectedAliases);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the alias to the alias table/model and scrolls the view it
|
||||
*/
|
||||
private void addAlias(Alias alias)
|
||||
{
|
||||
//Remove any column headers so that the newly added alias shows correctly.
|
||||
mTableFilterHeader.resetFilter();
|
||||
|
||||
int index = mAliasModel.addAlias(alias);
|
||||
|
||||
if(index >= 0)
|
||||
{
|
||||
int translatedIndex = mAliasTable.convertRowIndexToView(index);
|
||||
mAliasTable.setRowSelectionInterval(translatedIndex, translatedIndex);
|
||||
mAliasTable.scrollRectToVisible(
|
||||
new Rectangle(mAliasTable.getCellRect(translatedIndex, 0, true)));
|
||||
}
|
||||
}
|
||||
|
||||
private List<Alias> getSelectedAliases()
|
||||
{
|
||||
List<Alias> selected = new ArrayList<>();
|
||||
|
||||
int[] rows = mAliasTable.getSelectedRows();
|
||||
|
||||
for(int row : rows)
|
||||
{
|
||||
if(row >= 0)
|
||||
{
|
||||
Alias alias = mAliasModel.getAliasAtIndex(mAliasTable.convertRowIndexToModel(row));
|
||||
|
||||
if(alias != null)
|
||||
{
|
||||
selected.add(alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to New, Copy and Delete Channel button invocations
|
||||
*/
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event)
|
||||
{
|
||||
switch(event.getActionCommand())
|
||||
{
|
||||
case NEW_ALIAS:
|
||||
addAlias(new Alias("New Alias"));
|
||||
break;
|
||||
case COPY_ALIAS:
|
||||
for(Alias alias : getSelectedAliases())
|
||||
{
|
||||
addAlias(AliasFactory.copyOf(alias));
|
||||
}
|
||||
break;
|
||||
case DELETE_ALIAS:
|
||||
List<Alias> toDelete = getSelectedAliases();
|
||||
|
||||
if(toDelete != null && !toDelete.isEmpty())
|
||||
{
|
||||
String title = toDelete.size() == 1 ? "Delete Alias?" : "Delete Aliases?";
|
||||
String prompt = toDelete.size() == 1 ? "Do you want to delete this alias?" :
|
||||
"Do you want to delete these " + toDelete.size() + " aliases?";
|
||||
|
||||
int choice = JOptionPane.showConfirmDialog(AliasController.this,
|
||||
prompt, title, JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
|
||||
|
||||
if(choice == JOptionPane.YES_OPTION)
|
||||
{
|
||||
for(Alias alias : toDelete)
|
||||
{
|
||||
mAliasModel.removeAlias(alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Colorizes the cell based on the cell's integer value
|
||||
*/
|
||||
public class ColorCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table,
|
||||
Object value, boolean isSelected, boolean hasFocus, int row,
|
||||
int column)
|
||||
{
|
||||
JLabel label = (JLabel) super.getTableCellRendererComponent(table,
|
||||
value, isSelected, hasFocus, row, column);
|
||||
|
||||
label.setBackground(new Color((int) value));
|
||||
|
||||
label.setText("");
|
||||
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays cell's icon and name
|
||||
*/
|
||||
public class IconCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private IconManager mIconManager;
|
||||
|
||||
public IconCellRenderer(IconManager iconManager)
|
||||
{
|
||||
mIconManager = iconManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table,
|
||||
Object value, boolean isSelected, boolean hasFocus, int row,
|
||||
int column)
|
||||
{
|
||||
JLabel label = (JLabel) super.getTableCellRendererComponent(table,
|
||||
value, isSelected, hasFocus, row, column);
|
||||
|
||||
ImageIcon icon = mIconManager.getIcon(label.getText(), 12);
|
||||
|
||||
if(icon != null)
|
||||
{
|
||||
label.setIcon(icon);
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias;
|
||||
|
||||
import com.jidesoft.swing.JideTabbedPane;
|
||||
import io.github.dsheirer.alias.action.AliasActionEditor;
|
||||
import io.github.dsheirer.alias.id.AliasIdentifierEditor;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastModel;
|
||||
import io.github.dsheirer.gui.editor.Editor;
|
||||
import io.github.dsheirer.icon.IconManager;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
public class AliasEditor extends Editor<Alias> implements ActionListener, Listener<AliasEvent>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String DEFAULT_NAME = "select an alias";
|
||||
|
||||
private AliasModel mAliasModel;
|
||||
|
||||
private JLabel mAliasLabel;
|
||||
private AliasNameEditor mAliasNameEditor;
|
||||
private AliasIdentifierEditor mAliasIdentifierEditor;
|
||||
private AliasActionEditor mAliasActionEditor;
|
||||
|
||||
public AliasEditor(AliasModel aliasModel, BroadcastModel broadcastModel, IconManager iconManager )
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
mAliasNameEditor = new AliasNameEditor( mAliasModel, iconManager );
|
||||
mAliasIdentifierEditor = new AliasIdentifierEditor( aliasModel, broadcastModel );
|
||||
mAliasActionEditor = new AliasActionEditor( aliasModel );
|
||||
|
||||
mAliasModel.addListener( this );
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setItem( Alias alias )
|
||||
{
|
||||
super.setItem( alias );
|
||||
|
||||
if( hasItem() )
|
||||
{
|
||||
mAliasLabel.setText( getItem().getName() );
|
||||
}
|
||||
else
|
||||
{
|
||||
mAliasLabel.setText( DEFAULT_NAME );
|
||||
}
|
||||
|
||||
mAliasNameEditor.setItem( alias );
|
||||
mAliasIdentifierEditor.setItem( alias );
|
||||
mAliasActionEditor.setItem( alias );
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2", "[grow,fill][grow,fill]",
|
||||
"[][][][][][grow,fill]" ) );
|
||||
|
||||
add( new JLabel( "Alias:" ), "align right" );
|
||||
mAliasLabel = new JLabel( DEFAULT_NAME );
|
||||
add( mAliasLabel, "align left" );
|
||||
|
||||
add( new JSeparator( JSeparator.HORIZONTAL ), "span,growx,push" );
|
||||
|
||||
JideTabbedPane tabs = new JideTabbedPane();
|
||||
tabs.setFont( this.getFont() );
|
||||
tabs.setForeground( Color.BLACK );
|
||||
|
||||
tabs.addTab( "Alias", mAliasNameEditor );
|
||||
tabs.addTab( "Audio / Identifier", mAliasIdentifierEditor );
|
||||
tabs.addTab( "Action", mAliasActionEditor );
|
||||
add( tabs, "span,grow" );
|
||||
|
||||
//Playlist management buttons
|
||||
JButton btnSave = new JButton( "Save" );
|
||||
btnSave.setToolTipText( "Save alias changes to the playlist" );
|
||||
btnSave.addActionListener( AliasEditor.this );
|
||||
add( btnSave );
|
||||
|
||||
JButton btnReset = new JButton( "Reset" );
|
||||
btnReset.setToolTipText( "Reset alias changes since last save" );
|
||||
btnReset.addActionListener( AliasEditor.this );
|
||||
add( btnReset, "wrap" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( AliasEvent event )
|
||||
{
|
||||
//If this is the currently displayed alias, reload it
|
||||
if( hasItem() && getItem() == event.getAlias() )
|
||||
{
|
||||
switch( event.getEvent() )
|
||||
{
|
||||
case CHANGE:
|
||||
//Alias changed - reset the editor with changed alias
|
||||
setItem( event.getAlias() );
|
||||
break;
|
||||
case DELETE:
|
||||
setItem( null );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
if( hasItem() )
|
||||
{
|
||||
String command = e.getActionCommand();
|
||||
|
||||
if( command.contentEquals( "Save" ) )
|
||||
{
|
||||
save();
|
||||
}
|
||||
else if( command.contentEquals( "Reset" ) )
|
||||
{
|
||||
reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
mAliasNameEditor.save();
|
||||
mAliasIdentifierEditor.save();
|
||||
mAliasActionEditor.save();
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias;
|
||||
|
||||
public class AliasEvent
|
||||
{
|
||||
private Alias mAlias;
|
||||
private Event mEvent;
|
||||
|
||||
/**
|
||||
* AliasEvent - event describing any changes to an alias
|
||||
* @param alias - alias that changed
|
||||
* @param event - change event
|
||||
*/
|
||||
public AliasEvent( Alias alias, Event event )
|
||||
{
|
||||
mAlias = alias;
|
||||
mEvent = event;
|
||||
}
|
||||
|
||||
public Alias getAlias()
|
||||
{
|
||||
return mAlias;
|
||||
}
|
||||
|
||||
public Event getEvent()
|
||||
{
|
||||
return mEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel events to describe the specific event
|
||||
*/
|
||||
public enum Event
|
||||
{
|
||||
ADD,
|
||||
CHANGE,
|
||||
DELETE;
|
||||
}
|
||||
}
|
||||
@@ -1,78 +1,60 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias;
|
||||
|
||||
import io.github.dsheirer.alias.action.AliasAction;
|
||||
import io.github.dsheirer.alias.action.AliasActionType;
|
||||
import io.github.dsheirer.alias.action.beep.BeepAction;
|
||||
import io.github.dsheirer.alias.action.beep.BeepActionEditor;
|
||||
import io.github.dsheirer.alias.action.clip.ClipAction;
|
||||
import io.github.dsheirer.alias.action.clip.ClipActionEditor;
|
||||
import io.github.dsheirer.alias.action.script.ScriptAction;
|
||||
import io.github.dsheirer.alias.action.script.ScriptActionEditor;
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.alias.id.AliasIDType;
|
||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannelEditor;
|
||||
import io.github.dsheirer.alias.id.esn.ESNEditor;
|
||||
import io.github.dsheirer.alias.id.esn.Esn;
|
||||
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
|
||||
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncIDEditor;
|
||||
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
|
||||
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200IDEditor;
|
||||
import io.github.dsheirer.alias.id.legacy.mobileID.MINEditor;
|
||||
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
|
||||
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327ID;
|
||||
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327IDEditor;
|
||||
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordable;
|
||||
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordableEditor;
|
||||
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
|
||||
import io.github.dsheirer.alias.id.legacy.siteID.SiteIDEditor;
|
||||
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
|
||||
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupIDEditor;
|
||||
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
|
||||
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueIDEditor;
|
||||
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
||||
import io.github.dsheirer.alias.id.lojack.LoJackIDEditor;
|
||||
import io.github.dsheirer.alias.id.priority.Priority;
|
||||
import io.github.dsheirer.alias.id.priority.PriorityEditor;
|
||||
import io.github.dsheirer.alias.id.radio.Radio;
|
||||
import io.github.dsheirer.alias.id.radio.RadioEditor;
|
||||
import io.github.dsheirer.alias.id.radio.RadioRange;
|
||||
import io.github.dsheirer.alias.id.radio.RadioRangeEditor;
|
||||
import io.github.dsheirer.alias.id.record.Record;
|
||||
import io.github.dsheirer.alias.id.record.RecordEditor;
|
||||
import io.github.dsheirer.alias.id.status.StatusID;
|
||||
import io.github.dsheirer.alias.id.status.StatusIDEditor;
|
||||
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
||||
import io.github.dsheirer.alias.id.status.UserStatusID;
|
||||
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupEditor;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRangeEditor;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastModel;
|
||||
import io.github.dsheirer.gui.editor.Editor;
|
||||
import io.github.dsheirer.gui.editor.EmptyEditor;
|
||||
import io.github.dsheirer.alias.id.tone.TonesID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AliasFactory
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(AliasFactory.class);
|
||||
|
||||
public static AliasID copyOf(AliasID id)
|
||||
{
|
||||
switch(id.getType())
|
||||
@@ -87,11 +69,6 @@ public class AliasFactory
|
||||
Esn copyESN = new Esn();
|
||||
copyESN.setEsn(originalESN.getEsn());
|
||||
return copyESN;
|
||||
case LTR_NET_UID:
|
||||
UniqueID originalUniqueID = (UniqueID)id;
|
||||
UniqueID copyUniqueID = new UniqueID();
|
||||
copyUniqueID.setUid(originalUniqueID.getUid());
|
||||
return copyUniqueID;
|
||||
case LOJACK:
|
||||
LoJackFunctionAndID originalLoJackFunctionAndID = (LoJackFunctionAndID)id;
|
||||
LoJackFunctionAndID copyLoJackFunctionAndID = new LoJackFunctionAndID();
|
||||
@@ -111,11 +88,13 @@ public class AliasFactory
|
||||
case RADIO_ID:
|
||||
Radio originalRadio = (Radio)id;
|
||||
Radio copyRadio = new Radio(originalRadio.getProtocol(), originalRadio.getValue());
|
||||
copyRadio.setOverlap(originalRadio.overlapProperty().get());
|
||||
return copyRadio;
|
||||
case RADIO_ID_RANGE:
|
||||
RadioRange originalRadioRange = (RadioRange)id;
|
||||
RadioRange copyRadioRange = new RadioRange(originalRadioRange.getProtocol(), originalRadioRange.getMinRadio(),
|
||||
originalRadioRange.getMaxRadio());
|
||||
copyRadioRange.setOverlap(originalRadioRange.overlapProperty().get());
|
||||
return copyRadioRange;
|
||||
case RECORD:
|
||||
return new Record();
|
||||
@@ -125,27 +104,45 @@ public class AliasFactory
|
||||
copySiteID.setSite(originalSiteID.getSite());
|
||||
return copySiteID;
|
||||
case STATUS:
|
||||
StatusID originalStatusID = (StatusID)id;
|
||||
StatusID copyStatusID = new StatusID();
|
||||
copyStatusID.setStatus(originalStatusID.getStatus());
|
||||
return copyStatusID;
|
||||
UserStatusID originalUserStatusID = (UserStatusID)id;
|
||||
UserStatusID copyUserStatusID = new UserStatusID();
|
||||
copyUserStatusID.setStatus(originalUserStatusID.getStatus());
|
||||
copyUserStatusID.setOverlap(originalUserStatusID.overlapProperty().get());
|
||||
return copyUserStatusID;
|
||||
case TALKGROUP:
|
||||
Talkgroup originalTalkgroup = (Talkgroup)id;
|
||||
Talkgroup copyTalkgroup = new Talkgroup(originalTalkgroup.getProtocol(), originalTalkgroup.getValue());
|
||||
copyTalkgroup.setOverlap(originalTalkgroup.overlapProperty().get());
|
||||
return copyTalkgroup;
|
||||
case TALKGROUP_RANGE:
|
||||
TalkgroupRange originalRange = (TalkgroupRange)id;
|
||||
TalkgroupRange copyRange = new TalkgroupRange(originalRange.getProtocol(), originalRange.getMinTalkgroup(),
|
||||
originalRange.getMaxTalkgroup());
|
||||
copyRange.setOverlap(originalRange.overlapProperty().get());
|
||||
return copyRange;
|
||||
case TONES:
|
||||
TonesID originalTones = (TonesID)id;
|
||||
TonesID copyTones = new TonesID();
|
||||
copyTones.setToneSequence(originalTones.getToneSequence().copyOf());
|
||||
return copyTones;
|
||||
case UNIT_STATUS:
|
||||
UnitStatusID originalUnitStatus = (UnitStatusID)id;
|
||||
UnitStatusID copyUnitStatusID = new UnitStatusID();
|
||||
copyUnitStatusID.setStatus(originalUnitStatus.getStatus());
|
||||
copyUnitStatusID.setOverlap(originalUnitStatus.overlapProperty().get());
|
||||
return copyUnitStatusID;
|
||||
|
||||
//Legacy identifiers ... not supported
|
||||
case FLEETSYNC:
|
||||
case LEGACY_TALKGROUP:
|
||||
case LTR_NET_UID:
|
||||
case MDC1200:
|
||||
case MPT1327:
|
||||
case NON_RECORDABLE:
|
||||
return null;
|
||||
default:
|
||||
mLog.warn("Unrecognized Alias ID Type [" + id.getType() + "] cannot make copy of instance");
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -155,7 +152,11 @@ public class AliasFactory
|
||||
{
|
||||
if(action instanceof BeepAction)
|
||||
{
|
||||
return new BeepAction();
|
||||
BeepAction original = (BeepAction)action;
|
||||
BeepAction copyBeep = new BeepAction();
|
||||
copyBeep.setInterval(original.getInterval());
|
||||
copyBeep.setPeriod(original.getPeriod());
|
||||
return copyBeep;
|
||||
}
|
||||
else if(action instanceof ClipAction)
|
||||
{
|
||||
@@ -179,106 +180,59 @@ public class AliasFactory
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Alias copyOf(Alias original)
|
||||
public static Alias shallowCopyOf(Alias original)
|
||||
{
|
||||
Alias copy = new Alias(original.getName());
|
||||
copy.setList(original.getList());
|
||||
copy.setAliasListName(original.getAliasListName());
|
||||
copy.setGroup(original.getGroup());
|
||||
copy.setColor(original.getColor());
|
||||
copy.setIconName(original.getIconName());
|
||||
|
||||
for(AliasID id : original.getId())
|
||||
{
|
||||
AliasID copyID = copyOf(id);
|
||||
|
||||
if(copyID != null)
|
||||
{
|
||||
copy.addAliasID(copyID);
|
||||
}
|
||||
}
|
||||
|
||||
for(AliasAction action : original.getAction())
|
||||
{
|
||||
AliasAction copyAction = copyOf(action);
|
||||
|
||||
if(copyAction != null)
|
||||
{
|
||||
copy.addAliasAction(copyAction);
|
||||
}
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
public static Editor<AliasID> getEditor(AliasID aliasID, BroadcastModel broadcastModel)
|
||||
public static List<AliasID> copyAliasIDs(Alias original)
|
||||
{
|
||||
if(aliasID != null)
|
||||
{
|
||||
switch(aliasID.getType())
|
||||
{
|
||||
case BROADCAST_CHANNEL:
|
||||
return new BroadcastChannelEditor(aliasID, broadcastModel);
|
||||
case ESN:
|
||||
return new ESNEditor(aliasID);
|
||||
case LTR_NET_UID:
|
||||
return new UniqueIDEditor(aliasID);
|
||||
case LOJACK:
|
||||
return new LoJackIDEditor(aliasID);
|
||||
case MIN:
|
||||
return new MINEditor(aliasID);
|
||||
case PRIORITY:
|
||||
return new PriorityEditor(aliasID);
|
||||
case RADIO_ID:
|
||||
return new RadioEditor(aliasID);
|
||||
case RADIO_ID_RANGE:
|
||||
return new RadioRangeEditor(aliasID);
|
||||
case RECORD:
|
||||
return new RecordEditor(aliasID);
|
||||
case SITE:
|
||||
return new SiteIDEditor(aliasID);
|
||||
case STATUS:
|
||||
return new StatusIDEditor(aliasID);
|
||||
case TALKGROUP:
|
||||
return new TalkgroupEditor(aliasID);
|
||||
case TALKGROUP_RANGE:
|
||||
return new TalkgroupRangeEditor(aliasID);
|
||||
List<AliasID> aliasIDS = new ArrayList<>();
|
||||
|
||||
case FLEETSYNC:
|
||||
return new FleetsyncIDEditor(aliasID);
|
||||
case LEGACY_TALKGROUP:
|
||||
return new LegacyTalkgroupIDEditor(aliasID);
|
||||
case MDC1200:
|
||||
return new MDC1200IDEditor(aliasID);
|
||||
case MPT1327:
|
||||
return new MPT1327IDEditor(aliasID);
|
||||
case NON_RECORDABLE:
|
||||
return new NonRecordableEditor(aliasID);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
for(AliasID id : original.getAliasIdentifiers())
|
||||
{
|
||||
aliasIDS.add(copyOf(id));
|
||||
}
|
||||
|
||||
return new EmptyEditor<AliasID>();
|
||||
return aliasIDS;
|
||||
}
|
||||
|
||||
public static Editor<AliasAction> getEditor(AliasAction aliasAction)
|
||||
public static List<AliasAction> copyAliasActions(Alias original)
|
||||
{
|
||||
if(aliasAction != null)
|
||||
List<AliasAction> actions = new ArrayList<>();
|
||||
|
||||
for(AliasAction aliasAction: original.getAliasActions())
|
||||
{
|
||||
switch(aliasAction.getType())
|
||||
{
|
||||
case BEEP:
|
||||
return new BeepActionEditor(aliasAction);
|
||||
case CLIP:
|
||||
return new ClipActionEditor(aliasAction);
|
||||
case SCRIPT:
|
||||
return new ScriptActionEditor(aliasAction);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
actions.add(copyOf(aliasAction));
|
||||
}
|
||||
|
||||
return new EmptyEditor<AliasAction>();
|
||||
return actions;
|
||||
}
|
||||
|
||||
public static Alias copyOf(Alias original)
|
||||
{
|
||||
Alias copy = shallowCopyOf(original);
|
||||
|
||||
List<AliasID> aliasIDS = copyAliasIDs(original);
|
||||
|
||||
for(AliasID aliasID: aliasIDS)
|
||||
{
|
||||
copy.addAliasID(aliasID);
|
||||
}
|
||||
|
||||
List<AliasAction> actions = copyAliasActions(original);
|
||||
|
||||
for(AliasAction action: actions)
|
||||
{
|
||||
copy.addAliasAction(action);
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
public static AliasID getAliasID(AliasIDType type)
|
||||
@@ -318,7 +272,7 @@ public class AliasFactory
|
||||
case SITE:
|
||||
return new SiteID();
|
||||
case STATUS:
|
||||
return new StatusID();
|
||||
return new UserStatusID();
|
||||
case LEGACY_TALKGROUP:
|
||||
return new LegacyTalkgroupID();
|
||||
default:
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias;
|
||||
|
||||
@@ -28,9 +25,11 @@ import io.github.dsheirer.alias.id.esn.Esn;
|
||||
import io.github.dsheirer.alias.id.priority.Priority;
|
||||
import io.github.dsheirer.alias.id.radio.Radio;
|
||||
import io.github.dsheirer.alias.id.radio.RadioRange;
|
||||
import io.github.dsheirer.alias.id.status.StatusID;
|
||||
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
||||
import io.github.dsheirer.alias.id.status.UserStatusID;
|
||||
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||
import io.github.dsheirer.alias.id.tone.TonesID;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.esn.ESNIdentifier;
|
||||
@@ -40,32 +39,41 @@ import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.identifier.status.UnitStatusIdentifier;
|
||||
import io.github.dsheirer.identifier.status.UserStatusIdentifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
import io.github.dsheirer.identifier.tone.ToneIdentifier;
|
||||
import io.github.dsheirer.identifier.tone.ToneSequence;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* List of aliases that share the same alias list name and provides convenient methods for looking up alias
|
||||
* objects that match an identifier.
|
||||
*/
|
||||
public class AliasList implements Listener<AliasEvent>
|
||||
public class AliasList
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AliasList.class);
|
||||
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new HashMap<>();
|
||||
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new HashMap<>();
|
||||
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new EnumMap<>(Protocol.class);
|
||||
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new EnumMap<>(Protocol.class);
|
||||
private Map<String,Alias> mESNMap = new HashMap<>();
|
||||
private Map<Integer,Alias> mStatusMap = new HashMap<>();
|
||||
private Map<Integer,Alias> mUnitStatusMap = new HashMap<>();
|
||||
private Map<Integer,Alias> mUserStatusMap = new HashMap<>();
|
||||
private Map<ToneSequence,Alias> mToneSequenceMap = new HashMap<>();
|
||||
private boolean mHasAliasActions = false;
|
||||
private String mName;
|
||||
private ObservableList<Alias> mAliases = FXCollections.observableArrayList(Alias.extractor());
|
||||
|
||||
/**
|
||||
* List of aliases where all aliases share the same list name. Contains
|
||||
@@ -78,6 +86,14 @@ public class AliasList implements Listener<AliasEvent>
|
||||
mName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Observable list of aliases contained in this alias list
|
||||
*/
|
||||
public ObservableList<Alias> aliases()
|
||||
{
|
||||
return mAliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the alias to this list
|
||||
*/
|
||||
@@ -85,16 +101,18 @@ public class AliasList implements Listener<AliasEvent>
|
||||
{
|
||||
if(alias != null)
|
||||
{
|
||||
for(AliasID aliasID : alias.getId())
|
||||
{
|
||||
addAliasID(aliasID, alias);
|
||||
}
|
||||
alias.getAliasIdentifiers().stream().forEach(aliasID -> addAliasID(aliasID, alias));
|
||||
}
|
||||
|
||||
if(alias.hasActions())
|
||||
{
|
||||
mHasAliasActions = true;
|
||||
}
|
||||
|
||||
if(!mAliases.contains(alias))
|
||||
{
|
||||
mAliases.add(alias);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,7 +179,7 @@ public class AliasList implements Listener<AliasEvent>
|
||||
radioRangeAliasList.add(radioRange, alias);
|
||||
break;
|
||||
case ESN:
|
||||
String esn = ((Esn) id).getEsn();
|
||||
String esn = ((Esn)id).getEsn();
|
||||
|
||||
if(esn != null && !esn.isEmpty())
|
||||
{
|
||||
@@ -169,7 +187,67 @@ public class AliasList implements Listener<AliasEvent>
|
||||
}
|
||||
break;
|
||||
case STATUS:
|
||||
mStatusMap.put(((StatusID) id).getStatus(), alias);
|
||||
int userStatus = ((UserStatusID)id).getStatus();
|
||||
|
||||
if(mUserStatusMap.containsKey(userStatus) && !mUserStatusMap.get(userStatus).equals(alias))
|
||||
{
|
||||
id.setOverlap(true);
|
||||
|
||||
Alias existing = mUserStatusMap.get(userStatus);
|
||||
|
||||
for(AliasID aliasID: existing.getAliasIdentifiers())
|
||||
{
|
||||
if(aliasID instanceof UserStatusID && ((UserStatusID)aliasID).getStatus() == userStatus)
|
||||
{
|
||||
aliasID.setOverlap(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
mUserStatusMap.put(userStatus, alias);
|
||||
break;
|
||||
case UNIT_STATUS:
|
||||
int unitStatus = ((UnitStatusID)id).getStatus();
|
||||
|
||||
if(mUnitStatusMap.containsKey(unitStatus) && !mUnitStatusMap.get(unitStatus).equals(alias))
|
||||
{
|
||||
id.setOverlap(true);
|
||||
|
||||
Alias existing = mUnitStatusMap.get(unitStatus);
|
||||
|
||||
for(AliasID aliasID: existing.getAliasIdentifiers())
|
||||
{
|
||||
if(aliasID instanceof UnitStatusID && ((UnitStatusID)aliasID).getStatus() == unitStatus)
|
||||
{
|
||||
aliasID.setOverlap(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
mUnitStatusMap.put(unitStatus, alias);
|
||||
break;
|
||||
case TONES:
|
||||
ToneSequence toneSequence = ((TonesID)id).getToneSequence();
|
||||
|
||||
if(toneSequence != null)
|
||||
{
|
||||
if(mToneSequenceMap.containsKey(toneSequence) && !mToneSequenceMap.get(toneSequence).equals(alias))
|
||||
{
|
||||
id.setOverlap(true);
|
||||
|
||||
Alias existing = mToneSequenceMap.get(toneSequence);
|
||||
|
||||
for(AliasID aliasID: existing.getAliasIdentifiers())
|
||||
{
|
||||
if(aliasID instanceof TonesID && aliasID.equals(id))
|
||||
{
|
||||
aliasID.setOverlap(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mToneSequenceMap.put(toneSequence, alias);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -185,13 +263,45 @@ public class AliasList implements Listener<AliasEvent>
|
||||
*/
|
||||
public void removeAlias(Alias alias)
|
||||
{
|
||||
for(TalkgroupAliasList talkgroupAliasList: mTalkgroupProtocolMap.values())
|
||||
//Note: because the alias' identifiers could have changed from when we initially added the alias, we have to
|
||||
//inspect every collection and map to remove the alias completely.
|
||||
mAliases.remove(alias);
|
||||
|
||||
mTalkgroupProtocolMap.values().stream().forEach(talkgroupAliasList -> talkgroupAliasList.remove(alias));
|
||||
mRadioProtocolMap.values().stream().forEach(radioAliasList -> radioAliasList.remove(alias));
|
||||
|
||||
Collection<Alias> collection = Collections.singleton(alias);
|
||||
mESNMap.values().removeAll(collection);
|
||||
mUnitStatusMap.values().removeAll(collection);
|
||||
mUserStatusMap.values().removeAll(collection);
|
||||
mToneSequenceMap.values().removeAll(collection);
|
||||
|
||||
validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies all aliases with an alias identifier that has the overlap flag set, resets the flag, and then readds
|
||||
* each alias back to this alias list so that overlap can be detected again.
|
||||
*/
|
||||
public void validate()
|
||||
{
|
||||
Set<Alias> overlapAliases = new HashSet<>();
|
||||
|
||||
List<Alias> aliases = new ArrayList<>(mAliases);
|
||||
|
||||
for(Alias alias: aliases)
|
||||
{
|
||||
talkgroupAliasList.remove(alias);
|
||||
for(AliasID aliasID: alias.getAliasIdentifiers())
|
||||
{
|
||||
if(aliasID.overlapProperty().get())
|
||||
{
|
||||
aliasID.setOverlap(false);
|
||||
overlapAliases.add(alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remove(alias, mStatusMap);
|
||||
remove(alias, mESNMap);
|
||||
overlapAliases.stream().forEach(alias -> addAlias(alias));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,39 +345,15 @@ public class AliasList implements Listener<AliasEvent>
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive alias change event notifications and modify this list accordingly
|
||||
* Updates the alias by removing it from this list and then adding it back to this list when the list name matches.
|
||||
*/
|
||||
@Override
|
||||
public void receive(AliasEvent event)
|
||||
public void updateAlias(Alias alias)
|
||||
{
|
||||
if(hasName())
|
||||
{
|
||||
Alias alias = event.getAlias();
|
||||
removeAlias(alias);
|
||||
|
||||
switch(event.getEvent())
|
||||
{
|
||||
case ADD:
|
||||
if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList()))
|
||||
{
|
||||
addAlias(alias);
|
||||
}
|
||||
break;
|
||||
case CHANGE:
|
||||
if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList()))
|
||||
{
|
||||
removeAlias(alias);
|
||||
addAlias(alias);
|
||||
}
|
||||
break;
|
||||
case DELETE:
|
||||
if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList()))
|
||||
{
|
||||
removeAlias(alias);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if(hasName() && getName().equals(alias.getAliasListName()))
|
||||
{
|
||||
addAlias(alias);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,14 +428,31 @@ public class AliasList implements Listener<AliasEvent>
|
||||
if(identifier instanceof UnitStatusIdentifier)
|
||||
{
|
||||
int status = ((UnitStatusIdentifier)identifier).getValue();
|
||||
return toList(mStatusMap.get(status));
|
||||
return toList(mUserStatusMap.get(status));
|
||||
}
|
||||
break;
|
||||
case USER_STATUS:
|
||||
if(identifier instanceof UserStatusIdentifier)
|
||||
{
|
||||
int status = ((UserStatusIdentifier)identifier).getValue();
|
||||
return toList(mStatusMap.get(status));
|
||||
return toList(mUserStatusMap.get(status));
|
||||
}
|
||||
break;
|
||||
case TONE:
|
||||
if(identifier instanceof ToneIdentifier)
|
||||
{
|
||||
ToneSequence toneSequence = ((ToneIdentifier)identifier).getValue();
|
||||
|
||||
if(toneSequence != null && toneSequence.hasTones())
|
||||
{
|
||||
for(Map.Entry<ToneSequence,Alias> entry: mToneSequenceMap.entrySet())
|
||||
{
|
||||
if(entry.getKey().isContainedIn(toneSequence))
|
||||
{
|
||||
return toList(entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -482,23 +585,6 @@ public class AliasList implements Listener<AliasEvent>
|
||||
return channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the alias (as a value) from the specified map
|
||||
*/
|
||||
public static void remove(Alias alias, Map map)
|
||||
{
|
||||
Iterator<Map.Entry> it = map.entrySet().iterator();
|
||||
|
||||
while(it.hasNext())
|
||||
{
|
||||
if(it.next().getValue().equals(alias))
|
||||
{
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Listing of talkgroups and ranges for a specific protocol
|
||||
*/
|
||||
@@ -515,16 +601,17 @@ public class AliasList implements Listener<AliasEvent>
|
||||
{
|
||||
int value = identifier.getValue();
|
||||
|
||||
if(mTalkgroupAliasMap.containsKey(value))
|
||||
Alias mapValue = mTalkgroupAliasMap.get(value);
|
||||
if (mapValue != null)
|
||||
{
|
||||
return mTalkgroupAliasMap.get(value);
|
||||
return mapValue;
|
||||
}
|
||||
|
||||
for(TalkgroupRange talkgroupRange: mTalkgroupRangeAliasMap.keySet())
|
||||
for(Map.Entry<TalkgroupRange, Alias> entry : mTalkgroupRangeAliasMap.entrySet())
|
||||
{
|
||||
if(talkgroupRange.contains(value))
|
||||
if(entry.getKey().contains(value))
|
||||
{
|
||||
return mTalkgroupRangeAliasMap.get(talkgroupRange);
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,16 +620,22 @@ public class AliasList implements Listener<AliasEvent>
|
||||
|
||||
public void add(Talkgroup talkgroup, Alias alias)
|
||||
{
|
||||
//Detect talkgroup collisions
|
||||
//Detect talkgroup collisions and set overlap flag for both
|
||||
if(mTalkgroupAliasMap.containsKey(talkgroup.getValue()))
|
||||
{
|
||||
Alias existing = mTalkgroupAliasMap.get(talkgroup.getValue());
|
||||
|
||||
if(!existing.equals(alias))
|
||||
{
|
||||
mLog.warn("Alias [" + alias.getName() + "] talkgroup [" + talkgroup.getValue() +
|
||||
"] has the same talkgroup value as alias [" + existing.getName() +
|
||||
"] - alias [" + alias.getName() + "] will be used for alias list [" + getName() + "]");
|
||||
talkgroup.setOverlap(true);
|
||||
|
||||
for(AliasID aliasID: existing.getAliasIdentifiers())
|
||||
{
|
||||
if(aliasID instanceof Talkgroup && ((Talkgroup)aliasID).getValue() == talkgroup.getValue())
|
||||
{
|
||||
aliasID.setOverlap(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,32 +649,21 @@ public class AliasList implements Listener<AliasEvent>
|
||||
{
|
||||
if(talkgroupRange.overlaps(entry.getKey()) && !entry.getValue().equals(alias))
|
||||
{
|
||||
mLog.warn("Alias [" + alias.getName() + "] with talkgroup range [" + talkgroupRange.toString() +
|
||||
"] overlaps with alias [" + entry.getValue().getName() +
|
||||
"] with talkgroup range [" + entry.getKey().toString() + "] for alias list [" + getName() + "]");
|
||||
talkgroupRange.setOverlap(true);
|
||||
entry.getKey().setOverlap(true);
|
||||
}
|
||||
}
|
||||
|
||||
mTalkgroupRangeAliasMap.put(talkgroupRange, alias);
|
||||
}
|
||||
|
||||
public void remove(Talkgroup talkgroup)
|
||||
{
|
||||
mTalkgroupAliasMap.remove(talkgroup.getValue());
|
||||
}
|
||||
|
||||
public void remove(TalkgroupRange talkgroupRange)
|
||||
{
|
||||
mTalkgroupRangeAliasMap.remove(talkgroupRange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the alias from all internal maps
|
||||
* Removes the alias from both the talkgroup and the talkgroup range maps.
|
||||
*/
|
||||
public void remove(Alias alias)
|
||||
{
|
||||
AliasList.remove(alias, mTalkgroupAliasMap);
|
||||
AliasList.remove(alias, mTalkgroupRangeAliasMap);
|
||||
mTalkgroupAliasMap.values().removeAll(Collections.singleton(alias));
|
||||
mTalkgroupRangeAliasMap.values().removeAll(Collections.singleton(alias));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -601,16 +683,17 @@ public class AliasList implements Listener<AliasEvent>
|
||||
{
|
||||
int value = identifier.getValue();
|
||||
|
||||
if(mRadioAliasMap.containsKey(value))
|
||||
Alias mapValue = mRadioAliasMap.get(value);
|
||||
if(mapValue != null)
|
||||
{
|
||||
return mRadioAliasMap.get(value);
|
||||
return mapValue;
|
||||
}
|
||||
|
||||
for(RadioRange radioRange: mRadioRangeAliasMap.keySet())
|
||||
for(Map.Entry<RadioRange, Alias> entry : mRadioRangeAliasMap.entrySet())
|
||||
{
|
||||
if(radioRange.contains(value))
|
||||
if(entry.getKey().contains(value))
|
||||
{
|
||||
return mRadioRangeAliasMap.get(radioRange);
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,9 +709,15 @@ public class AliasList implements Listener<AliasEvent>
|
||||
|
||||
if(!existing.equals(alias))
|
||||
{
|
||||
mLog.warn("Alias [" + alias.getName() + "] radio ID [" + radio.getValue() +
|
||||
"] has the same value as alias [" + existing.getName() +
|
||||
"] - alias [" + alias.getName() + "] will be used for alias list [" + getName() + "]");
|
||||
radio.setOverlap(true);
|
||||
|
||||
for(AliasID aliasID: existing.getAliasIdentifiers())
|
||||
{
|
||||
if(aliasID instanceof Radio && ((Radio)aliasID).getValue() == radio.getValue())
|
||||
{
|
||||
aliasID.setOverlap(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -642,32 +731,21 @@ public class AliasList implements Listener<AliasEvent>
|
||||
{
|
||||
if(radioRange.overlaps(entry.getKey()) && !entry.getValue().equals(alias))
|
||||
{
|
||||
mLog.warn("Alias [" + alias.getName() + "] with radio ID range [" + radioRange.toString() +
|
||||
"] overlaps with alias [" + entry.getValue().getName() +
|
||||
"] with range [" + entry.getKey().toString() + "] for alias list [" + getName() + "]");
|
||||
radioRange.setOverlap(true);
|
||||
entry.getKey().setOverlap(true);
|
||||
}
|
||||
}
|
||||
|
||||
mRadioRangeAliasMap.put(radioRange, alias);
|
||||
}
|
||||
|
||||
public void remove(Radio radio)
|
||||
{
|
||||
mRadioAliasMap.remove(radio.getValue());
|
||||
}
|
||||
|
||||
public void remove(RadioRange radioRange)
|
||||
{
|
||||
mRadioRangeAliasMap.remove(radioRange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the alias from all internal maps
|
||||
* Removes the alias from both the radio and the radio range maps.
|
||||
*/
|
||||
public void remove(Alias alias)
|
||||
{
|
||||
AliasList.remove(alias, mRadioAliasMap);
|
||||
AliasList.remove(alias, mRadioRangeAliasMap);
|
||||
mRadioAliasMap.values().removeAll(Collections.singleton(alias));
|
||||
mRadioRangeAliasMap.values().removeAll(Collections.singleton(alias));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* 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
|
||||
@@ -15,45 +14,54 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.alias.id.AliasIDType;
|
||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* Alias Model contains all aliases and is responsible for creation and management of alias lists. Alias lists are a
|
||||
* set of aliases that all share a common alias list name and can be attached to a decoding channel for aliasing
|
||||
* identifiers produced by channel decoder(s).
|
||||
*/
|
||||
public class AliasModel extends AbstractTableModel
|
||||
public class AliasModel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final int COLUMN_LIST = 0;
|
||||
public static final int COLUMN_GROUP = 1;
|
||||
public static final int COLUMN_NAME = 2;
|
||||
public static final int COLUMN_ICON = 3;
|
||||
public static final int COLUMN_COLOR = 4;
|
||||
|
||||
private List<Alias> mAliases = new CopyOnWriteArrayList<>();
|
||||
private Broadcaster<AliasEvent> mAliasEventBroadcaster = new Broadcaster<>();
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AliasModel.class);
|
||||
public static final String NO_ALIAS_LIST = "(No Alias List)";
|
||||
private ObservableList<Alias> mAliases = FXCollections.observableArrayList(Alias.extractor());
|
||||
private ObservableList<String> mAliasListNames = FXCollections.observableArrayList();
|
||||
private Map<String,AliasList> mAliasListMap = new HashMap<>();
|
||||
|
||||
public AliasModel()
|
||||
{
|
||||
//Register a listener to detect alias changes and broadcast change events to cause playlist save requests
|
||||
mAliases.addListener(new AliasListChangeListener());
|
||||
}
|
||||
|
||||
public ObservableList<Alias> aliasList()
|
||||
{
|
||||
return mAliases;
|
||||
}
|
||||
|
||||
public ObservableList<String> aliasListNames()
|
||||
{
|
||||
return mAliasListNames;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,14 +72,19 @@ public class AliasModel extends AbstractTableModel
|
||||
return Collections.unmodifiableList(mAliases);
|
||||
}
|
||||
|
||||
public Alias getAliasAtIndex(int row)
|
||||
/**
|
||||
* Removes all aliases from the list and broadcasts the alias delete event for each
|
||||
*/
|
||||
public void clear()
|
||||
{
|
||||
if(mAliases.size() >= row)
|
||||
List<Alias> aliasToRemove = new ArrayList<>(mAliases);
|
||||
|
||||
for(Alias alias: aliasToRemove)
|
||||
{
|
||||
return mAliases.get(row);
|
||||
removeAlias(alias);
|
||||
}
|
||||
|
||||
return null;
|
||||
mAliasListNames.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,16 +130,17 @@ public class AliasModel extends AbstractTableModel
|
||||
return new AliasList(name);
|
||||
}
|
||||
|
||||
if(mAliasListMap.containsKey(name))
|
||||
AliasList mapValue = mAliasListMap.get(name);
|
||||
if (mapValue != null)
|
||||
{
|
||||
return mAliasListMap.get(name);
|
||||
return mapValue;
|
||||
}
|
||||
|
||||
AliasList aliasList = new AliasList(name);
|
||||
|
||||
for(Alias alias : mAliases)
|
||||
{
|
||||
if(alias.hasList() && alias.getList().equalsIgnoreCase(name))
|
||||
if(alias.hasList() && alias.getAliasListName().equalsIgnoreCase(name))
|
||||
{
|
||||
aliasList.addAlias(alias);
|
||||
}
|
||||
@@ -134,9 +148,6 @@ public class AliasModel extends AbstractTableModel
|
||||
|
||||
mAliasListMap.put(name, aliasList);
|
||||
|
||||
//Register the new alias list to receive updates from this model
|
||||
addListener(aliasList);
|
||||
|
||||
return aliasList;
|
||||
}
|
||||
|
||||
@@ -145,19 +156,7 @@ public class AliasModel extends AbstractTableModel
|
||||
*/
|
||||
public List<String> getListNames()
|
||||
{
|
||||
List<String> listNames = new ArrayList<>();
|
||||
|
||||
for(Alias alias : mAliases)
|
||||
{
|
||||
if(alias.hasList() && !listNames.contains(alias.getList()))
|
||||
{
|
||||
listNames.add(alias.getList());
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(listNames);
|
||||
|
||||
return listNames;
|
||||
return mAliasListNames;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,33 +179,6 @@ public class AliasModel extends AbstractTableModel
|
||||
return groupNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of alias group names for all aliases that have a matching
|
||||
* list name value
|
||||
*/
|
||||
public List<String> getGroupNames(String listName)
|
||||
{
|
||||
List<String> groupNames = new ArrayList<>();
|
||||
|
||||
if(listName != null)
|
||||
{
|
||||
for(Alias alias : mAliases)
|
||||
{
|
||||
if(alias.hasList() &&
|
||||
alias.hasGroup() &&
|
||||
listName.equals(alias.getList()) &&
|
||||
!groupNames.contains(alias.getGroup()))
|
||||
{
|
||||
groupNames.add(alias.getGroup());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(groupNames);
|
||||
|
||||
return groupNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk loading of aliases
|
||||
*/
|
||||
@@ -221,173 +193,177 @@ public class AliasModel extends AbstractTableModel
|
||||
/**
|
||||
* Adds the alias to the model
|
||||
*/
|
||||
public int addAlias(Alias alias)
|
||||
public void addAlias(Alias alias)
|
||||
{
|
||||
if(alias != null)
|
||||
if(mAliases.contains(alias))
|
||||
{
|
||||
mAliases.add(alias);
|
||||
|
||||
int index = mAliases.size() - 1;
|
||||
|
||||
fireTableRowsInserted(index, index);
|
||||
|
||||
broadcast(new AliasEvent(alias, AliasEvent.Event.ADD));
|
||||
|
||||
return index;
|
||||
removeAlias(alias);
|
||||
}
|
||||
|
||||
return -1;
|
||||
mAliases.add(alias);
|
||||
}
|
||||
|
||||
private boolean hasAliasList(String aliasListName)
|
||||
{
|
||||
return aliasListName != null && mAliasListMap.containsKey(aliasListName);
|
||||
}
|
||||
|
||||
public void addAliasList(String aliasListName)
|
||||
{
|
||||
if(aliasListName != null && !aliasListName.isEmpty())
|
||||
{
|
||||
if(!mAliasListNames.contains(aliasListName))
|
||||
{
|
||||
mAliasListNames.add(aliasListName);
|
||||
}
|
||||
}
|
||||
else if(!mAliasListNames.contains(NO_ALIAS_LIST))
|
||||
{
|
||||
//This list allows users to view unassigned aliases so that they can move them to a valid alias list, but
|
||||
// it is not assignable to a channel
|
||||
mAliasListNames.add(NO_ALIAS_LIST);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the channel from the model and broadcasts a channel remove event
|
||||
* Removes the alias from this model and an alias list (if one exists)
|
||||
*/
|
||||
public void removeAlias(Alias alias)
|
||||
{
|
||||
if(alias != null)
|
||||
{
|
||||
int index = mAliases.indexOf(alias);
|
||||
|
||||
mAliases.remove(alias);
|
||||
|
||||
fireTableRowsDeleted(index, index);
|
||||
|
||||
broadcast(new AliasEvent(alias, AliasEvent.Event.DELETE));
|
||||
if(hasAliasList(alias.getAliasListName()))
|
||||
{
|
||||
getAliasList(alias.getAliasListName()).removeAlias(alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames any broadcast channels that have the previous name.
|
||||
*
|
||||
* @param previousName to rename
|
||||
* @param newName to assign to the broadcast channel
|
||||
* Retrieves all aliases that match the alias list and have at least one alias ID of the specified type
|
||||
* @param aliasListName to search
|
||||
* @param type to find
|
||||
* @return list of aliases
|
||||
*/
|
||||
public void renameBroadcastChannel(String previousName, String newName)
|
||||
public List<Alias> getAliases(String aliasListName, AliasIDType type)
|
||||
{
|
||||
if(previousName == null || previousName.isEmpty() || newName == null || newName.isEmpty())
|
||||
List<Alias> aliases = new ArrayList<>();
|
||||
|
||||
for(Alias alias : mAliases)
|
||||
{
|
||||
if(alias.hasList() && alias.getAliasListName().equalsIgnoreCase(aliasListName))
|
||||
{
|
||||
for(AliasID aliasID: alias.getAliasIdentifiers())
|
||||
{
|
||||
if(aliasID.getType() == type)
|
||||
{
|
||||
aliases.add(alias);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that one or more of the aliases managed by this model are configured to stream to the specified
|
||||
* broadcast channel argument.
|
||||
* @param broadcastChannel to check
|
||||
* @return true if the broadcast channel is non-null, non-empty and at least one alias is configured to stream to
|
||||
* the specified stream name.
|
||||
*/
|
||||
public boolean hasAliasesWithBroadcastChannel(String broadcastChannel)
|
||||
{
|
||||
if(broadcastChannel == null || broadcastChannel.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for(Alias alias: mAliases)
|
||||
{
|
||||
if(alias.hasBroadcastChannel(broadcastChannel))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all aliases configured to stream to the previousStreamName with the updatedStreamName
|
||||
* @param previousStreamName to be removed
|
||||
* @param updatedStreamName to be added
|
||||
*/
|
||||
public void updateBroadcastChannel(String previousStreamName, String updatedStreamName)
|
||||
{
|
||||
if(previousStreamName == null || previousStreamName.isEmpty() || updatedStreamName == null || updatedStreamName.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(Alias alias : mAliases)
|
||||
for(Alias alias: mAliases)
|
||||
{
|
||||
if(alias.hasBroadcastChannel(previousName))
|
||||
if(alias.hasBroadcastChannel(previousStreamName))
|
||||
{
|
||||
for(BroadcastChannel broadcastChannel : alias.getBroadcastChannels())
|
||||
for(BroadcastChannel broadcastChannel: alias.getBroadcastChannels())
|
||||
{
|
||||
if(broadcastChannel.getChannelName().contentEquals(previousName))
|
||||
if(broadcastChannel.getChannelName().contentEquals(previousStreamName))
|
||||
{
|
||||
broadcastChannel.setChannelName(newName);
|
||||
alias.removeAliasID(broadcastChannel);
|
||||
|
||||
if(!alias.hasBroadcastChannel(updatedStreamName))
|
||||
{
|
||||
alias.addAliasID(new BroadcastChannel(updatedStreamName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount()
|
||||
/**
|
||||
* Monitors alias additions and removals and updates alias lists
|
||||
*/
|
||||
public class AliasListChangeListener implements ListChangeListener<Alias>
|
||||
{
|
||||
return mAliases.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount()
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int columnIndex)
|
||||
{
|
||||
switch(columnIndex)
|
||||
@Override
|
||||
public void onChanged(ListChangeListener.Change<? extends Alias> change)
|
||||
{
|
||||
case COLUMN_LIST:
|
||||
return "List";
|
||||
case COLUMN_GROUP:
|
||||
return "Group";
|
||||
case COLUMN_NAME:
|
||||
return "Name";
|
||||
case COLUMN_ICON:
|
||||
return "Icon";
|
||||
case COLUMN_COLOR:
|
||||
return "Color";
|
||||
while(change.next())
|
||||
{
|
||||
if(change.wasAdded())
|
||||
{
|
||||
for(Alias alias: change.getAddedSubList())
|
||||
{
|
||||
addAliasList(alias.getAliasListName());
|
||||
|
||||
if(hasAliasList(alias.getAliasListName()))
|
||||
{
|
||||
getAliasList(alias.getAliasListName()).addAlias(alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(change.wasRemoved())
|
||||
{
|
||||
for(Alias alias: change.getRemoved())
|
||||
{
|
||||
if(hasAliasList(alias.getAliasListName()))
|
||||
{
|
||||
AliasList aliasList = getAliasList(alias.getName());
|
||||
|
||||
if(alias != null)
|
||||
{
|
||||
aliasList.removeAlias(alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getColumnClass(int columnIndex)
|
||||
{
|
||||
if(columnIndex == COLUMN_COLOR)
|
||||
{
|
||||
return Integer.class;
|
||||
}
|
||||
|
||||
return String.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int rowIndex, int columnIndex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex)
|
||||
{
|
||||
Alias alias = mAliases.get(rowIndex);
|
||||
|
||||
switch(columnIndex)
|
||||
{
|
||||
case COLUMN_LIST:
|
||||
return alias.getList();
|
||||
case COLUMN_GROUP:
|
||||
return alias.getGroup();
|
||||
case COLUMN_NAME:
|
||||
return alias.getName();
|
||||
case COLUMN_ICON:
|
||||
return alias.getIconName();
|
||||
case COLUMN_COLOR:
|
||||
return alias.getColor();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueAt(Object aValue, int rowIndex, int columnIndex)
|
||||
{
|
||||
throw new IllegalArgumentException("Not yet implemented");
|
||||
}
|
||||
|
||||
public void broadcast(AliasEvent event)
|
||||
{
|
||||
Alias alias = event.getAlias();
|
||||
|
||||
//Validate the alias following a user action that changed the alias or any alias IDs
|
||||
if(alias != null)
|
||||
{
|
||||
alias.validate();
|
||||
}
|
||||
|
||||
if(event.getEvent() == AliasEvent.Event.CHANGE)
|
||||
{
|
||||
int index = mAliases.indexOf(event.getAlias());
|
||||
|
||||
fireTableRowsUpdated(index, index);
|
||||
}
|
||||
|
||||
mAliasEventBroadcaster.broadcast(event);
|
||||
}
|
||||
|
||||
public void addListener(Listener<AliasEvent> listener)
|
||||
{
|
||||
mAliasEventBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
public void removeListener(Listener<AliasEvent> listener)
|
||||
{
|
||||
mAliasEventBroadcaster.removeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,333 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias;
|
||||
|
||||
import io.github.dsheirer.gui.editor.Editor;
|
||||
import io.github.dsheirer.icon.Icon;
|
||||
import io.github.dsheirer.icon.IconListCellRenderer;
|
||||
import io.github.dsheirer.icon.IconManager;
|
||||
import io.github.dsheirer.icon.IconTableModel;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.event.TableModelEvent;
|
||||
import javax.swing.event.TableModelListener;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.List;
|
||||
|
||||
public class AliasNameEditor extends Editor<Alias>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static ComboBoxModel<String> EMPTY_LIST_MODEL = new DefaultComboBoxModel<>();
|
||||
private static ComboBoxModel<String> EMPTY_GROUP_MODEL = new DefaultComboBoxModel<>();
|
||||
|
||||
private JComboBox<String> mListCombo = new JComboBox<>(EMPTY_LIST_MODEL);
|
||||
private JComboBox<String> mGroupCombo = new JComboBox<>(EMPTY_GROUP_MODEL);
|
||||
private JComboBox<Icon> mIconCombo;
|
||||
private JTextField mName;
|
||||
private JButton mButtonColor;
|
||||
private JButton mBtnIconManager;
|
||||
|
||||
private AliasModel mAliasModel;
|
||||
private IconManager mIconManager;
|
||||
|
||||
public AliasNameEditor(AliasModel aliasModel, IconManager iconManager)
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
mIconManager = iconManager;
|
||||
|
||||
mIconManager.getModel().addTableModelListener(new TableModelListener()
|
||||
{
|
||||
@Override
|
||||
public void tableChanged(TableModelEvent e)
|
||||
{
|
||||
refreshIcons();
|
||||
}
|
||||
});
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 2", "[right][grow,fill]", "[][][][][][][grow]"));
|
||||
|
||||
add(new JLabel("Name:"));
|
||||
mName = new JTextField();
|
||||
mName.getDocument().addDocumentListener(new DocumentListener()
|
||||
{
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e)
|
||||
{
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e)
|
||||
{
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e)
|
||||
{
|
||||
}
|
||||
});
|
||||
add(mName, "wrap");
|
||||
|
||||
add(new JLabel("List:"));
|
||||
mListCombo.setEditable(true);
|
||||
mListCombo.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
if(mListCombo.getSelectedItem() != null)
|
||||
{
|
||||
List<String> groups = mAliasModel
|
||||
.getGroupNames((String) mListCombo.getSelectedItem());
|
||||
|
||||
if(groups.isEmpty())
|
||||
{
|
||||
mGroupCombo.setModel(EMPTY_GROUP_MODEL);
|
||||
}
|
||||
else
|
||||
{
|
||||
mGroupCombo.setModel(new DefaultComboBoxModel<String>(
|
||||
groups.toArray(new String[groups.size()])));
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
add(mListCombo, "wrap");
|
||||
|
||||
add(new JLabel("Group:"));
|
||||
mGroupCombo.setEditable(true);
|
||||
mGroupCombo.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
add(mGroupCombo, "wrap");
|
||||
|
||||
add(new JLabel("Color:"));
|
||||
|
||||
mButtonColor = new JButton("Select ...");
|
||||
mButtonColor.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
Color newColor = JColorChooser.showDialog(
|
||||
AliasNameEditor.this,
|
||||
"Choose color for this alias",
|
||||
(hasItem() ? getItem().getDisplayColor() : null));
|
||||
|
||||
if(newColor != null)
|
||||
{
|
||||
mButtonColor.setForeground(newColor);
|
||||
mButtonColor.setBackground(newColor);
|
||||
|
||||
setModified(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
add(mButtonColor, "wrap");
|
||||
|
||||
add(new JLabel("Icon:"));
|
||||
|
||||
mIconCombo = new JComboBox<Icon>(mIconManager.getIcons());
|
||||
|
||||
IconListCellRenderer renderer = new IconListCellRenderer(mIconManager);
|
||||
renderer.setPreferredSize(new Dimension(200, 30));
|
||||
mIconCombo.setRenderer(renderer);
|
||||
mIconCombo.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
|
||||
add(mIconCombo, "wrap");
|
||||
|
||||
//Dummy place holder
|
||||
add(new JLabel());
|
||||
|
||||
mBtnIconManager = new JButton("Icon Manager");
|
||||
mBtnIconManager.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
mIconManager.showEditor(AliasNameEditor.this);
|
||||
}
|
||||
});
|
||||
|
||||
add(mBtnIconManager, "span 2,wrap");
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
private void refreshIcons()
|
||||
{
|
||||
if(mIconCombo != null)
|
||||
{
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mIconCombo.setModel(new DefaultComboBoxModel<>(mIconManager.getIcons()));
|
||||
|
||||
if(hasItem())
|
||||
{
|
||||
String iconName = getItem().getIconName();
|
||||
Icon aliasIcon = mIconManager.getModel().getIcon(iconName);
|
||||
|
||||
if(aliasIcon != null)
|
||||
{
|
||||
mIconCombo.setSelectedItem(aliasIcon);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(Alias alias)
|
||||
{
|
||||
super.setItem(alias);
|
||||
|
||||
if(hasItem())
|
||||
{
|
||||
mName.setText(alias.getName());
|
||||
|
||||
mIconCombo.setModel(new DefaultComboBoxModel<>(mIconManager.getIcons()));
|
||||
|
||||
List<String> listNames = mAliasModel.getListNames();
|
||||
|
||||
if(listNames.isEmpty())
|
||||
{
|
||||
mListCombo.setModel(EMPTY_LIST_MODEL);
|
||||
}
|
||||
else
|
||||
{
|
||||
mListCombo.setModel(new DefaultComboBoxModel<String>(
|
||||
listNames.toArray(new String[listNames.size()])));
|
||||
;
|
||||
}
|
||||
|
||||
mListCombo.setSelectedItem(alias.getList());
|
||||
|
||||
List<String> groupNames = mAliasModel.getGroupNames(alias.getList());
|
||||
|
||||
if(groupNames.isEmpty())
|
||||
{
|
||||
mGroupCombo.setModel(EMPTY_GROUP_MODEL);
|
||||
}
|
||||
else
|
||||
{
|
||||
mGroupCombo.setModel(new DefaultComboBoxModel<String>(
|
||||
groupNames.toArray(new String[groupNames.size()])));
|
||||
;
|
||||
}
|
||||
|
||||
mGroupCombo.setSelectedItem(alias.getGroup());
|
||||
|
||||
Color color = alias.getDisplayColor();
|
||||
|
||||
mButtonColor.setBackground(color);
|
||||
mButtonColor.setForeground(color);
|
||||
|
||||
String iconName = alias.getIconName();
|
||||
|
||||
if(iconName == null)
|
||||
{
|
||||
iconName = IconTableModel.DEFAULT_ICON;
|
||||
}
|
||||
|
||||
Icon savedIcon = mIconManager.getModel().getIcon(iconName);
|
||||
|
||||
if(savedIcon != null)
|
||||
{
|
||||
mIconCombo.setSelectedItem(savedIcon);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mListCombo.setModel(EMPTY_LIST_MODEL);
|
||||
mGroupCombo.setModel(EMPTY_GROUP_MODEL);
|
||||
mName.setText(null);
|
||||
|
||||
mButtonColor.setBackground(getBackground());
|
||||
mButtonColor.setForeground(getForeground());
|
||||
}
|
||||
|
||||
repaint();
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if(hasItem() && isModified())
|
||||
{
|
||||
Alias alias = getItem();
|
||||
|
||||
if(mListCombo.getSelectedItem() != null)
|
||||
{
|
||||
alias.setList((String) mListCombo.getSelectedItem());
|
||||
}
|
||||
|
||||
if(mGroupCombo.getSelectedItem() != null)
|
||||
{
|
||||
alias.setGroup((String) mGroupCombo.getSelectedItem());
|
||||
}
|
||||
|
||||
alias.setName(mName.getText());
|
||||
|
||||
alias.setColor(mButtonColor.getBackground().getRGB());
|
||||
|
||||
if(mIconCombo.getSelectedItem() != null)
|
||||
{
|
||||
alias.setIconName(((Icon) mIconCombo.getSelectedItem()).getName());
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
|
||||
//Broadcast an alias change event to save the updates
|
||||
mAliasModel.broadcast(new AliasEvent(getItem(), AliasEvent.Event.CHANGE));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
public abstract class ComponentEditor<T> extends JPanel implements DocumentListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private boolean mModified;
|
||||
private ComponentModificationListener mListener;
|
||||
protected T mComponent;
|
||||
|
||||
/**
|
||||
* Abstract class for editor with notifications when contents are modified
|
||||
* and methods for saving or resetting the component values with the
|
||||
* contents of the editor.
|
||||
*/
|
||||
public ComponentEditor( T component )
|
||||
{
|
||||
mComponent = component;
|
||||
}
|
||||
|
||||
public ComponentEditor()
|
||||
{
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mComponent = null;
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
public T getComponent()
|
||||
{
|
||||
return mComponent;
|
||||
}
|
||||
|
||||
protected void setModified( boolean modified )
|
||||
{
|
||||
mModified = modified;
|
||||
|
||||
if( modified && mListener != null )
|
||||
{
|
||||
mListener.componentModified();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the contents of the editor have been modified
|
||||
*/
|
||||
public boolean isModified()
|
||||
{
|
||||
return mModified;
|
||||
}
|
||||
|
||||
public void setComponentModificationListener( ComponentModificationListener listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or resets the editable component
|
||||
*/
|
||||
public abstract void setComponent( T t );
|
||||
|
||||
/**
|
||||
* Saves changed editor values to the component
|
||||
*/
|
||||
public abstract void save();
|
||||
|
||||
/**
|
||||
* Reloads the editor contents from the source component and resets the
|
||||
* modified flag
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
setComponent( getComponent() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate contents of other editors against this component editor before
|
||||
* saving. Override this method to allow controller to pass other editors
|
||||
* to this editor for content inspection/validation before indicating that
|
||||
* this editor passes validation. This allows for validating editor contents
|
||||
* that are contingent on settings in other editors.
|
||||
*
|
||||
* @throws ComponentValidationException if the contents of the editor
|
||||
* argument cannot be validated, with the exception containing a detailed
|
||||
* description of the issue.
|
||||
*/
|
||||
public void validate( ComponentEditor<?> editor ) throws ComponentValidationException
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener interface to receive notification that the contents of the
|
||||
* editor have been modified from the original component value and either
|
||||
* need to be saved or reset
|
||||
*/
|
||||
public interface ComponentModificationListener
|
||||
{
|
||||
public void componentModified();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package io.github.dsheirer.alias;
|
||||
|
||||
public class ComponentValidationException extends Exception
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Component editor contents validation error
|
||||
*/
|
||||
public ComponentValidationException( String message )
|
||||
{
|
||||
super( message );
|
||||
}
|
||||
}
|
||||
@@ -1,466 +0,0 @@
|
||||
package io.github.dsheirer.alias;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasIDType;
|
||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||
import io.github.dsheirer.alias.id.priority.Priority;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastModel;
|
||||
import io.github.dsheirer.gui.editor.Editor;
|
||||
import io.github.dsheirer.icon.Icon;
|
||||
import io.github.dsheirer.icon.IconListCellRenderer;
|
||||
import io.github.dsheirer.icon.IconManager;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.event.TableModelEvent;
|
||||
import javax.swing.event.TableModelListener;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.List;
|
||||
|
||||
public class MultipleAliasEditor extends Editor<List<Alias>>
|
||||
implements Listener<AliasEvent>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MultipleAliasEditor.class);
|
||||
|
||||
private static String HELP_TEXT =
|
||||
"<html>Select attributes below to change for all selected aliases</html>";
|
||||
|
||||
private JLabel mAliasCountLabel;
|
||||
private JLabel mAliasCount;
|
||||
|
||||
private JCheckBox mListCheckBox = new JCheckBox("List");
|
||||
private JComboBox<String> mListCombo = new JComboBox<>();
|
||||
private JCheckBox mGroupCheckBox = new JCheckBox("Group");
|
||||
private JComboBox<String> mGroupCombo = new JComboBox<>();
|
||||
private JCheckBox mIconCheckBox = new JCheckBox("Icon");
|
||||
private JComboBox<Icon> mIconCombo;
|
||||
private JCheckBox mColorCheckBox = new JCheckBox("Color");
|
||||
private JButton mButtonColor;
|
||||
private JButton mBtnIconManager;
|
||||
private JCheckBox mRecordCheckBox = new JCheckBox("Record");
|
||||
private JComboBox<Record> mRecordActionCombo = new JComboBox<>(Record.values());
|
||||
private JCheckBox mPriorityCheckBox = new JCheckBox("Priority");
|
||||
private JSlider mPrioritySlider;
|
||||
private JLabel mPrioritySliderLabel;
|
||||
private JCheckBox mStreamCheckBox = new JCheckBox("Stream");
|
||||
private JComboBox<String> mStreamCombo;
|
||||
|
||||
private AliasModel mAliasModel;
|
||||
private BroadcastModel mBroadcastModel;
|
||||
private IconManager mIconManager;
|
||||
|
||||
public MultipleAliasEditor(AliasModel aliasModel, BroadcastModel broadcastModel, IconManager iconManager)
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
mBroadcastModel = broadcastModel;
|
||||
mIconManager = iconManager;
|
||||
|
||||
mAliasModel.addListener(this);
|
||||
mIconManager.getModel().addTableModelListener(new TableModelListener()
|
||||
{
|
||||
@Override
|
||||
public void tableChanged(TableModelEvent e)
|
||||
{
|
||||
refreshIcons();
|
||||
}
|
||||
});
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
public List<Alias> getAliases()
|
||||
{
|
||||
if (hasItem())
|
||||
{
|
||||
return (List<Alias>) getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void init()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 2", "[grow,fill][grow,fill]",
|
||||
"[][][][][][][][][][][][][][grow,fill]"));
|
||||
|
||||
mAliasCountLabel = new JLabel("Alias:");
|
||||
add(mAliasCountLabel);
|
||||
|
||||
mAliasCount = new JLabel("Multiple");
|
||||
add(mAliasCount, "span");
|
||||
|
||||
add(new JSeparator(), "span");
|
||||
|
||||
add(new JLabel(HELP_TEXT), "span");
|
||||
|
||||
add(mListCheckBox);
|
||||
|
||||
mListCombo.setEditable(true);
|
||||
add(mListCombo, "wrap");
|
||||
|
||||
add(mGroupCheckBox);
|
||||
|
||||
mGroupCombo.setEditable(true);
|
||||
add(mGroupCombo, "wrap");
|
||||
|
||||
add(mColorCheckBox);
|
||||
|
||||
mButtonColor = new JButton("Select ...");
|
||||
mButtonColor.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
Color newColor = JColorChooser.showDialog(
|
||||
MultipleAliasEditor.this,
|
||||
"Choose color for this alias", null);
|
||||
|
||||
if (newColor != null)
|
||||
{
|
||||
mButtonColor.setForeground(newColor);
|
||||
mButtonColor.setBackground(newColor);
|
||||
}
|
||||
}
|
||||
});
|
||||
add(mButtonColor, "wrap");
|
||||
|
||||
add(mIconCheckBox);
|
||||
|
||||
mIconCombo = new JComboBox<Icon>(mIconManager.getIcons());
|
||||
|
||||
IconListCellRenderer renderer = new IconListCellRenderer(mIconManager);
|
||||
renderer.setPreferredSize(new Dimension(200, 30));
|
||||
mIconCombo.setRenderer(renderer);
|
||||
add(mIconCombo, "wrap");
|
||||
|
||||
//Dummy place holder
|
||||
add(new JLabel());
|
||||
|
||||
mBtnIconManager = new JButton("Icon Manager");
|
||||
mBtnIconManager.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
mIconManager.showEditor(MultipleAliasEditor.this);
|
||||
}
|
||||
});
|
||||
|
||||
add(mBtnIconManager, "span,wrap");
|
||||
|
||||
add(mRecordCheckBox);
|
||||
add(mRecordActionCombo);
|
||||
|
||||
//Placeholder
|
||||
add(new JLabel(" "));
|
||||
mPrioritySliderLabel = new JLabel("Audio Priority: " + Priority.MIN_PRIORITY);
|
||||
add(mPrioritySliderLabel);
|
||||
|
||||
add(mPriorityCheckBox);
|
||||
|
||||
mPrioritySlider = new JSlider(JSlider.HORIZONTAL,
|
||||
Priority.MIN_PRIORITY,
|
||||
Priority.MAX_PRIORITY + 1,
|
||||
Priority.MIN_PRIORITY);
|
||||
|
||||
mPrioritySlider.setMajorTickSpacing(20);
|
||||
mPrioritySlider.setMinorTickSpacing(5);
|
||||
mPrioritySlider.setPaintTicks(true);
|
||||
mPrioritySlider.setLabelTable(mPrioritySlider.createStandardLabels(25, 25));
|
||||
mPrioritySlider.setPaintLabels(true);
|
||||
mPrioritySlider.addChangeListener(new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e)
|
||||
{
|
||||
int priority = mPrioritySlider.getValue();
|
||||
|
||||
if (priority > Priority.MAX_PRIORITY)
|
||||
{
|
||||
mPrioritySliderLabel.setText("Audio Priority: Do Not Monitor");
|
||||
}
|
||||
else
|
||||
{
|
||||
mPrioritySliderLabel.setText("Audio Priority: " + mPrioritySlider.getValue());
|
||||
}
|
||||
}
|
||||
});
|
||||
mPrioritySlider.setToolTipText(HELP_TEXT);
|
||||
|
||||
add(mPrioritySlider);
|
||||
|
||||
add(mStreamCheckBox);
|
||||
|
||||
mStreamCombo = new JComboBox<String>();
|
||||
mStreamCombo.setEditable(true);
|
||||
add(mStreamCombo, "wrap");
|
||||
|
||||
JButton saveButton = new JButton("Save");
|
||||
saveButton.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
save();
|
||||
}
|
||||
});
|
||||
add(saveButton);
|
||||
|
||||
JButton resetButton = new JButton("Reset");
|
||||
resetButton.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
});
|
||||
add(resetButton);
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
private void refreshIcons()
|
||||
{
|
||||
if(mIconCombo != null)
|
||||
{
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mIconCombo.setModel(new DefaultComboBoxModel<>(mIconManager.getIcons()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(List<Alias> item)
|
||||
{
|
||||
super.setItem(item);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Multiple Selected [ ");
|
||||
|
||||
if (hasItem())
|
||||
{
|
||||
sb.append(String.valueOf(getAliases().size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("0");
|
||||
}
|
||||
|
||||
sb.append(" ]");
|
||||
|
||||
mAliasCount.setText(sb.toString());
|
||||
|
||||
List<String> listNames = mAliasModel.getListNames();
|
||||
listNames.add(0, "");
|
||||
mListCombo.setModel(new DefaultComboBoxModel<String>(
|
||||
listNames.toArray(new String[listNames.size()])));
|
||||
|
||||
List<String> groupNames = mAliasModel.getGroupNames();
|
||||
groupNames.add(0, "");
|
||||
mGroupCombo.setModel(new DefaultComboBoxModel<String>(
|
||||
groupNames.toArray(new String[groupNames.size()])));
|
||||
|
||||
List<String> streamNames = mBroadcastModel.getBroadcastConfigurationNames();
|
||||
mStreamCombo.setModel(new DefaultComboBoxModel<String>(streamNames.toArray(new String[streamNames.size()])));
|
||||
|
||||
mIconCombo.setModel(new DefaultComboBoxModel<>(mIconManager.getIcons()));
|
||||
|
||||
setCheckBoxesUnselected();
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
private void setCheckBoxesUnselected()
|
||||
{
|
||||
mListCheckBox.setSelected(false);
|
||||
mGroupCheckBox.setSelected(false);
|
||||
mColorCheckBox.setSelected(false);
|
||||
mIconCheckBox.setSelected(false);
|
||||
mRecordCheckBox.setSelected(false);
|
||||
mPriorityCheckBox.setSelected(false);
|
||||
mStreamCheckBox.setSelected(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
List<String> listNames = mAliasModel.getListNames();
|
||||
listNames.add(0, "");
|
||||
mListCombo.setModel(new DefaultComboBoxModel<String>(
|
||||
listNames.toArray(new String[listNames.size()])));
|
||||
|
||||
List<String> groupNames = mAliasModel.getGroupNames();
|
||||
groupNames.add(0, "");
|
||||
mGroupCombo.setModel(new DefaultComboBoxModel<String>(
|
||||
groupNames.toArray(new String[groupNames.size()])));
|
||||
|
||||
mButtonColor.setForeground(getForeground());
|
||||
mButtonColor.setBackground(getBackground());
|
||||
|
||||
super.reset();
|
||||
}
|
||||
|
||||
private boolean hasRequestedChanges()
|
||||
{
|
||||
return mListCheckBox.isSelected() ||
|
||||
mGroupCheckBox.isSelected() ||
|
||||
mColorCheckBox.isSelected() ||
|
||||
mIconCheckBox.isSelected() ||
|
||||
mRecordCheckBox.isSelected() ||
|
||||
mPriorityCheckBox.isSelected() ||
|
||||
mStreamCheckBox.isSelected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if (hasRequestedChanges() && hasItem())
|
||||
{
|
||||
List<Alias> aliases = getAliases();
|
||||
|
||||
for (Alias alias : aliases)
|
||||
{
|
||||
if (mListCheckBox.isSelected())
|
||||
{
|
||||
String list = null;
|
||||
|
||||
if (mListCombo.getSelectedItem() != null)
|
||||
{
|
||||
list = (String) mListCombo.getSelectedItem();
|
||||
}
|
||||
|
||||
alias.setList(list);
|
||||
}
|
||||
|
||||
if (mGroupCheckBox.isSelected())
|
||||
{
|
||||
String group = null;
|
||||
|
||||
if (mGroupCombo.getSelectedItem() != null)
|
||||
{
|
||||
group = (String) mGroupCombo.getSelectedItem();
|
||||
}
|
||||
|
||||
alias.setGroup(group);
|
||||
}
|
||||
|
||||
if (mColorCheckBox.isSelected())
|
||||
{
|
||||
alias.setColor(mButtonColor.getForeground().getRGB());
|
||||
}
|
||||
|
||||
if (mIconCheckBox.isSelected())
|
||||
{
|
||||
if (mIconCombo.getSelectedItem() != null)
|
||||
{
|
||||
alias.setIconName(((Icon)mIconCombo.getSelectedItem()).getName());
|
||||
}
|
||||
else
|
||||
{
|
||||
alias.setIconName(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (mRecordCheckBox.isSelected())
|
||||
{
|
||||
Record action =
|
||||
(Record) mRecordActionCombo.getSelectedItem();
|
||||
|
||||
alias.setRecordable(action == Record.RECORDABLE);
|
||||
}
|
||||
|
||||
if (mPriorityCheckBox.isSelected())
|
||||
{
|
||||
int priority = mPrioritySlider.getValue();
|
||||
|
||||
//This is a work-around -- we use max priority + 1 in the
|
||||
//gui to indicate do not monitor, and change the value here
|
||||
if (priority == Priority.MAX_PRIORITY + 1)
|
||||
{
|
||||
priority = Priority.DO_NOT_MONITOR;
|
||||
}
|
||||
|
||||
alias.setCallPriority(priority);
|
||||
}
|
||||
|
||||
if(mStreamCheckBox.isSelected())
|
||||
{
|
||||
if(mStreamCombo.getSelectedItem() != null)
|
||||
{
|
||||
String streamName = (String)mStreamCombo.getSelectedItem();
|
||||
|
||||
boolean hasChannel = false;
|
||||
|
||||
//Ensure the alias doesn't already have the channel
|
||||
for(BroadcastChannel channel: alias.getBroadcastChannels())
|
||||
{
|
||||
if(channel.getChannelName().equals(streamName))
|
||||
{
|
||||
hasChannel = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if(!hasChannel)
|
||||
{
|
||||
BroadcastChannel channel = (BroadcastChannel)AliasFactory
|
||||
.getAliasID(AliasIDType.BROADCAST_CHANNEL);
|
||||
|
||||
channel.setChannelName(streamName);
|
||||
alias.addAliasID(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Alias alias : aliases)
|
||||
{
|
||||
//Broadcast an alias change event to save the updates
|
||||
mAliasModel.broadcast(new AliasEvent(alias, AliasEvent.Event.CHANGE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(AliasEvent event)
|
||||
{
|
||||
if (event.getEvent() == AliasEvent.Event.DELETE &&
|
||||
hasItem() &&
|
||||
getAliases().contains(event.getAlias()))
|
||||
{
|
||||
getAliases().remove(event.getAlias());
|
||||
setItem(getAliases());
|
||||
}
|
||||
}
|
||||
|
||||
public enum Record
|
||||
{
|
||||
NON_RECORDABLE("Non-Recordable"),
|
||||
RECORDABLE("Recordable");
|
||||
|
||||
private String mLabel;
|
||||
|
||||
private Record(String label)
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,9 @@ import io.github.dsheirer.alias.action.beep.BeepAction;
|
||||
import io.github.dsheirer.alias.action.clip.ClipAction;
|
||||
import io.github.dsheirer.alias.action.script.ScriptAction;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.util.Callback;
|
||||
|
||||
/**
|
||||
* Alias action defines an action to execute when an alias is detected active.
|
||||
@@ -41,10 +44,33 @@ import io.github.dsheirer.message.IMessage;
|
||||
@JacksonXmlRootElement(localName = "action")
|
||||
public abstract class AliasAction
|
||||
{
|
||||
private SimpleStringProperty mValueProperty = new SimpleStringProperty();
|
||||
|
||||
public AliasAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* String property representation of this alias ID
|
||||
*/
|
||||
@JsonIgnore
|
||||
public SimpleStringProperty valueProperty()
|
||||
{
|
||||
return mValueProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the value property for this alias ID. Note: this method is intended to be invoked by all subclasses
|
||||
* each time that any of the subclass member variable values change.
|
||||
*/
|
||||
public void updateValueProperty()
|
||||
{
|
||||
valueProperty().set(toString());
|
||||
|
||||
//Hack: harmless, but the list extractor does not consistently update unless we do this.
|
||||
valueProperty().get();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public abstract AliasActionType getType();
|
||||
|
||||
@@ -60,4 +86,12 @@ public abstract class AliasAction
|
||||
* Dismiss a persistent alias action
|
||||
*/
|
||||
public abstract void dismiss(boolean reset);
|
||||
|
||||
/**
|
||||
* Creates an observable property extractor for use with observable lists to detect changes internal to this object.
|
||||
*/
|
||||
public static Callback<AliasAction,Observable[]> extractor()
|
||||
{
|
||||
return (AliasAction aliasAction) -> new Observable[] {aliasAction.valueProperty()};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,339 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias.action;
|
||||
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasEvent;
|
||||
import io.github.dsheirer.alias.AliasEvent.Event;
|
||||
import io.github.dsheirer.alias.AliasFactory;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.gui.editor.Editor;
|
||||
import io.github.dsheirer.gui.editor.EmptyEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class AliasActionEditor extends Editor<Alias>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static ListModel<AliasAction> EMPTY_MODEL = new DefaultListModel<>();
|
||||
private JList<AliasAction> mAliasActionList = new JList<>( EMPTY_MODEL );
|
||||
private JButton mNewActionButton;
|
||||
private JButton mCloneActionButton;
|
||||
private JButton mDeleteActionButton;
|
||||
private EditorContainer mEditorContainer = new EditorContainer();
|
||||
|
||||
private AliasModel mAliasModel;
|
||||
|
||||
public AliasActionEditor( AliasModel model )
|
||||
{
|
||||
mAliasModel = model;
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 3",
|
||||
"[grow,fill][grow,fill][grow,fill]", "[][grow,fill][]" ) );
|
||||
|
||||
mAliasActionList.setVisibleRowCount( 6 );
|
||||
mAliasActionList.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
|
||||
mAliasActionList.setLayoutOrientation( JList.VERTICAL );
|
||||
mAliasActionList.addListSelectionListener( new ListSelectionListener()
|
||||
{
|
||||
@Override
|
||||
public void valueChanged( ListSelectionEvent event )
|
||||
{
|
||||
if( !event.getValueIsAdjusting() )
|
||||
{
|
||||
JList<?> list = (JList<?>)event.getSource();
|
||||
|
||||
Object selectedItem = list.getSelectedValue();
|
||||
|
||||
if( selectedItem != null && selectedItem instanceof AliasAction )
|
||||
{
|
||||
AliasAction selected = (AliasAction)selectedItem;
|
||||
|
||||
mEditorContainer.setAliasAction( selected );
|
||||
|
||||
mCloneActionButton.setEnabled( true );
|
||||
mDeleteActionButton.setEnabled( true );
|
||||
}
|
||||
else
|
||||
{
|
||||
mCloneActionButton.setEnabled( false );
|
||||
mDeleteActionButton.setEnabled( false );
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
JScrollPane scroller = new JScrollPane( mAliasActionList );
|
||||
add( scroller, "span,grow" );
|
||||
|
||||
add( mEditorContainer, "span,grow" );
|
||||
|
||||
mNewActionButton = new JButton( "New ..." );
|
||||
mNewActionButton.setToolTipText( "Create a new Alias Action" );
|
||||
mNewActionButton.addMouseListener( new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e )
|
||||
{
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
|
||||
menu.add( new AddAliasActionItem( AliasActionType.BEEP ) );
|
||||
menu.add( new AddAliasActionItem( AliasActionType.CLIP ) );
|
||||
menu.add( new AddAliasActionItem( AliasActionType.SCRIPT ) );
|
||||
|
||||
menu.show( e.getComponent(), e.getX(), e.getY() );
|
||||
}
|
||||
} );
|
||||
|
||||
add( mNewActionButton );
|
||||
|
||||
mCloneActionButton = new JButton( "Clone" );
|
||||
mCloneActionButton.setToolTipText( "Create a copy of the currently selected action" );
|
||||
mCloneActionButton.setEnabled( false );
|
||||
mCloneActionButton.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
AliasAction selected = mAliasActionList.getSelectedValue();
|
||||
|
||||
if( selected != null )
|
||||
{
|
||||
AliasAction clone = AliasFactory.copyOf( selected );
|
||||
|
||||
if( clone != null )
|
||||
{
|
||||
addAliasActionToList( clone );
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
add( mCloneActionButton );
|
||||
|
||||
mDeleteActionButton = new JButton( "Delete" );
|
||||
mDeleteActionButton.setToolTipText( "Delete the currently selected action" );
|
||||
mDeleteActionButton.setEnabled( false );
|
||||
mDeleteActionButton.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
final AliasAction selected = mAliasActionList.getSelectedValue();
|
||||
|
||||
if( selected != null )
|
||||
{
|
||||
int choice = JOptionPane.showConfirmDialog( mDeleteActionButton,
|
||||
"Do you want to delete [" + selected.toString() + "]",
|
||||
"Are you sure?", JOptionPane.YES_NO_OPTION );
|
||||
|
||||
if( choice == JOptionPane.YES_OPTION && hasItem() )
|
||||
{
|
||||
Alias alias = getItem();
|
||||
|
||||
alias.removeAliasAction( selected );
|
||||
|
||||
mAliasModel.broadcast( new AliasEvent( alias, Event.CHANGE ) );
|
||||
|
||||
setItem( alias );
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
add( mDeleteActionButton, "wrap" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem( Alias alias )
|
||||
{
|
||||
super.setItem( alias );
|
||||
|
||||
if( alias == null || alias.getAction().isEmpty() )
|
||||
{
|
||||
mAliasActionList.setModel( EMPTY_MODEL );
|
||||
}
|
||||
else
|
||||
{
|
||||
DefaultListModel<AliasAction> model = new DefaultListModel<>();
|
||||
|
||||
List<AliasAction> actions = alias.getAction();
|
||||
|
||||
Collections.sort( actions, new Comparator<AliasAction>()
|
||||
{
|
||||
@Override
|
||||
public int compare( AliasAction o1, AliasAction o2 )
|
||||
{
|
||||
return o1.toString().compareTo( o2.toString() );
|
||||
}
|
||||
} );
|
||||
|
||||
for( AliasAction action: actions )
|
||||
{
|
||||
model.addElement( action );
|
||||
}
|
||||
|
||||
mAliasActionList.setModel( model );
|
||||
}
|
||||
|
||||
mEditorContainer.setAliasAction( null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if( isModified() || mEditorContainer.isModified() )
|
||||
{
|
||||
if( mEditorContainer.isModified() )
|
||||
{
|
||||
mEditorContainer.save();
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
|
||||
if( hasItem() )
|
||||
{
|
||||
mAliasModel.broadcast( new AliasEvent( getItem(), Event.CHANGE ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class EditorContainer extends JPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Editor<AliasAction> mEditor = new EmptyEditor<>();
|
||||
|
||||
public EditorContainer()
|
||||
{
|
||||
setLayout( new MigLayout( "","[grow,fill]", "[grow,fill]" ) );
|
||||
|
||||
add( mEditor );
|
||||
}
|
||||
|
||||
public boolean isModified()
|
||||
{
|
||||
if( mEditor != null )
|
||||
{
|
||||
return mEditor.isModified();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void save()
|
||||
{
|
||||
if( mEditor != null )
|
||||
{
|
||||
mEditor.save();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAliasAction( AliasAction aliasAction )
|
||||
{
|
||||
if( mEditor != null )
|
||||
{
|
||||
if( mEditor.isModified() )
|
||||
{
|
||||
int option = JOptionPane.showConfirmDialog(
|
||||
EditorContainer.this,
|
||||
"Action settings have changed. Do you want to save these changes?",
|
||||
"Save Changes?",
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.QUESTION_MESSAGE );
|
||||
|
||||
if( option == JOptionPane.YES_OPTION )
|
||||
{
|
||||
mEditor.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
removeAll();
|
||||
|
||||
//This will always give us an editor
|
||||
mEditor = AliasFactory.getEditor( aliasAction );
|
||||
|
||||
add( mEditor );
|
||||
|
||||
revalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private void addAliasActionToList( final AliasAction action )
|
||||
{
|
||||
if( action != null && hasItem() )
|
||||
{
|
||||
final Alias alias = getItem();
|
||||
|
||||
alias.addAliasAction( action );
|
||||
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
setItem( alias );
|
||||
|
||||
mAliasActionList.setSelectedValue( action, true );
|
||||
|
||||
AliasActionEditor.this.setModified( true );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
public class AddAliasActionItem extends JMenuItem
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private AliasActionType mAliasActionType;
|
||||
|
||||
public AddAliasActionItem( AliasActionType type )
|
||||
{
|
||||
super( type.toString() );
|
||||
|
||||
mAliasActionType = type;
|
||||
|
||||
addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
final AliasAction action =
|
||||
AliasFactory.getAliasAction( mAliasActionType );
|
||||
|
||||
addAliasActionToList( action );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias.action;
|
||||
|
||||
import io.github.dsheirer.alias.AliasEvent;
|
||||
|
||||
/**
|
||||
* Interface for receiving alias events. Each listener will receive
|
||||
* notification of all alias events.
|
||||
*/
|
||||
public interface AliasActionListener
|
||||
{
|
||||
/**
|
||||
* Notifies the listener that an alias event has occurred
|
||||
*/
|
||||
public void aliasChanged( AliasEvent event );
|
||||
}
|
||||
@@ -1,21 +1,23 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias.action;
|
||||
|
||||
@@ -63,7 +65,7 @@ public class AliasActionManager extends Module implements IMessageListener, List
|
||||
{
|
||||
if(alias != null && alias.hasActions())
|
||||
{
|
||||
for(AliasAction action: alias.getAction())
|
||||
for(AliasAction action: alias.getAliasActions())
|
||||
{
|
||||
action.execute(alias, message);
|
||||
}
|
||||
@@ -76,13 +78,13 @@ public class AliasActionManager extends Module implements IMessageListener, List
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
//No actions neeeded
|
||||
//No actions needed
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
//No actions neeeded
|
||||
//No actions needed
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -91,12 +93,6 @@ public class AliasActionManager extends Module implements IMessageListener, List
|
||||
//No actions neeeded
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
//No actions neeeded
|
||||
}
|
||||
|
||||
/**
|
||||
* IMessageListener interface ... delegates to this class implementation of Listener<Message>
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* 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
|
||||
@@ -15,7 +14,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.alias.action;
|
||||
@@ -26,8 +25,8 @@ import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
|
||||
import javax.swing.JOptionPane;
|
||||
import java.awt.EventQueue;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -61,7 +60,7 @@ public abstract class RecurringAction extends AliasAction
|
||||
break;
|
||||
case UNTIL_DISMISSED:
|
||||
mPerpetualAction = ThreadPool.SCHEDULED.scheduleAtFixedRate(
|
||||
new PerformActionTask(alias, message), mPeriod, mPeriod, TimeUnit.SECONDS);
|
||||
new PerformActionTask(alias, message), 0, mPeriod, TimeUnit.SECONDS);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("<html><div width='250'>Alias [");
|
||||
@@ -92,7 +91,7 @@ public abstract class RecurringAction extends AliasAction
|
||||
*/
|
||||
private void performThreadedAction(final Alias alias, final IMessage message)
|
||||
{
|
||||
ThreadPool.SCHEDULED.schedule(() -> performAction(alias, message), 0, TimeUnit.SECONDS);
|
||||
ThreadPool.CACHED.execute(() -> performAction(alias, message));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -120,6 +119,7 @@ public abstract class RecurringAction extends AliasAction
|
||||
public void setPeriod(int period)
|
||||
{
|
||||
mPeriod = period;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "interval")
|
||||
@@ -131,14 +131,14 @@ public abstract class RecurringAction extends AliasAction
|
||||
public void setInterval(Interval interval)
|
||||
{
|
||||
mInterval = interval;
|
||||
|
||||
mRunning.set(false);
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
public enum Interval
|
||||
{
|
||||
ONCE("Once"),
|
||||
DELAYED_RESET("Once, Reset After Delay"),
|
||||
DELAYED_RESET("Once - Reset After Delay"),
|
||||
UNTIL_DISMISSED("Until Dismissed");
|
||||
|
||||
private String mLabel;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* 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
|
||||
@@ -15,7 +14,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.alias.action.beep;
|
||||
@@ -24,10 +23,43 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.action.AliasActionType;
|
||||
import io.github.dsheirer.alias.action.RecurringAction;
|
||||
import io.github.dsheirer.audio.AudioFormats;
|
||||
import io.github.dsheirer.gui.preference.playback.ToneFrequency;
|
||||
import io.github.dsheirer.gui.preference.playback.ToneUtil;
|
||||
import io.github.dsheirer.gui.preference.playback.ToneVolume;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.Clip;
|
||||
import javax.sound.sampled.DataLine;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
public class BeepAction extends RecurringAction
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(BeepAction.class);
|
||||
private byte[] mToneBytes;
|
||||
|
||||
public BeepAction()
|
||||
{
|
||||
setInterval(Interval.ONCE);
|
||||
float[] tone = ToneUtil.getTone(ToneFrequency.F700, ToneVolume.V10, 1000);
|
||||
|
||||
/* Little-endian byte buffer */
|
||||
ByteBuffer buffer = ByteBuffer.allocate(tone.length * 2).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
ShortBuffer shortBuffer = buffer.asShortBuffer();
|
||||
|
||||
for(float sample : tone)
|
||||
{
|
||||
shortBuffer.put((short) (sample * Short.MAX_VALUE));
|
||||
}
|
||||
|
||||
mToneBytes = buffer.array();
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
@Override
|
||||
@@ -39,12 +71,48 @@ public class BeepAction extends RecurringAction
|
||||
@Override
|
||||
public void performAction(Alias alias, IMessage message )
|
||||
{
|
||||
System.out.println( "\007" );
|
||||
DataLine.Info info = new DataLine.Info(Clip.class, AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO);
|
||||
|
||||
if(!AudioSystem.isLineSupported(info))
|
||||
{
|
||||
mLog.error("Audio clip playback is not supported on this system");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Clip clip = (Clip)AudioSystem.getLine(info);
|
||||
clip.open(AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO, mToneBytes, 0, mToneBytes.length);
|
||||
clip.start();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error attempting to play audio test tone");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "Beep";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Beep");
|
||||
|
||||
if(getInterval() != null)
|
||||
{
|
||||
switch(getInterval())
|
||||
{
|
||||
case ONCE:
|
||||
sb.append(" Once");
|
||||
break;
|
||||
case DELAYED_RESET:
|
||||
sb.append(" Once, Reset After ").append(getPeriod()).append(" Seconds");
|
||||
break;
|
||||
case UNTIL_DISMISSED:
|
||||
sb.append(" Every ").append(getPeriod()).append(" Seconds Until Dismissed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias.action.beep;
|
||||
|
||||
import io.github.dsheirer.alias.action.AliasAction;
|
||||
import io.github.dsheirer.alias.action.RecurringAction;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
public class BeepActionEditor extends DocumentListenerEditor<AliasAction>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String HELP_TEXT =
|
||||
"<html><h3>Beep Action</h3>"
|
||||
+ "This action will sound a beep according to the interval<br>"
|
||||
+ "that you select:<br><br>"
|
||||
+ "<b>Once:</b> Beep the first time the alias is active and<br>"
|
||||
+ "never again<br><br>"
|
||||
+ "<b>Once, Reset After Delay:</b> Beep once and suppress<br>"
|
||||
+ "subsequent beeps for the specified period in seconds.<br>"
|
||||
+ "After the reset period, beep again when the alias is active.<br><br>"
|
||||
+ "<b>Until Dismissed:</b> Beep every period seconds until you<br>"
|
||||
+ "click OK on the dialog that appears. Alerting is suppressed<br>"
|
||||
+ "for 15 seconds after you click OK.<br>"
|
||||
+ "</html>";
|
||||
|
||||
private JComboBox<RecurringAction.Interval> mComboInterval;
|
||||
private JSlider mPeriodSlider;
|
||||
private JLabel mPeriodSliderLabel;
|
||||
|
||||
public BeepActionEditor( AliasAction aliasAction )
|
||||
{
|
||||
init();
|
||||
|
||||
setItem( aliasAction );
|
||||
}
|
||||
|
||||
public BeepAction getBeepAction()
|
||||
{
|
||||
if( getItem() instanceof BeepAction )
|
||||
{
|
||||
return (BeepAction)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2", "[right][left]", "[][][]" ) );
|
||||
|
||||
add( new JLabel( "Beep Interval:" ) );
|
||||
|
||||
mComboInterval = new JComboBox<RecurringAction.Interval>( RecurringAction.Interval.values() );
|
||||
mComboInterval.setToolTipText( HELP_TEXT );
|
||||
mComboInterval.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
RecurringAction.Interval selected = mComboInterval
|
||||
.getItemAt( mComboInterval.getSelectedIndex() );
|
||||
|
||||
if( selected != null )
|
||||
{
|
||||
/* Enable/disable period spinner based on selection */
|
||||
boolean enabled = selected != RecurringAction.Interval.ONCE;
|
||||
|
||||
mPeriodSlider.setEnabled( enabled );
|
||||
mPeriodSliderLabel.setEnabled( enabled );
|
||||
}
|
||||
|
||||
setModified( true );
|
||||
}
|
||||
});
|
||||
add( mComboInterval, "wrap" );
|
||||
|
||||
final SpinnerModel model = new SpinnerNumberModel( 1, 1, 30, 1 );
|
||||
model.addChangeListener( new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged( ChangeEvent e )
|
||||
{
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
|
||||
mPeriodSlider = new JSlider( JSlider.HORIZONTAL, 1, 60, 1 );
|
||||
mPeriodSlider.setMajorTickSpacing( 10 );
|
||||
mPeriodSlider.setMinorTickSpacing( 2 );
|
||||
mPeriodSlider.setPaintTicks( true );
|
||||
mPeriodSlider.setLabelTable( mPeriodSlider.createStandardLabels( 10, 10 ) );
|
||||
mPeriodSlider.setPaintLabels( true );
|
||||
mPeriodSlider.addChangeListener( new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged( ChangeEvent e )
|
||||
{
|
||||
mPeriodSliderLabel.setText( "Period: " + mPeriodSlider.getValue() );
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
mPeriodSlider.setToolTipText( HELP_TEXT );
|
||||
|
||||
mPeriodSliderLabel = new JLabel( "Period: " + mPeriodSlider.getValue() + " " );
|
||||
add( mPeriodSliderLabel );
|
||||
add( mPeriodSlider, "wrap,grow" );
|
||||
|
||||
JLabel help = new JLabel( "Help ..." );
|
||||
help.setForeground( Color.BLUE.brighter() );
|
||||
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
|
||||
help.addMouseListener( new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e )
|
||||
{
|
||||
JOptionPane.showMessageDialog( BeepActionEditor.this,
|
||||
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
|
||||
}
|
||||
} );
|
||||
add( help, "align left" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem( AliasAction item )
|
||||
{
|
||||
super.setItem( item );
|
||||
|
||||
if( hasItem() )
|
||||
{
|
||||
BeepAction beep = getBeepAction();
|
||||
|
||||
RecurringAction.Interval interval = beep.getInterval();
|
||||
|
||||
mComboInterval.setSelectedItem( interval );
|
||||
|
||||
boolean enabled = interval != RecurringAction.Interval.ONCE;
|
||||
|
||||
mPeriodSliderLabel.setEnabled( enabled );
|
||||
mPeriodSlider.setEnabled( enabled );
|
||||
mPeriodSlider.setValue( beep.getPeriod() );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if( hasItem() && isModified() )
|
||||
{
|
||||
BeepAction beep = getBeepAction();
|
||||
|
||||
beep.setInterval( (RecurringAction.Interval)mComboInterval.getSelectedItem() );
|
||||
beep.setPeriod( mPeriodSlider.getValue() );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ public class ClipAction extends RecurringAction
|
||||
|
||||
public ClipAction()
|
||||
{
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
@@ -62,6 +63,7 @@ public class ClipAction extends RecurringAction
|
||||
public void setPath(String path)
|
||||
{
|
||||
mFilePath = path;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,8 +89,7 @@ public class ClipAction extends RecurringAction
|
||||
{
|
||||
mClip = AudioSystem.getClip();
|
||||
|
||||
AudioInputStream ais =
|
||||
AudioSystem.getAudioInputStream(new File(mFilePath));
|
||||
AudioInputStream ais = AudioSystem.getAudioInputStream(new File(mFilePath));
|
||||
|
||||
mClip.open(ais);
|
||||
}
|
||||
@@ -113,17 +114,33 @@ public class ClipAction extends RecurringAction
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
if(mFilePath == null)
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Play Audio Clip");
|
||||
|
||||
if(getInterval() != null)
|
||||
{
|
||||
return "Play Clip";
|
||||
switch(getInterval())
|
||||
{
|
||||
case ONCE:
|
||||
sb.append(" Once");
|
||||
break;
|
||||
case DELAYED_RESET:
|
||||
sb.append(" Once, Reset After ").append(getPeriod()).append(" Seconds");
|
||||
break;
|
||||
case UNTIL_DISMISSED:
|
||||
sb.append(" Every ").append(getPeriod()).append(" Seconds Until Dismissed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if(getPath() == null)
|
||||
{
|
||||
return "Play Clip: " + mFilePath;
|
||||
sb.append(" - (audio file empty)");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,285 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias.action.clip;
|
||||
|
||||
import io.github.dsheirer.alias.action.AliasAction;
|
||||
import io.github.dsheirer.alias.action.RecurringAction;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.File;
|
||||
|
||||
public class ClipActionEditor extends DocumentListenerEditor<AliasAction>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String PLEASE_SELECT_A_FILE = "Please select a file";
|
||||
private static final String HELP_TEXT =
|
||||
"<html><h3>Clip Action</h3>"
|
||||
+ "This action will play an audio clip according to the interval<br>"
|
||||
+ "that you select:<br><br>"
|
||||
+ "<b>Once:</b> play the first time the alias is active and<br>"
|
||||
+ "never again<br><br>"
|
||||
+ "<b>Once, Reset After Delay:</b> play once and suppress<br>"
|
||||
+ "subsequent runs for the specified period in seconds.<br>"
|
||||
+ "After the reset period, play again when the alias is active.<br><br>"
|
||||
+ "<b>Until Dismissed:</b> play every period seconds until you<br>"
|
||||
+ "click OK on the dialog that appears. Alerting is suppressed<br>"
|
||||
+ "for 15 seconds after you click OK.<br>"
|
||||
+ "</html>";
|
||||
|
||||
private JComboBox<RecurringAction.Interval> mComboInterval;
|
||||
private JSlider mPeriodSlider;
|
||||
private JLabel mPeriodSliderLabel;
|
||||
private JTextField mTextFilePath;
|
||||
|
||||
public ClipActionEditor( AliasAction aliasAction )
|
||||
{
|
||||
init();
|
||||
|
||||
setItem( aliasAction );
|
||||
}
|
||||
|
||||
public ClipAction getClipAction()
|
||||
{
|
||||
if( getItem() instanceof ClipAction )
|
||||
{
|
||||
return (ClipAction)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2", "[right][left]", "[][][][][]" ) );
|
||||
|
||||
add( new JLabel( "Play Interval:" ) );
|
||||
|
||||
mComboInterval = new JComboBox<RecurringAction.Interval>( RecurringAction.Interval.values() );
|
||||
mComboInterval.setToolTipText( HELP_TEXT );
|
||||
mComboInterval.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
RecurringAction.Interval selected = mComboInterval
|
||||
.getItemAt( mComboInterval.getSelectedIndex() );
|
||||
|
||||
if( selected != null )
|
||||
{
|
||||
/* Enable/disable period slider based on selection */
|
||||
boolean enabled = selected != RecurringAction.Interval.ONCE;
|
||||
|
||||
mPeriodSlider.setEnabled( enabled );
|
||||
mPeriodSliderLabel.setEnabled( enabled );
|
||||
}
|
||||
|
||||
setModified( true );
|
||||
}
|
||||
});
|
||||
add( mComboInterval );
|
||||
|
||||
final SpinnerModel model = new SpinnerNumberModel( 1, 1, 30, 1 );
|
||||
model.addChangeListener( new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged( ChangeEvent e )
|
||||
{
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
|
||||
mPeriodSlider = new JSlider( JSlider.HORIZONTAL, 1, 60, 1 );
|
||||
mPeriodSlider.setMajorTickSpacing( 10 );
|
||||
mPeriodSlider.setMinorTickSpacing( 2 );
|
||||
mPeriodSlider.setPaintTicks( true );
|
||||
mPeriodSlider.setLabelTable( mPeriodSlider.createStandardLabels( 10, 10 ) );
|
||||
mPeriodSlider.setPaintLabels( true );
|
||||
mPeriodSlider.addChangeListener( new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged( ChangeEvent e )
|
||||
{
|
||||
mPeriodSliderLabel.setText( "Period: " + mPeriodSlider.getValue() );
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
mPeriodSlider.setToolTipText( HELP_TEXT );
|
||||
|
||||
mPeriodSliderLabel = new JLabel( "Period: " + mPeriodSlider.getValue() + " " );
|
||||
add( mPeriodSliderLabel );
|
||||
add( mPeriodSlider, "wrap,grow" );
|
||||
|
||||
mTextFilePath = new JTextField( PLEASE_SELECT_A_FILE );
|
||||
add( mTextFilePath, "growx,span" );
|
||||
|
||||
JButton fileButton = new JButton( "File" );
|
||||
|
||||
fileButton.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
final JFileChooser chooser = new JFileChooser();
|
||||
|
||||
int value = chooser.showOpenDialog( ClipActionEditor.this );
|
||||
|
||||
if( value == JFileChooser.APPROVE_OPTION )
|
||||
{
|
||||
File file = chooser.getSelectedFile();
|
||||
|
||||
mTextFilePath.setText( file.getAbsolutePath() );
|
||||
|
||||
setModified( true );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
add( fileButton, "grow" );
|
||||
|
||||
JButton testButton = new JButton( "Test" );
|
||||
|
||||
testButton.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
if( hasItem() )
|
||||
{
|
||||
String path = mTextFilePath.getText();
|
||||
|
||||
if( path == null || path.trim().isEmpty() )
|
||||
{
|
||||
JOptionPane.showMessageDialog( ClipActionEditor.this,
|
||||
"Please select an audio file", PLEASE_SELECT_A_FILE,
|
||||
JOptionPane.ERROR_MESSAGE );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( isModified() )
|
||||
{
|
||||
int option = JOptionPane.showConfirmDialog(
|
||||
ClipActionEditor.this,
|
||||
"Settings have changed. Do you want to save these changes?",
|
||||
"Save Changes?",
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.QUESTION_MESSAGE );
|
||||
|
||||
if( option == JOptionPane.YES_OPTION )
|
||||
{
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
new Thread( new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
getClipAction().play();
|
||||
}
|
||||
catch( Exception e1 )
|
||||
{
|
||||
JOptionPane.showMessageDialog( ClipActionEditor.this,
|
||||
"Couldn't play audio clip [" + e1.getMessage() + "]",
|
||||
"Error", JOptionPane.ERROR_MESSAGE );
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
add( testButton, "grow,wrap" );
|
||||
|
||||
JLabel help = new JLabel( "Help ..." );
|
||||
help.setForeground( Color.BLUE.brighter() );
|
||||
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
|
||||
help.addMouseListener( new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e )
|
||||
{
|
||||
JOptionPane.showMessageDialog( ClipActionEditor.this,
|
||||
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
|
||||
}
|
||||
} );
|
||||
add( help, "align left" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem( AliasAction item )
|
||||
{
|
||||
super.setItem( item );
|
||||
|
||||
if( hasItem() )
|
||||
{
|
||||
ClipAction clip = getClipAction();
|
||||
|
||||
RecurringAction.Interval interval = clip.getInterval();
|
||||
|
||||
mComboInterval.setSelectedItem( interval );
|
||||
|
||||
boolean enabled = interval != RecurringAction.Interval.ONCE;
|
||||
|
||||
mPeriodSliderLabel.setEnabled( enabled );
|
||||
mPeriodSlider.setEnabled( enabled );
|
||||
mPeriodSlider.setValue( clip.getPeriod() );
|
||||
|
||||
String filepath = clip.getPath();
|
||||
|
||||
if( filepath != null && !filepath.isEmpty() )
|
||||
{
|
||||
mTextFilePath.setText( filepath );
|
||||
}
|
||||
else
|
||||
{
|
||||
mTextFilePath.setText( PLEASE_SELECT_A_FILE );
|
||||
}
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if( hasItem() && isModified() )
|
||||
{
|
||||
ClipAction script = getClipAction();
|
||||
|
||||
script.setInterval( (RecurringAction.Interval)mComboInterval.getSelectedItem() );
|
||||
script.setPeriod( mPeriodSlider.getValue() );
|
||||
script.setPath( mTextFilePath.getText() );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ public class ScriptAction extends RecurringAction
|
||||
|
||||
public ScriptAction()
|
||||
{
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
@@ -56,6 +57,7 @@ public class ScriptAction extends RecurringAction
|
||||
public void setScript(String script)
|
||||
{
|
||||
mScript = script;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -104,13 +106,30 @@ public class ScriptAction extends RecurringAction
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
if(mScript == null)
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Run Script");
|
||||
|
||||
if(getInterval() != null)
|
||||
{
|
||||
return "Run Script";
|
||||
switch(getInterval())
|
||||
{
|
||||
case ONCE:
|
||||
sb.append(" Once");
|
||||
break;
|
||||
case DELAYED_RESET:
|
||||
sb.append(" Once, Reset After ").append(getPeriod()).append(" Seconds");
|
||||
break;
|
||||
case UNTIL_DISMISSED:
|
||||
sb.append(" Every ").append(getPeriod()).append(" Seconds Until Dismissed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if(getScript() == null)
|
||||
{
|
||||
return "Run Script: " + mScript;
|
||||
sb.append(" - (script file empty)");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias.action.script;
|
||||
|
||||
import io.github.dsheirer.alias.action.AliasAction;
|
||||
import io.github.dsheirer.alias.action.RecurringAction;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.File;
|
||||
|
||||
public class ScriptActionEditor extends DocumentListenerEditor<AliasAction>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String PLEASE_SELECT_A_FILE = "Please select a file";
|
||||
private static final String HELP_TEXT =
|
||||
"<html><h3>Script Action</h3>"
|
||||
+ "This action will run a script according to the interval<br>"
|
||||
+ "that you select:<br><br>"
|
||||
+ "<b>Once:</b> run the first time the alias is active and<br>"
|
||||
+ "never again<br><br>"
|
||||
+ "<b>Once, Reset After Delay:</b> run once and suppress<br>"
|
||||
+ "subsequent runs for the specified period in seconds.<br>"
|
||||
+ "After the reset period, run again when the alias is active.<br><br>"
|
||||
+ "<b>Until Dismissed:</b> run every period seconds until you<br>"
|
||||
+ "click OK on the dialog that appears. Alerting is suppressed<br>"
|
||||
+ "for 15 seconds after you click OK.<br>"
|
||||
+ "</html>";
|
||||
|
||||
private JComboBox<RecurringAction.Interval> mComboInterval;
|
||||
private JSlider mPeriodSlider;
|
||||
private JLabel mPeriodSliderLabel;
|
||||
private JTextField mTextFilePath;
|
||||
|
||||
public ScriptActionEditor( AliasAction aliasAction )
|
||||
{
|
||||
init();
|
||||
|
||||
setItem( aliasAction );
|
||||
}
|
||||
|
||||
public ScriptAction getScriptAction()
|
||||
{
|
||||
if( getItem() instanceof ScriptAction )
|
||||
{
|
||||
return (ScriptAction)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2", "[right][left]", "[][][][][]" ) );
|
||||
|
||||
add( new JLabel( "Run Interval:" ) );
|
||||
|
||||
mComboInterval = new JComboBox<RecurringAction.Interval>( RecurringAction.Interval.values() );
|
||||
mComboInterval.setToolTipText( HELP_TEXT );
|
||||
mComboInterval.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
RecurringAction.Interval selected = mComboInterval
|
||||
.getItemAt( mComboInterval.getSelectedIndex() );
|
||||
|
||||
if( selected != null )
|
||||
{
|
||||
/* Enable/disable period slider based on selection */
|
||||
boolean enabled = selected != RecurringAction.Interval.ONCE;
|
||||
|
||||
mPeriodSlider.setEnabled( enabled );
|
||||
mPeriodSliderLabel.setEnabled( enabled );
|
||||
}
|
||||
|
||||
setModified( true );
|
||||
}
|
||||
});
|
||||
add( mComboInterval );
|
||||
|
||||
final SpinnerModel model = new SpinnerNumberModel( 1, 1, 30, 1 );
|
||||
model.addChangeListener( new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged( ChangeEvent e )
|
||||
{
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
|
||||
mPeriodSlider = new JSlider( JSlider.HORIZONTAL, 1, 60, 1 );
|
||||
mPeriodSlider.setMajorTickSpacing( 10 );
|
||||
mPeriodSlider.setMinorTickSpacing( 2 );
|
||||
mPeriodSlider.setPaintTicks( true );
|
||||
mPeriodSlider.setLabelTable( mPeriodSlider.createStandardLabels( 10, 10 ) );
|
||||
mPeriodSlider.setPaintLabels( true );
|
||||
mPeriodSlider.addChangeListener( new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged( ChangeEvent e )
|
||||
{
|
||||
mPeriodSliderLabel.setText( "Period: " + mPeriodSlider.getValue() );
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
mPeriodSlider.setToolTipText( HELP_TEXT );
|
||||
|
||||
mPeriodSliderLabel = new JLabel( "Period: " + mPeriodSlider.getValue() + " " );
|
||||
add( mPeriodSliderLabel );
|
||||
add( mPeriodSlider, "wrap,grow" );
|
||||
|
||||
mTextFilePath = new JTextField( PLEASE_SELECT_A_FILE );
|
||||
add( mTextFilePath, "growx,span" );
|
||||
|
||||
JButton fileButton = new JButton( "File" );
|
||||
|
||||
fileButton.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
final JFileChooser chooser = new JFileChooser();
|
||||
|
||||
int value = chooser.showOpenDialog( ScriptActionEditor.this );
|
||||
|
||||
if( value == JFileChooser.APPROVE_OPTION )
|
||||
{
|
||||
File file = chooser.getSelectedFile();
|
||||
|
||||
mTextFilePath.setText( file.getAbsolutePath() );
|
||||
|
||||
setModified( true );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
add( fileButton, "grow" );
|
||||
|
||||
JButton testButton = new JButton( "Test" );
|
||||
testButton.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent actionEvent )
|
||||
{
|
||||
String script = mTextFilePath.getText();
|
||||
|
||||
if( script == null || script.trim().isEmpty() )
|
||||
{
|
||||
JOptionPane.showMessageDialog( ScriptActionEditor.this,
|
||||
"Please select a script file", PLEASE_SELECT_A_FILE,
|
||||
JOptionPane.ERROR_MESSAGE );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( isModified() )
|
||||
{
|
||||
int option = JOptionPane.showConfirmDialog(
|
||||
ScriptActionEditor.this,
|
||||
"Settings have changed. Do you want to save these changes?",
|
||||
"Save Changes?",
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.QUESTION_MESSAGE );
|
||||
|
||||
if( option == JOptionPane.YES_OPTION )
|
||||
{
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
new Thread( new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
getScriptAction().play();
|
||||
}
|
||||
catch( Exception e1 )
|
||||
{
|
||||
JOptionPane.showMessageDialog( ScriptActionEditor.this,
|
||||
e1.getMessage(), "Error", JOptionPane.ERROR_MESSAGE );
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
}
|
||||
} );
|
||||
|
||||
add( testButton, "grow,wrap" );
|
||||
|
||||
JLabel help = new JLabel( "Help ..." );
|
||||
help.setForeground( Color.BLUE.brighter() );
|
||||
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
|
||||
help.addMouseListener( new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e )
|
||||
{
|
||||
JOptionPane.showMessageDialog( ScriptActionEditor.this,
|
||||
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
|
||||
}
|
||||
} );
|
||||
add( help, "align left" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem( AliasAction item )
|
||||
{
|
||||
super.setItem( item );
|
||||
|
||||
if( hasItem() )
|
||||
{
|
||||
ScriptAction script = getScriptAction();
|
||||
|
||||
RecurringAction.Interval interval = script.getInterval();
|
||||
|
||||
mComboInterval.setSelectedItem( interval );
|
||||
|
||||
boolean enabled = interval != RecurringAction.Interval.ONCE;
|
||||
|
||||
mPeriodSliderLabel.setEnabled( enabled );
|
||||
mPeriodSlider.setEnabled( enabled );
|
||||
mPeriodSlider.setValue( script.getPeriod() );
|
||||
|
||||
String filepath = script.getScript();
|
||||
|
||||
if( filepath != null && !filepath.isEmpty() )
|
||||
{
|
||||
mTextFilePath.setText( filepath );
|
||||
}
|
||||
else
|
||||
{
|
||||
mTextFilePath.setText( PLEASE_SELECT_A_FILE );
|
||||
}
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if( hasItem() && isModified() )
|
||||
{
|
||||
ScriptAction script = getScriptAction();
|
||||
|
||||
script.setInterval( (RecurringAction.Interval)mComboInterval.getSelectedItem() );
|
||||
script.setPeriod( mPeriodSlider.getValue() );
|
||||
script.setScript( mTextFilePath.getText() );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
}
|
||||
@@ -40,43 +40,126 @@ import io.github.dsheirer.alias.id.priority.Priority;
|
||||
import io.github.dsheirer.alias.id.radio.Radio;
|
||||
import io.github.dsheirer.alias.id.radio.RadioRange;
|
||||
import io.github.dsheirer.alias.id.record.Record;
|
||||
import io.github.dsheirer.alias.id.status.StatusID;
|
||||
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
||||
import io.github.dsheirer.alias.id.status.UserStatusID;
|
||||
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||
import io.github.dsheirer.alias.id.tone.TonesID;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.util.Callback;
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = BroadcastChannel.class, name = "broadcastChannel"),
|
||||
@JsonSubTypes.Type(value = Esn.class, name = "esn"),
|
||||
@JsonSubTypes.Type(value = FleetsyncID.class, name = "fleetsyncID"),
|
||||
@JsonSubTypes.Type(value = LegacyTalkgroupID.class, name = "talkgroupID"),
|
||||
@JsonSubTypes.Type(value = LoJackFunctionAndID.class, name = "loJackFunctionAndID"),
|
||||
@JsonSubTypes.Type(value = MDC1200ID.class, name = "mdc1200ID"),
|
||||
@JsonSubTypes.Type(value = Min.class, name = "min"),
|
||||
@JsonSubTypes.Type(value = MPT1327ID.class, name = "mpt1327ID"),
|
||||
@JsonSubTypes.Type(value = NonRecordable.class, name = "nonRecordable"),
|
||||
@JsonSubTypes.Type(value = Priority.class, name = "priority"),
|
||||
@JsonSubTypes.Type(value = Radio.class, name = "radio"),
|
||||
@JsonSubTypes.Type(value = RadioRange.class, name = "radioRange"),
|
||||
@JsonSubTypes.Type(value = Talkgroup.class, name = "talkgroup"),
|
||||
@JsonSubTypes.Type(value = TalkgroupRange.class, name = "talkgroupRange"),
|
||||
@JsonSubTypes.Type(value = Priority.class, name = "priority"),
|
||||
@JsonSubTypes.Type(value = Record.class, name = "record"),
|
||||
@JsonSubTypes.Type(value = SiteID.class, name = "siteID"),
|
||||
@JsonSubTypes.Type(value = StatusID.class, name = "statusID"),
|
||||
@JsonSubTypes.Type(value = LegacyTalkgroupID.class, name = "talkgroupID"),
|
||||
@JsonSubTypes.Type(value = Talkgroup.class, name = "talkgroup"),
|
||||
@JsonSubTypes.Type(value = TalkgroupRange.class, name = "talkgroupRange"),
|
||||
@JsonSubTypes.Type(value = TonesID.class, name = "tones"),
|
||||
@JsonSubTypes.Type(value = UserStatusID.class, name = "statusID"),
|
||||
@JsonSubTypes.Type(value = UnitStatusID.class, name = "unitStatusID"),
|
||||
@JsonSubTypes.Type(value = UniqueID.class, name = "uniqueID")
|
||||
})
|
||||
@JacksonXmlRootElement(localName = "id")
|
||||
public abstract class AliasID
|
||||
{
|
||||
private SimpleStringProperty mValueProperty = new SimpleStringProperty();
|
||||
private BooleanProperty mOverlapProperty = new SimpleBooleanProperty();
|
||||
|
||||
public AliasID()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* String property representation of this alias ID
|
||||
*/
|
||||
@JsonIgnore
|
||||
public SimpleStringProperty valueProperty()
|
||||
{
|
||||
return mValueProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this alias identifier overlaps with any other alias identifier in the same alias list
|
||||
*/
|
||||
@JsonIgnore
|
||||
public BooleanProperty overlapProperty()
|
||||
{
|
||||
return mOverlapProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the overlap flag for this alias id. Overlap indicates that this identifier collides with another
|
||||
* identifier within the same alias list.
|
||||
*/
|
||||
public void setOverlap(boolean overlap)
|
||||
{
|
||||
mOverlapProperty.set(overlap);
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the value property for this alias ID. Note: this method is intended to be invoked by all subclasses
|
||||
* each time that any of the subclass member variable values change.
|
||||
*/
|
||||
public void updateValueProperty()
|
||||
{
|
||||
if(overlapProperty().get())
|
||||
{
|
||||
valueProperty().set(toString() + " - Error: Overlap");
|
||||
}
|
||||
else
|
||||
{
|
||||
valueProperty().set(toString());
|
||||
}
|
||||
|
||||
//Hack: harmless, but the list extractor does not consistently update unless we do this.
|
||||
valueProperty().get();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public abstract AliasIDType getType();
|
||||
|
||||
public abstract boolean matches(AliasID id);
|
||||
|
||||
/**
|
||||
* Indicates if this alias ID overlaps with another alias ID.
|
||||
* Note: this method is intended to be overridden by certain subclass implementations.
|
||||
*/
|
||||
@JsonIgnore
|
||||
public boolean overlaps(AliasID id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public abstract boolean isValid();
|
||||
|
||||
/**
|
||||
* Indicates if the identifier is an audio identifier (monitor, record, stream)
|
||||
*/
|
||||
@JsonIgnore
|
||||
public abstract boolean isAudioIdentifier();
|
||||
|
||||
/**
|
||||
* Creates an observable property extractor for use with observable lists to detect changes internal to this object.
|
||||
*/
|
||||
public static Callback<AliasID,Observable[]> extractor()
|
||||
{
|
||||
return (AliasID aid) -> new Observable[] {aid.valueProperty(), aid.overlapProperty()};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
*/
|
||||
package io.github.dsheirer.alias.id;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
public enum AliasIDType
|
||||
{
|
||||
BROADCAST_CHANNEL("Audio Broadcast Channel"),
|
||||
@@ -34,7 +36,9 @@ public enum AliasIDType
|
||||
RADIO_ID_RANGE("Radio ID Range"),
|
||||
RECORD("Record"),
|
||||
SITE("Site"),
|
||||
STATUS("Status"),
|
||||
STATUS("User Status"),
|
||||
TONES("Tone Sequence"),
|
||||
UNIT_STATUS("Unit Status"),
|
||||
TALKGROUP("Talkgroup"),
|
||||
TALKGROUP_RANGE("Talkgroup Range"),
|
||||
|
||||
@@ -52,6 +56,10 @@ public enum AliasIDType
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
//Values used by the View-By alias editor
|
||||
public static EnumSet<AliasIDType> VIEW_BY_VALUES = EnumSet.of(TALKGROUP, TALKGROUP_RANGE, RADIO_ID, RADIO_ID_RANGE,
|
||||
UNIT_STATUS, STATUS, TONES);
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
|
||||
@@ -1,377 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.alias.id;
|
||||
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasEvent;
|
||||
import io.github.dsheirer.alias.AliasFactory;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastModel;
|
||||
import io.github.dsheirer.gui.editor.Editor;
|
||||
import io.github.dsheirer.gui.editor.EmptyEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.DefaultListModel;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.ListModel;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class AliasIdentifierEditor extends Editor<Alias>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static ListModel<AliasID> EMPTY_MODEL = new DefaultListModel<>();
|
||||
private JList<AliasID> mAliasIDList = new JList<>(EMPTY_MODEL);
|
||||
private JButton mNewIDButton;
|
||||
private JButton mCloneIDButton;
|
||||
private JButton mDeleteIDButton;
|
||||
private EditorContainer mEditorContainer = new EditorContainer();
|
||||
|
||||
private AliasModel mAliasModel;
|
||||
private BroadcastModel mBroadcastModel;
|
||||
|
||||
public AliasIdentifierEditor(AliasModel aliasModel, BroadcastModel broadcastModel)
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
mBroadcastModel = broadcastModel;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 3",
|
||||
"[grow,fill][grow,fill][grow,fill]", "[][grow,fill][]"));
|
||||
|
||||
mAliasIDList.setVisibleRowCount(6);
|
||||
mAliasIDList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
mAliasIDList.setLayoutOrientation(JList.VERTICAL);
|
||||
mAliasIDList.addListSelectionListener(new ListSelectionListener()
|
||||
{
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent event)
|
||||
{
|
||||
if(!event.getValueIsAdjusting())
|
||||
{
|
||||
JList<?> list = (JList<?>)event.getSource();
|
||||
|
||||
Object selectedItem = list.getSelectedValue();
|
||||
|
||||
if(selectedItem != null && selectedItem instanceof AliasID)
|
||||
{
|
||||
AliasID selected = (AliasID)selectedItem;
|
||||
|
||||
mEditorContainer.setAliasID(selected);
|
||||
|
||||
mCloneIDButton.setEnabled(true);
|
||||
mDeleteIDButton.setEnabled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
mCloneIDButton.setEnabled(false);
|
||||
mDeleteIDButton.setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
JScrollPane scroller = new JScrollPane(mAliasIDList);
|
||||
add(scroller, "span,grow");
|
||||
|
||||
add(mEditorContainer, "span,grow");
|
||||
|
||||
mNewIDButton = new JButton("New ...");
|
||||
mNewIDButton.setToolTipText("Create a new Alias Identifier");
|
||||
mNewIDButton.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
|
||||
menu.add(new AddAliasIdentifierItem(AliasIDType.TALKGROUP));
|
||||
menu.add(new AddAliasIdentifierItem(AliasIDType.TALKGROUP_RANGE));
|
||||
menu.add(new AddAliasIdentifierItem(AliasIDType.RADIO_ID));
|
||||
menu.add(new AddAliasIdentifierItem(AliasIDType.RADIO_ID_RANGE));
|
||||
menu.add(new AddAliasIdentifierItem(AliasIDType.ESN));
|
||||
// menu.add(new AddAliasIdentifierItem(AliasIDType.LOJACK));
|
||||
menu.add(new AddAliasIdentifierItem(AliasIDType.STATUS));
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
menu.add(new AddAliasIdentifierItem(AliasIDType.BROADCAST_CHANNEL));
|
||||
|
||||
if(hasItem())
|
||||
{
|
||||
Alias alias = getItem();
|
||||
|
||||
if(!alias.isRecordable())
|
||||
{
|
||||
menu.add(new AddAliasIdentifierItem(AliasIDType.RECORD));
|
||||
}
|
||||
|
||||
if(!alias.hasCallPriority())
|
||||
{
|
||||
menu.add(new AddAliasIdentifierItem(AliasIDType.PRIORITY));
|
||||
}
|
||||
}
|
||||
|
||||
menu.show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
});
|
||||
|
||||
add(mNewIDButton);
|
||||
|
||||
mCloneIDButton = new JButton("Clone");
|
||||
mCloneIDButton.setToolTipText("Create a copy of the currently selected identifier");
|
||||
mCloneIDButton.setEnabled(false);
|
||||
mCloneIDButton.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
AliasID selected = mAliasIDList.getSelectedValue();
|
||||
|
||||
if(selected != null)
|
||||
{
|
||||
AliasID clone = AliasFactory.copyOf(selected);
|
||||
|
||||
if(clone != null)
|
||||
{
|
||||
addAliasIDToList(clone);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
add(mCloneIDButton);
|
||||
|
||||
mDeleteIDButton = new JButton("Delete");
|
||||
mDeleteIDButton.setToolTipText("Delete the currently selected identifier");
|
||||
mDeleteIDButton.setEnabled(false);
|
||||
mDeleteIDButton.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
final AliasID selected = mAliasIDList.getSelectedValue();
|
||||
|
||||
if(selected != null)
|
||||
{
|
||||
int choice = JOptionPane.showConfirmDialog(mDeleteIDButton,
|
||||
"Do you want to delete [" + selected.toString() + "]",
|
||||
"Are you sure?", JOptionPane.YES_NO_OPTION);
|
||||
|
||||
if(choice == JOptionPane.YES_OPTION && hasItem())
|
||||
{
|
||||
Alias alias = getItem();
|
||||
|
||||
alias.removeAliasID(selected);
|
||||
|
||||
mAliasModel.broadcast(new AliasEvent(alias, AliasEvent.Event.CHANGE));
|
||||
|
||||
setItem(alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
add(mDeleteIDButton, "wrap");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(Alias alias)
|
||||
{
|
||||
super.setItem(alias);
|
||||
|
||||
if(alias == null || alias.getId().isEmpty())
|
||||
{
|
||||
mAliasIDList.setModel(EMPTY_MODEL);
|
||||
}
|
||||
else
|
||||
{
|
||||
DefaultListModel<AliasID> model = new DefaultListModel<AliasID>();
|
||||
|
||||
List<AliasID> ids = alias.getId();
|
||||
|
||||
Collections.sort(ids, new Comparator<AliasID>()
|
||||
{
|
||||
@Override
|
||||
public int compare(AliasID o1, AliasID o2)
|
||||
{
|
||||
return o1.toString().compareTo(o2.toString());
|
||||
}
|
||||
});
|
||||
|
||||
for(AliasID id : ids)
|
||||
{
|
||||
model.addElement(id);
|
||||
}
|
||||
|
||||
mAliasIDList.setModel(model);
|
||||
}
|
||||
|
||||
mEditorContainer.setAliasID(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if(isModified() || mEditorContainer.isModified())
|
||||
{
|
||||
if(mEditorContainer.isModified())
|
||||
{
|
||||
mEditorContainer.save();
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
|
||||
if(hasItem())
|
||||
{
|
||||
mAliasModel.broadcast(new AliasEvent(getItem(), AliasEvent.Event.CHANGE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class EditorContainer extends JPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Editor<AliasID> mEditor = new EmptyEditor<>();
|
||||
|
||||
public EditorContainer()
|
||||
{
|
||||
setLayout(new MigLayout("", "[grow,fill]", "[grow,fill]"));
|
||||
|
||||
add(mEditor);
|
||||
}
|
||||
|
||||
public boolean isModified()
|
||||
{
|
||||
if(mEditor != null)
|
||||
{
|
||||
return mEditor.isModified();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void save()
|
||||
{
|
||||
if(mEditor != null)
|
||||
{
|
||||
mEditor.save();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAliasID(AliasID aliasID)
|
||||
{
|
||||
if(mEditor != null)
|
||||
{
|
||||
if(mEditor.isModified())
|
||||
{
|
||||
int option = JOptionPane.showConfirmDialog(
|
||||
EditorContainer.this,
|
||||
"Identifier settings have changed. Do you want to save these changes?",
|
||||
"Save Changes?",
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.QUESTION_MESSAGE);
|
||||
|
||||
if(option == JOptionPane.YES_OPTION)
|
||||
{
|
||||
mEditor.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
removeAll();
|
||||
|
||||
//This will always give us an editor
|
||||
mEditor = AliasFactory.getEditor(aliasID, mBroadcastModel);
|
||||
|
||||
add(mEditor);
|
||||
|
||||
revalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private void addAliasIDToList(final AliasID id)
|
||||
{
|
||||
if(id != null && hasItem())
|
||||
{
|
||||
final Alias alias = getItem();
|
||||
|
||||
alias.addAliasID(id);
|
||||
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
setItem(alias);
|
||||
|
||||
mAliasIDList.setSelectedValue(id, true);
|
||||
|
||||
AliasIdentifierEditor.this.setModified(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class AddAliasIdentifierItem extends JMenuItem
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private AliasIDType mAliasIDType;
|
||||
|
||||
public AddAliasIdentifierItem(AliasIDType type)
|
||||
{
|
||||
super(type.toString());
|
||||
|
||||
mAliasIDType = type;
|
||||
|
||||
addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
final AliasID id = AliasFactory.getAliasID(mAliasIDType);
|
||||
|
||||
addAliasIDToList(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias.id;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class WildcardID implements Comparable<WildcardID>
|
||||
{
|
||||
public static final String WILDCARD = "*";
|
||||
public static final String REGEX_WILDCARD = ".";
|
||||
|
||||
private String mValue;
|
||||
private Pattern mPattern;
|
||||
private int mWeight;
|
||||
|
||||
/**
|
||||
* Wildcard identifier for matching to string identifiers containing single-character (*) wildcard values.
|
||||
*
|
||||
* Supports ordering from most-specific to least-specific using the build-in weighting calculation where identifier
|
||||
* patterns containing fewer wildcards closer to the least significant digit will be weighted more heavily than
|
||||
* identifier patterns containing more wildcards, or wildcard characters in the most significant digits.
|
||||
*
|
||||
* @param value is a string value containing one or more asterisk (*) single character wildcard with a maximum
|
||||
* length of 10 characters.
|
||||
*
|
||||
* @throws AssertionError if the value is null, longer than 10 characters, or doesn't contain at least 1 asterisk
|
||||
* @throws IllegalArgumentException if the value cannot be compiled as a regular expression after converting the
|
||||
* asterisk characters to the regex single-character wildcard value (.)
|
||||
*/
|
||||
public WildcardID(String value)
|
||||
{
|
||||
Validate.isTrue(value != null && value.contains(WILDCARD) && value.length() < 10);
|
||||
|
||||
mValue = value;
|
||||
|
||||
try
|
||||
{
|
||||
mPattern = Pattern.compile(value.replace(WILDCARD, REGEX_WILDCARD));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid regex pattern for alias ID value [" + value + "]", e);
|
||||
}
|
||||
|
||||
mWeight = calculateWeight();
|
||||
}
|
||||
|
||||
public String value()
|
||||
{
|
||||
return mValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the id matches the internal regex pattern for this wildcard alias ID
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
public boolean matches(String id)
|
||||
{
|
||||
return id != null && mPattern.matcher(id).matches();
|
||||
}
|
||||
|
||||
public int weight()
|
||||
{
|
||||
return mWeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a weighting value for wildcard character quantity and significant digit location
|
||||
*/
|
||||
private int calculateWeight()
|
||||
{
|
||||
int weight = 0;
|
||||
int characterCount = -1;
|
||||
|
||||
for( int x = 0; x < mValue.length(); x++)
|
||||
{
|
||||
if(mValue.substring(x, x + 1).equals(WILDCARD))
|
||||
{
|
||||
weight += (int)(Math.pow(2, mValue.length() - x - 1)); //Position weight
|
||||
characterCount++;
|
||||
}
|
||||
}
|
||||
|
||||
weight += (int)(Math.pow(2, characterCount)) * 1000; //Character count weight
|
||||
|
||||
return weight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(WildcardID otherWildcardID)
|
||||
{
|
||||
return Integer.compare(this.weight(), otherWildcardID.weight());
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
*/
|
||||
package io.github.dsheirer.alias.id.broadcast;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
@@ -31,6 +34,12 @@ public class BroadcastChannel extends AliasID implements Comparable<BroadcastCha
|
||||
//JAXB Constructor
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(BroadcastChannel other)
|
||||
{
|
||||
@@ -94,6 +103,7 @@ public class BroadcastChannel extends AliasID implements Comparable<BroadcastCha
|
||||
public void setChannelName(String channel)
|
||||
{
|
||||
mChannelName = channel;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
@@ -118,13 +128,6 @@ public class BroadcastChannel extends AliasID implements Comparable<BroadcastCha
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
if(isValid())
|
||||
{
|
||||
return "Broadcast Channel: " + mChannelName;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "Broadcast Channel: None Selected";
|
||||
}
|
||||
return isValid() ? mChannelName : "(invalid)";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias.id.broadcast;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastModel;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.List;
|
||||
|
||||
public class BroadcastChannelEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String HELP_TEXT = "<html>"
|
||||
+ "<h3>Audio Broadcast Channel</h3>"
|
||||
+ "Name of a audio streaming broadcast configuration<br>"
|
||||
+ "to be used for streaming audio to an external<br>"
|
||||
+ "streaming server";
|
||||
|
||||
private JComboBox<String> mBroadcastConfigurations;
|
||||
private BroadcastModel mBroadcastModel;
|
||||
|
||||
public BroadcastChannelEditor(AliasID aliasID, BroadcastModel broadcastModel)
|
||||
{
|
||||
mBroadcastModel = broadcastModel;
|
||||
|
||||
initGUI();
|
||||
setItem( aliasID );
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 1", "[]", "[][]" ) );
|
||||
|
||||
add( new JLabel( "Broadcast Channel" ), "grow" );
|
||||
|
||||
List<String> channelNames = mBroadcastModel.getBroadcastConfigurationNames();
|
||||
|
||||
mBroadcastConfigurations = new JComboBox<String>(channelNames.toArray(new String[channelNames.size()]));
|
||||
mBroadcastConfigurations.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
|
||||
add(mBroadcastConfigurations, "grow");
|
||||
|
||||
JLabel help = new JLabel( "Help ..." );
|
||||
help.setForeground( Color.BLUE.brighter() );
|
||||
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
|
||||
help.addMouseListener( new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e )
|
||||
{
|
||||
JOptionPane.showMessageDialog( BroadcastChannelEditor.this,
|
||||
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
|
||||
}
|
||||
} );
|
||||
|
||||
add( help, "align left" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(AliasID item)
|
||||
{
|
||||
super.setItem(item);
|
||||
|
||||
if(item instanceof BroadcastChannel)
|
||||
{
|
||||
BroadcastChannel channel = (BroadcastChannel)item;
|
||||
|
||||
if(channel.isValid())
|
||||
{
|
||||
mBroadcastConfigurations.setSelectedItem(channel.getChannelName());
|
||||
}
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
String channel = (String)mBroadcastConfigurations.getSelectedItem();
|
||||
|
||||
BroadcastConfiguration selectedConfiguration = mBroadcastModel.getBroadcastConfiguration(channel);
|
||||
|
||||
|
||||
if(selectedConfiguration != null)
|
||||
{
|
||||
((BroadcastChannel)getItem()).setChannelName(selectedConfiguration.getName());
|
||||
}
|
||||
else
|
||||
{
|
||||
((BroadcastChannel)getItem()).setChannelName(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias.id.esn;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
public class ESNEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String HELP_TEXT =
|
||||
"<html><h3>Electronic Serial Number (ESN)</h3>"
|
||||
+ "<b>ESN:</b> hexadecimal 0-9, A-F (e.g. <u>ABCD1234</u> )<br>"
|
||||
+ "<b>Wildcard:</b> use an asterisk (*) to wildcard individual<br>"
|
||||
+ "digits (e.g. <u>ABCD123*</u> or <u>AB**1**4</u>)"
|
||||
+ "</html>";
|
||||
|
||||
private JTextField mTextField;
|
||||
|
||||
public ESNEditor( AliasID aliasID )
|
||||
{
|
||||
initGUI();
|
||||
|
||||
setItem( aliasID );
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2", "[right][left]", "[][]" ) );
|
||||
|
||||
add( new JLabel( "ESN:" ) );
|
||||
mTextField = new JTextField();
|
||||
mTextField.getDocument().addDocumentListener( this );
|
||||
mTextField.setToolTipText( HELP_TEXT );
|
||||
add( mTextField, "growx,push" );
|
||||
|
||||
JLabel help = new JLabel( "Help ..." );
|
||||
help.setForeground( Color.BLUE.brighter() );
|
||||
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
|
||||
help.addMouseListener( new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e )
|
||||
{
|
||||
JOptionPane.showMessageDialog( ESNEditor.this,
|
||||
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
|
||||
}
|
||||
} );
|
||||
add( help, "align left" );
|
||||
}
|
||||
|
||||
public Esn getEsn()
|
||||
{
|
||||
if( getItem() instanceof Esn )
|
||||
{
|
||||
return (Esn)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem( AliasID aliasID )
|
||||
{
|
||||
super.setItem( aliasID );
|
||||
|
||||
Esn esn = getEsn();
|
||||
|
||||
if( esn != null )
|
||||
{
|
||||
mTextField.setText( esn.getEsn() );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
Esn esn = getEsn();
|
||||
|
||||
if( esn != null )
|
||||
{
|
||||
esn.setEsn( mTextField.getText() );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,12 @@ public class Esn extends AliasID
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "esn")
|
||||
public String getEsn()
|
||||
{
|
||||
@@ -44,6 +50,7 @@ public class Esn extends AliasID
|
||||
public void setEsn(String esn )
|
||||
{
|
||||
mEsn = esn;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
public String toString()
|
||||
|
||||
@@ -31,6 +31,12 @@ public class FleetsyncID extends AliasID
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "ident")
|
||||
public String getIdent()
|
||||
{
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias.id.legacy.fleetsync;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.JFormattedTextField;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.text.MaskFormatter;
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
public class FleetsyncIDEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( FleetsyncIDEditor.class );
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String HELP_TEXT = "<html>"
|
||||
+ "<h3>Fleetsync Identifier</h3>"
|
||||
+ "<b>Fleetsync:</b> ggg-uuuu where g=Group and u=Unit (e.g. <u>001-0001</u>)<br>"
|
||||
+ "<b>Wildcard:</b> use an asterisk (*) for each digit (e.g. <u>001-****</u>)"
|
||||
+ "</html>";
|
||||
|
||||
private JTextField mTextField;
|
||||
|
||||
public FleetsyncIDEditor( AliasID aliasID )
|
||||
{
|
||||
initGUI();
|
||||
|
||||
setItem( aliasID );
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2", "[right][left]", "[][]" ) );
|
||||
|
||||
add( new JLabel( "Fleetsync ID:" ) );
|
||||
|
||||
MaskFormatter formatter = null;
|
||||
|
||||
try
|
||||
{
|
||||
//Mask: 3 digits - 4 digits
|
||||
formatter = new MaskFormatter( "###-####" );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
//Do nothing, the mask was invalid
|
||||
}
|
||||
|
||||
mTextField = new JFormattedTextField( formatter );
|
||||
mTextField.getDocument().addDocumentListener( this );
|
||||
mTextField.setToolTipText( HELP_TEXT );
|
||||
|
||||
add( mTextField, "growx,push" );
|
||||
|
||||
JLabel help = new JLabel( "Help ..." );
|
||||
help.setForeground( Color.BLUE.brighter() );
|
||||
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
|
||||
help.addMouseListener( new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e )
|
||||
{
|
||||
JOptionPane.showMessageDialog( FleetsyncIDEditor.this,
|
||||
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
|
||||
}
|
||||
} );
|
||||
add( help, "align left" );
|
||||
}
|
||||
|
||||
public FleetsyncID getFleetsyncID()
|
||||
{
|
||||
if( getItem() instanceof FleetsyncID )
|
||||
{
|
||||
return (FleetsyncID)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem( AliasID aliasID )
|
||||
{
|
||||
super.setItem( aliasID );
|
||||
|
||||
FleetsyncID fleetsync = getFleetsyncID();
|
||||
|
||||
if( fleetsync != null )
|
||||
{
|
||||
mTextField.setText( fleetsync.getIdent() );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
FleetsyncID fleetsync = getFleetsyncID();
|
||||
|
||||
if( fleetsync != null )
|
||||
{
|
||||
fleetsync.setIdent( mTextField.getText() );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,12 @@ public class MDC1200ID extends AliasID
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "ident")
|
||||
public String getIdent()
|
||||
{
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias.id.legacy.mdc;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.JFormattedTextField;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.text.MaskFormatter;
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
public class MDC1200IDEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String HELP_TEXT = "<html>"
|
||||
+ "<h3>MDC-1200 Identifier</h3>"
|
||||
+ "<b>MDC-1200:</b> four digit [0-9] value (e.g. <u>1234</u>)<br>"
|
||||
+ "<b>Wildcard:</b> use an asterisk (*) to wildcard individual<br>"
|
||||
+ "digits (e.g. <u>123*</u> or <u>**34</u>)"
|
||||
+ "</html>";
|
||||
|
||||
private JTextField mTextField;
|
||||
|
||||
public MDC1200IDEditor(AliasID aliasID)
|
||||
{
|
||||
initGUI();
|
||||
|
||||
setItem(aliasID);
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 2", "[right][left]", "[][]"));
|
||||
|
||||
add(new JLabel("MDC-1200 ID:"));
|
||||
|
||||
MaskFormatter formatter = null;
|
||||
|
||||
try
|
||||
{
|
||||
//Mask: 4 digits
|
||||
formatter = new MaskFormatter("####");
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//Do nothing, the mask was invalid
|
||||
}
|
||||
|
||||
mTextField = new JFormattedTextField(formatter);
|
||||
mTextField.getDocument().addDocumentListener(this);
|
||||
mTextField.setToolTipText(HELP_TEXT);
|
||||
add(mTextField, "growx,push");
|
||||
|
||||
JLabel help = new JLabel("Help ...");
|
||||
help.setForeground(Color.BLUE.brighter());
|
||||
help.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
help.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
JOptionPane.showMessageDialog(MDC1200IDEditor.this,
|
||||
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
});
|
||||
add(help, "align left");
|
||||
}
|
||||
|
||||
public MDC1200ID getMDC1200ID()
|
||||
{
|
||||
if(getItem() instanceof MDC1200ID)
|
||||
{
|
||||
return (MDC1200ID)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(AliasID aliasID)
|
||||
{
|
||||
super.setItem(aliasID);
|
||||
|
||||
MDC1200ID mdc = getMDC1200ID();
|
||||
|
||||
if(mdc != null)
|
||||
{
|
||||
mTextField.setText(mdc.getIdent());
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
MDC1200ID mdc = getMDC1200ID();
|
||||
|
||||
if(mdc != null)
|
||||
{
|
||||
mdc.setIdent(mTextField.getText());
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias.id.legacy.mobileID;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.MaskFormatter;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
public class MINEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String HELP_TEXT = "<html>"
|
||||
+ "<h3>Passport Mobile ID (MIN)</h3>"
|
||||
+ "<b>MIN:</b> six character hex value (e.g. <u>AB12CD</u>)<br>"
|
||||
+ "<b>Wildcard:</b> use an asterisk (*) to wildcard individual<br>"
|
||||
+ "digits (e.g. <u>AB**CD</u> or <u>AB12**</u>)"
|
||||
+ "</html>";
|
||||
|
||||
private JTextField mTextField;
|
||||
|
||||
public MINEditor( AliasID aliasID )
|
||||
{
|
||||
initGUI();
|
||||
|
||||
setItem( aliasID );
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2", "[right][left]", "[][]" ) );
|
||||
|
||||
add( new JLabel( "MIN:" ) );
|
||||
|
||||
MaskFormatter formatter = null;
|
||||
|
||||
try
|
||||
{
|
||||
//Mask: 6 hex characters
|
||||
formatter = new MaskFormatter( "HHHHHH" );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
//Do nothing, the mask was invalid
|
||||
}
|
||||
|
||||
mTextField = new JFormattedTextField( formatter );
|
||||
mTextField.getDocument().addDocumentListener( this );
|
||||
mTextField.setToolTipText( HELP_TEXT );
|
||||
add( mTextField, "growx,push" );
|
||||
|
||||
JLabel help = new JLabel( "Help ..." );
|
||||
help.setForeground( Color.BLUE.brighter() );
|
||||
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
|
||||
help.addMouseListener( new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e )
|
||||
{
|
||||
JOptionPane.showMessageDialog( MINEditor.this,
|
||||
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
|
||||
}
|
||||
} );
|
||||
add( help, "align left" );
|
||||
}
|
||||
|
||||
public Min getMin()
|
||||
{
|
||||
if( getItem() instanceof Min )
|
||||
{
|
||||
return (Min)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem( AliasID aliasID )
|
||||
{
|
||||
super.setItem( aliasID );
|
||||
|
||||
Min min = getMin();
|
||||
|
||||
if( min != null )
|
||||
{
|
||||
mTextField.setText( min.getMin() );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
Min min = getMin();
|
||||
|
||||
if( min != null )
|
||||
{
|
||||
min.setMin( mTextField.getText() );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,12 @@ public class Min extends AliasID
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "min")
|
||||
public String getMin()
|
||||
{
|
||||
|
||||
@@ -32,6 +32,12 @@ public class MPT1327ID extends AliasID
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "ident")
|
||||
public String getIdent()
|
||||
{
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias.id.legacy.mpt1327;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.JFormattedTextField;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.text.MaskFormatter;
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
public class MPT1327IDEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String HELP_TEXT = "<html>"
|
||||
+ "<h3>MPT-1327 Identifier</h3>"
|
||||
+ "<b>MPT-1327:</b> decimal (0-9) format ppp-iiii where<br>"
|
||||
+ "p=Prefix and i=Ident (e.g. <u>123-0001</u>)<br>"
|
||||
+ "<b>Wildcard:</b> use an asterisk (*) to wildcard individual<br>"
|
||||
+ "digits (e.g. <u>ABCD123*</u> or <u>AB**1**4</u>)"
|
||||
+ "</html>";
|
||||
|
||||
private JTextField mTextField;
|
||||
|
||||
public MPT1327IDEditor( AliasID aliasID )
|
||||
{
|
||||
initGUI();
|
||||
|
||||
setItem( aliasID );
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2", "[right][left]", "[][]" ) );
|
||||
|
||||
add( new JLabel( "MPT-1327 ID:" ) );
|
||||
|
||||
MaskFormatter formatter = null;
|
||||
|
||||
try
|
||||
{
|
||||
//Mask: 3 digits - 4 digits
|
||||
formatter = new MaskFormatter( "***-****" );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
//Do nothing, the mask was invalid
|
||||
}
|
||||
|
||||
mTextField = new JFormattedTextField( formatter );
|
||||
mTextField.getDocument().addDocumentListener( this );
|
||||
mTextField.setToolTipText( HELP_TEXT );
|
||||
|
||||
add( mTextField, "growx,push" );
|
||||
|
||||
JLabel help = new JLabel( "Help ..." );
|
||||
help.setForeground( Color.BLUE.brighter() );
|
||||
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
|
||||
help.addMouseListener( new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e )
|
||||
{
|
||||
JOptionPane.showMessageDialog( MPT1327IDEditor.this,
|
||||
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
|
||||
}
|
||||
} );
|
||||
add( help, "align left" );
|
||||
}
|
||||
|
||||
public MPT1327ID getMPT1327ID()
|
||||
{
|
||||
if( getItem() instanceof MPT1327ID )
|
||||
{
|
||||
return (MPT1327ID)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem( AliasID aliasID )
|
||||
{
|
||||
super.setItem( aliasID );
|
||||
|
||||
MPT1327ID mpt = getMPT1327ID();
|
||||
|
||||
if( mpt != null )
|
||||
{
|
||||
mTextField.setText( mpt.getIdent() );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
MPT1327ID mpt = getMPT1327ID();
|
||||
|
||||
if( mpt != null )
|
||||
{
|
||||
mpt.setIdent( mTextField.getText() );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,12 @@ public class NonRecordable extends AliasID
|
||||
return AliasIDType.NON_RECORDABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias.id.legacy.nonrecordable;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
public class NonRecordableEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String HELP_TEXT = "<html>"
|
||||
+ "<h3>Audio Non-Recordable</h3>"
|
||||
+ "This identifies an alias as non-recordable.<br><br>"
|
||||
+ "The alias can still be monitored but associated audio will<br>"
|
||||
+ "not be recorded.<br><br>"
|
||||
+ "The default behavior when audio recording is enabled for a<br>"
|
||||
+ "channel is to record all aliases except any alias with a<br>"
|
||||
+ "non-recordable identifier";
|
||||
|
||||
public NonRecordableEditor(AliasID aliasID)
|
||||
{
|
||||
initGUI();
|
||||
|
||||
setItem(aliasID);
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 1", "[]", "[][]"));
|
||||
|
||||
add(new JLabel("Non-Recordable"), "wrap");
|
||||
|
||||
JLabel help = new JLabel("Help ...");
|
||||
help.setForeground(Color.BLUE.brighter());
|
||||
help.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
help.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
JOptionPane.showMessageDialog(NonRecordableEditor.this,
|
||||
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
});
|
||||
add(help, "align left");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,12 @@ public class SiteID extends AliasID
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "site")
|
||||
public String getSite()
|
||||
{
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias.id.legacy.siteID;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
public class SiteIDEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String HELP_TEXT = "<html>"
|
||||
+ "<h3>Site Identifier</h3>"
|
||||
+ "<b>LTR-Net:</b> decimal (0-9) (e.g. <u>019</u>)<br>"
|
||||
+ "<b>MPT-1327:</b> 5 digits (0-9) (e.g. <u>23619</u>)<br>"
|
||||
+ "<b>Passport:</b> 3 digits (0-9) (e.g. <u>019</u>)<br>"
|
||||
+ "<b>P25:</b> hex (0-9, A-F) format RR-SS where RR = RF Subsystem<br>"
|
||||
+ "and SS = Site Number (e.g. RFSS 1 Site 1F: <u>01-1F</u>)"
|
||||
+ "</html>";
|
||||
|
||||
private JTextField mTextField;
|
||||
|
||||
public SiteIDEditor( AliasID aliasID )
|
||||
{
|
||||
initGUI();
|
||||
|
||||
setItem( aliasID );
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2", "[right][left]", "[][]" ) );
|
||||
|
||||
add( new JLabel( "Site ID:" ) );
|
||||
mTextField = new JTextField();
|
||||
mTextField.getDocument().addDocumentListener( this );
|
||||
mTextField.setToolTipText( HELP_TEXT );
|
||||
add( mTextField, "growx,push" );
|
||||
|
||||
JLabel help = new JLabel( "Help ..." );
|
||||
help.setForeground( Color.BLUE.brighter() );
|
||||
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
|
||||
help.addMouseListener( new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e )
|
||||
{
|
||||
JOptionPane.showMessageDialog( SiteIDEditor.this,
|
||||
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
|
||||
}
|
||||
} );
|
||||
|
||||
add( help, "align left" );
|
||||
}
|
||||
|
||||
public SiteID getSiteID()
|
||||
{
|
||||
if( getItem() instanceof SiteID )
|
||||
{
|
||||
return (SiteID)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem( AliasID aliasID )
|
||||
{
|
||||
super.setItem( aliasID );
|
||||
|
||||
SiteID siteID = getSiteID();
|
||||
|
||||
if( siteID != null )
|
||||
{
|
||||
mTextField.setText( siteID.getSite() );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
SiteID siteID = getSiteID();
|
||||
|
||||
if( siteID != null )
|
||||
{
|
||||
siteID.setSite( mTextField.getText() );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,12 @@ public class LegacyTalkgroupID extends AliasID
|
||||
mTalkgroup = talkgroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "talkgroup")
|
||||
public String getTalkgroup()
|
||||
{
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias.id.legacy.talkgroup;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JTextField;
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
public class LegacyTalkgroupIDEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String HELP_TEXT =
|
||||
"<html><h3>Talkgroup Identifier</h3>"
|
||||
+ "<b>P25:</b> 4 or 6 hex characters (e.g. <u>AB12</u> or <u>ABC123</u>)<br>"
|
||||
+ "<b>LTR:</b> A-HH-TTT where A=Area H=Home T=Talkgroup (<u>0-01-128</u>)<br>"
|
||||
+ "<b>Passport</b>: 5-digit number (<u>12345</u> or <u>00023</u>)<br>"
|
||||
+ "<br>"
|
||||
+ "<b>Wildcard:</b> use an asterisk (*) in place of each talkgroup digit (e.g. <u>0*1*5</u>)"
|
||||
+ "</html>";
|
||||
|
||||
private JTextField mTextField;
|
||||
|
||||
public LegacyTalkgroupIDEditor(AliasID aliasID)
|
||||
{
|
||||
initGUI();
|
||||
|
||||
setItem(aliasID);
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 2", "[right][left]", "[][]"));
|
||||
|
||||
add(new JLabel("Talkgroup:"));
|
||||
|
||||
mTextField = new JTextField();
|
||||
mTextField.getDocument().addDocumentListener(this);
|
||||
mTextField.setToolTipText(HELP_TEXT);
|
||||
add(mTextField, "growx,push");
|
||||
|
||||
JLabel help = new JLabel("Help ...");
|
||||
help.setForeground(Color.BLUE.brighter());
|
||||
help.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
help.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
JOptionPane.showMessageDialog(LegacyTalkgroupIDEditor.this,
|
||||
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
});
|
||||
add(help, "align left");
|
||||
}
|
||||
|
||||
public LegacyTalkgroupID getTalkgroupID()
|
||||
{
|
||||
if(getItem() instanceof LegacyTalkgroupID)
|
||||
{
|
||||
return (LegacyTalkgroupID)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(AliasID aliasID)
|
||||
{
|
||||
super.setItem(aliasID);
|
||||
|
||||
LegacyTalkgroupID talkgroup = getTalkgroupID();
|
||||
|
||||
if(talkgroup != null)
|
||||
{
|
||||
mTextField.setText(talkgroup.getTalkgroup());
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
LegacyTalkgroupID talkgroup = getTalkgroupID();
|
||||
|
||||
if(talkgroup != null)
|
||||
{
|
||||
talkgroup.setTalkgroup(mTextField.getText());
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,12 @@ public class UniqueID extends AliasID
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "uid")
|
||||
public int getUid()
|
||||
{
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias.id.legacy.uniqueID;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
public class UniqueIDEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String HELP_TEXT = "<html>"
|
||||
+ "<h3>LTR-Net Unique ID (UID)</h3>"
|
||||
+ "<b>UID:</b> identifier assigned to each radio in<br>"
|
||||
+ "the range <u>1 - 2097152</u>"
|
||||
+ "</html>";
|
||||
|
||||
private JTextField mTextField;
|
||||
|
||||
public UniqueIDEditor( AliasID aliasID )
|
||||
{
|
||||
initGUI();
|
||||
|
||||
setItem( aliasID );
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2", "[right][left]", "[][]" ) );
|
||||
|
||||
add( new JLabel( "Unique ID:" ) );
|
||||
mTextField = new JTextField();
|
||||
mTextField.getDocument().addDocumentListener( this );
|
||||
mTextField.setToolTipText( HELP_TEXT );
|
||||
add( mTextField, "growx,push" );
|
||||
|
||||
JLabel help = new JLabel( "Help ..." );
|
||||
help.setForeground( Color.BLUE.brighter() );
|
||||
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
|
||||
help.addMouseListener( new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e )
|
||||
{
|
||||
JOptionPane.showMessageDialog( UniqueIDEditor.this,
|
||||
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
|
||||
}
|
||||
} );
|
||||
add( help, "align left" );
|
||||
}
|
||||
|
||||
public UniqueID getUniqueID()
|
||||
{
|
||||
if( getItem() instanceof UniqueID )
|
||||
{
|
||||
return (UniqueID)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem( AliasID aliasID )
|
||||
{
|
||||
super.setItem( aliasID );
|
||||
|
||||
UniqueID uid = getUniqueID();
|
||||
|
||||
if( uid != null )
|
||||
{
|
||||
mTextField.setText( String.valueOf( uid.getUid() ) );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
UniqueID uid = getUniqueID();
|
||||
|
||||
if( uid != null )
|
||||
{
|
||||
int id = 0;
|
||||
|
||||
try
|
||||
{
|
||||
id = Integer.parseInt( mTextField.getText() );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
//Do nothing, we couldn't parse the value
|
||||
}
|
||||
|
||||
uid.setUid( id );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,12 @@ public class LoJackFunctionAndID extends AliasID
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "id")
|
||||
public String getID()
|
||||
{
|
||||
@@ -41,6 +47,7 @@ public class LoJackFunctionAndID extends AliasID
|
||||
public void setID(String id)
|
||||
{
|
||||
mID = id;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "function")
|
||||
@@ -52,6 +59,7 @@ public class LoJackFunctionAndID extends AliasID
|
||||
public void setFunction(LJ1200Message.Function function)
|
||||
{
|
||||
mFunction = function;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias.id.lojack;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import io.github.dsheirer.module.decode.lj1200.LJ1200Message;
|
||||
import io.github.dsheirer.module.decode.lj1200.LJ1200Message.Function;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.MaskFormatter;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
public class LoJackIDEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JTextField mTextField;
|
||||
|
||||
private JComboBox<LJ1200Message.Function> mFunctionCombo;
|
||||
|
||||
private static final String HELP_TEXT = "<html>"
|
||||
+ "<h3>LoJack Function and Identifier</h3>"
|
||||
+ "<b>Function Code:</b> <u>1Y-SITE ID</u><br>"
|
||||
+ "<b>ID:</b> 5 numbers or characters (e.g. <u>1BN47</u>)<br>"
|
||||
+ "<br>"
|
||||
+ "<b>Wildcard:</b> use an asterisk (*) to wildcard ID<br>"
|
||||
+ "characters (e.g. <u>AB*CD</u> or <u>***12</u> or <u>*****</u>)<br><br>"
|
||||
+ "The middle character in a reply ID code identifies the<br>"
|
||||
+ "entity. Valid ID middle characters are:<br>"
|
||||
+ "<li><b>Tower:</b> X,Y</li>"
|
||||
+ "<li><b>Transponder:</b> 0-9,A,C-H,J-N,P-W</li>"
|
||||
+ "<li><b>Not Used:</b> B,I,O,Z</li>"
|
||||
+ "</html>";
|
||||
|
||||
public LoJackIDEditor( AliasID aliasID )
|
||||
{
|
||||
initGUI();
|
||||
|
||||
setItem( aliasID );
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2", "[right][left]", "[][][]" ) );
|
||||
|
||||
add( new JLabel( "Function:" ) );
|
||||
mFunctionCombo = new JComboBox<LJ1200Message.Function>( Function.values() );
|
||||
mFunctionCombo.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
mFunctionCombo.setToolTipText( HELP_TEXT );
|
||||
add( mFunctionCombo, "growx, push" );
|
||||
|
||||
add( new JLabel( "ID:" ) );
|
||||
|
||||
MaskFormatter formatter = null;
|
||||
|
||||
try
|
||||
{
|
||||
//Mask: any character or number, 5 places
|
||||
formatter = new MaskFormatter( "AAAAA" );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
//Do nothing, the mask was invalid
|
||||
}
|
||||
|
||||
mTextField = new JFormattedTextField( formatter );
|
||||
mTextField.getDocument().addDocumentListener( this );
|
||||
mTextField.setToolTipText( HELP_TEXT );
|
||||
add( mTextField, "growx,push" );
|
||||
|
||||
JLabel help = new JLabel( "Help ..." );
|
||||
help.setForeground( Color.BLUE.brighter() );
|
||||
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
|
||||
help.addMouseListener( new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e )
|
||||
{
|
||||
JOptionPane.showMessageDialog( LoJackIDEditor.this,
|
||||
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
|
||||
}
|
||||
} );
|
||||
add( help, "align left" );
|
||||
}
|
||||
|
||||
public LoJackFunctionAndID getLoJackID()
|
||||
{
|
||||
if( getItem() instanceof LoJackFunctionAndID )
|
||||
{
|
||||
return (LoJackFunctionAndID)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem( AliasID aliasID )
|
||||
{
|
||||
super.setItem( aliasID );
|
||||
|
||||
LoJackFunctionAndID lojack = getLoJackID();
|
||||
|
||||
if( lojack != null )
|
||||
{
|
||||
mFunctionCombo.setSelectedItem( lojack.getFunction() );
|
||||
mTextField.setText( lojack.getID() );
|
||||
}
|
||||
else
|
||||
{
|
||||
mFunctionCombo.setSelectedItem( null );
|
||||
mTextField.setText( null );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
LoJackFunctionAndID lojack = getLoJackID();
|
||||
|
||||
if( lojack != null )
|
||||
{
|
||||
lojack.setID( mTextField.getText() );
|
||||
lojack.setFunction( (Function)mFunctionCombo.getSelectedItem() );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,17 @@ public class Priority extends AliasID
|
||||
{
|
||||
}
|
||||
|
||||
public Priority(int priority)
|
||||
{
|
||||
mPriority = priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
@@ -63,6 +74,7 @@ public class Priority extends AliasID
|
||||
public void setPriority(int priority)
|
||||
{
|
||||
mPriority = priority;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
public String toString()
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias.id.priority;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
public class PriorityEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final String DO_NOT_MONITOR = "Do Not Monitor";
|
||||
|
||||
private static final String HELP_TEXT = "<html>"
|
||||
+ "<h3>Call Audio Priority</h3>"
|
||||
+ "Priority determines which calls have priority for playback<br>"
|
||||
+ "over your computer speakers, or designates an alias for<br>"
|
||||
+ "no-monitoring if you don't want to hear calls from an alias.<br><br>"
|
||||
+ "Lower values indicate higher priority levels.<br><br>"
|
||||
+ "<b>Do Not Monitor: </b> slide priority all the way to the right"
|
||||
+ "</html>";
|
||||
|
||||
private JSlider mPrioritySlider;
|
||||
private JLabel mPrioritySliderLabel;
|
||||
|
||||
public PriorityEditor( AliasID aliasID )
|
||||
{
|
||||
initGUI();
|
||||
|
||||
setItem( aliasID );
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2", "[right][left]", "[][]" ) );
|
||||
|
||||
mPrioritySlider = new JSlider( JSlider.HORIZONTAL,
|
||||
Priority.MIN_PRIORITY,
|
||||
Priority.MAX_PRIORITY + 1,
|
||||
Priority.MIN_PRIORITY );
|
||||
|
||||
mPrioritySlider.setMajorTickSpacing( 20 );
|
||||
mPrioritySlider.setMinorTickSpacing( 5 );
|
||||
mPrioritySlider.setPaintTicks( true );
|
||||
mPrioritySlider.setLabelTable( mPrioritySlider.createStandardLabels( 20, 20 ) );
|
||||
mPrioritySlider.setPaintLabels( true );
|
||||
mPrioritySlider.addChangeListener( new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged( ChangeEvent e )
|
||||
{
|
||||
int priority = mPrioritySlider.getValue();
|
||||
|
||||
if( priority == Priority.MAX_PRIORITY + 1 )
|
||||
{
|
||||
mPrioritySliderLabel.setText( "Priority: Do Not Monitor" );
|
||||
}
|
||||
else
|
||||
{
|
||||
mPrioritySliderLabel.setText( "Priority: " + priority );
|
||||
}
|
||||
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
mPrioritySlider.setToolTipText( HELP_TEXT );
|
||||
|
||||
mPrioritySliderLabel = new JLabel( "Priority: " + mPrioritySlider.getValue() + " " );
|
||||
add( mPrioritySliderLabel, "span,align center" );
|
||||
add( mPrioritySlider, "span,grow" );
|
||||
|
||||
JLabel help = new JLabel( "Help ..." );
|
||||
help.setForeground( Color.BLUE.brighter() );
|
||||
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
|
||||
help.addMouseListener( new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e )
|
||||
{
|
||||
JOptionPane.showMessageDialog( PriorityEditor.this,
|
||||
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
|
||||
}
|
||||
} );
|
||||
add( help, "align left" );
|
||||
}
|
||||
|
||||
public Priority getPriority()
|
||||
{
|
||||
if( getItem() instanceof Priority )
|
||||
{
|
||||
return (Priority)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem( AliasID aliasID )
|
||||
{
|
||||
super.setItem( aliasID );
|
||||
|
||||
Priority priority = getPriority();
|
||||
|
||||
if( priority != null )
|
||||
{
|
||||
int value = priority.getPriority();
|
||||
|
||||
if( value == Priority.DO_NOT_MONITOR )
|
||||
{
|
||||
value = Priority.MAX_PRIORITY + 1;
|
||||
}
|
||||
|
||||
mPrioritySlider.setValue( value );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
Priority priority = getPriority();
|
||||
|
||||
if( priority != null )
|
||||
{
|
||||
int value = mPrioritySlider.getValue();
|
||||
|
||||
if( value == Priority.MAX_PRIORITY + 1 )
|
||||
{
|
||||
value = Priority.DO_NOT_MONITOR;
|
||||
}
|
||||
|
||||
priority.setPriority( value );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
}
|
||||
@@ -25,14 +25,13 @@ package io.github.dsheirer.alias.id.radio;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.alias.id.AliasIDType;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupFormat;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupFormatter;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* Integer radio identifier with protocol.
|
||||
*/
|
||||
public class Radio extends AliasID
|
||||
public class Radio extends AliasID implements Comparable<Radio>
|
||||
{
|
||||
private Protocol mProtocol = Protocol.UNKNOWN;
|
||||
private int mValue;
|
||||
@@ -48,6 +47,12 @@ public class Radio extends AliasID
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "value")
|
||||
public int getValue()
|
||||
{
|
||||
@@ -57,6 +62,7 @@ public class Radio extends AliasID
|
||||
public void setValue(int value)
|
||||
{
|
||||
mValue = value;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "protocol")
|
||||
@@ -68,6 +74,7 @@ public class Radio extends AliasID
|
||||
public void setProtocol(Protocol protocol)
|
||||
{
|
||||
mProtocol = protocol;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -115,4 +122,22 @@ public class Radio extends AliasID
|
||||
{
|
||||
return AliasIDType.RADIO_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Radio other)
|
||||
{
|
||||
if(other == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(getProtocol().equals(other.getProtocol()))
|
||||
{
|
||||
return Integer.compare(getValue(), other.getValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
return getProtocol().compareTo(other.getProtocol());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.alias.id.radio;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JFormattedTextField;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.text.MaskFormatter;
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.text.ParseException;
|
||||
|
||||
public class RadioEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RadioEditor.class);
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String VALID_CHARACTERS_FOR_ASTERISK_MASKS = "0123456789 ";
|
||||
private MaskFormatter mMaskFormatter = new MaskFormatter();
|
||||
private JComboBox<Protocol> mComboProtocol;
|
||||
private JFormattedTextField mRadioIdField;
|
||||
|
||||
|
||||
public RadioEditor(AliasID aliasID)
|
||||
{
|
||||
initGUI();
|
||||
setItem(aliasID);
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 2", "[right][left]", "[][]"));
|
||||
|
||||
add(new JLabel("Protocol:"));
|
||||
|
||||
mComboProtocol = new JComboBox<>();
|
||||
|
||||
DefaultComboBoxModel<Protocol> model = new DefaultComboBoxModel<>();
|
||||
|
||||
for(Protocol protocol : Protocol.RADIO_ID_PROTOCOLS)
|
||||
{
|
||||
model.addElement(protocol);
|
||||
}
|
||||
|
||||
mComboProtocol.setModel(model);
|
||||
mComboProtocol.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
Protocol protocol = (Protocol)mComboProtocol.getSelectedItem();
|
||||
updateEditor(protocol);
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
|
||||
add(mComboProtocol);
|
||||
|
||||
add(new JLabel("Value:"));
|
||||
|
||||
mRadioIdField = new JFormattedTextField(mMaskFormatter);
|
||||
mRadioIdField.getDocument().addDocumentListener(this);
|
||||
add(mRadioIdField, "growx,push");
|
||||
|
||||
JLabel help = new JLabel("Help ...");
|
||||
help.setForeground(Color.BLUE.brighter());
|
||||
help.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
help.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
RadioFormat radioFormat = RadioFormat.get(getCurrentProtocol());
|
||||
JOptionPane.showMessageDialog(RadioEditor.this,
|
||||
radioFormat.getValidRangeHelpText(), "Help", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
});
|
||||
add(help, "align left");
|
||||
}
|
||||
|
||||
private void updateEditor(Protocol protocol)
|
||||
{
|
||||
RadioFormat mask = RadioFormat.get(protocol);
|
||||
int currentValue = 0;
|
||||
Radio radio = getRadio();
|
||||
|
||||
if(radio != null)
|
||||
{
|
||||
currentValue = radio.getValue();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
mRadioIdField.setValue(null);
|
||||
mRadioIdField.setToolTipText(mask.getValidRangeHelpText());
|
||||
mMaskFormatter.setMask(mask.getMask());
|
||||
if(mask.getMask().contains("*"))
|
||||
{
|
||||
mMaskFormatter.setValidCharacters(VALID_CHARACTERS_FOR_ASTERISK_MASKS);
|
||||
}
|
||||
mRadioIdField.setValue(RadioFormatter.format(protocol, currentValue));
|
||||
}
|
||||
catch(ParseException pe)
|
||||
{
|
||||
mLog.error("Error applying talkgroup editor mask to mask formatter [" + mask.getMask() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private Protocol getCurrentProtocol()
|
||||
{
|
||||
return mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
|
||||
}
|
||||
|
||||
/**
|
||||
* Current talkgroup alias id
|
||||
*/
|
||||
public Radio getRadio()
|
||||
{
|
||||
if(getItem() instanceof Radio)
|
||||
{
|
||||
return (Radio)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(AliasID aliasID)
|
||||
{
|
||||
super.setItem(aliasID);
|
||||
|
||||
Radio radio = getRadio();
|
||||
|
||||
if(radio != null)
|
||||
{
|
||||
Protocol currentProtocol = getCurrentProtocol();
|
||||
if(currentProtocol == radio.getProtocol())
|
||||
{
|
||||
updateEditor(radio.getProtocol());
|
||||
}
|
||||
mComboProtocol.getModel().setSelectedItem(radio.getProtocol());
|
||||
|
||||
String formatted = RadioFormatter.format(radio.getProtocol(), radio.getValue());
|
||||
mRadioIdField.setValue(formatted);
|
||||
}
|
||||
else
|
||||
{
|
||||
mComboProtocol.getModel().setSelectedItem(Protocol.UNKNOWN);
|
||||
mRadioIdField.setValue(RadioFormatter.format(getCurrentProtocol(), 0));
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
Radio radio = getRadio();
|
||||
|
||||
if(radio != null)
|
||||
{
|
||||
Protocol protocol = mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
|
||||
|
||||
if(protocol == null)
|
||||
{
|
||||
protocol = Protocol.UNKNOWN;
|
||||
}
|
||||
|
||||
int value = -1;
|
||||
|
||||
try
|
||||
{
|
||||
value = RadioFormatter.parse(protocol, mRadioIdField.getText());
|
||||
}
|
||||
catch(ParseException pe)
|
||||
{
|
||||
//ignore ... value is still -1 and outside valid value range
|
||||
}
|
||||
|
||||
RadioFormat mask = RadioFormat.get(protocol);
|
||||
|
||||
//Check for valid value within range ... notify user but allow value to persist
|
||||
if(value < mask.getMinimumValidValue() || value > mask.getMaximumValidValue())
|
||||
{
|
||||
String message = "Invalid value [" + mRadioIdField.getText() + "]. " + protocol.name() +
|
||||
" valid range is [" + mask.getValidRangeDescription() + "]";
|
||||
|
||||
JOptionPane.showMessageDialog(RadioEditor.this, message, "Invalid Radio ID Value",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
radio.setValue(value);
|
||||
}
|
||||
|
||||
radio.setProtocol(protocol);
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,10 @@ public enum RadioFormat
|
||||
{
|
||||
APCO25("********", 0, 0xFFFFFF, "0 to 16,777,215",
|
||||
"<html>APCO25 valid range is 0 to 16,777,215"),
|
||||
DMR("********", 0, 0xFFFFFF, "0 to 16,777,215",
|
||||
"<html>DMR unit id valid range is 0 to 16,777,215"),
|
||||
PASSPORT("********", 0, 0x7FFFFF, "0 to 8,388,607",
|
||||
"<html>PASSPORT valid range is 0 to 8,388,607"),
|
||||
UNKNOWN("********", 1, 0xFFFFFF, "1 to 16,777,215",
|
||||
"Unknown protocol valid value range is 1-16,777,215");
|
||||
|
||||
@@ -110,6 +114,10 @@ public enum RadioFormat
|
||||
{
|
||||
case APCO25:
|
||||
return APCO25;
|
||||
case DMR:
|
||||
return DMR;
|
||||
case PASSPORT:
|
||||
return PASSPORT;
|
||||
default:
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -22,14 +22,17 @@
|
||||
|
||||
package io.github.dsheirer.alias.id.radio;
|
||||
|
||||
import io.github.dsheirer.preference.identifier.IntegerFormat;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.IntegerFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.AbstractIntegerFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.DMRTalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.HashMap;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -38,12 +41,13 @@ import java.util.Map;
|
||||
public class RadioFormatter
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RadioFormatter.class);
|
||||
private static Map<Protocol,IntegerFormatter> mFormatterMap = new HashMap<>();
|
||||
private static Map<Protocol,AbstractIntegerFormatter> mFormatterMap = new EnumMap<>(Protocol.class);
|
||||
|
||||
static
|
||||
{
|
||||
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.UNKNOWN, new IntegerFormatter());
|
||||
mFormatterMap.put(Protocol.DMR, new DMRTalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
|
||||
}
|
||||
|
||||
public RadioFormatter()
|
||||
@@ -55,7 +59,7 @@ public class RadioFormatter
|
||||
*/
|
||||
public static int parse(Protocol protocol, String value) throws ParseException
|
||||
{
|
||||
IntegerFormatter formatter = mFormatterMap.get(protocol);
|
||||
AbstractIntegerFormatter formatter = mFormatterMap.get(protocol);
|
||||
|
||||
if(formatter == null)
|
||||
{
|
||||
@@ -70,7 +74,7 @@ public class RadioFormatter
|
||||
*/
|
||||
public static String format(Protocol protocol, int value)
|
||||
{
|
||||
IntegerFormatter formatter = mFormatterMap.get(protocol);
|
||||
AbstractIntegerFormatter formatter = mFormatterMap.get(protocol);
|
||||
|
||||
if(formatter == null)
|
||||
{
|
||||
@@ -79,4 +83,19 @@ public class RadioFormatter
|
||||
|
||||
return formatter.format(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the integer value to the specified integer format using the protocol specific formatter
|
||||
*/
|
||||
public static String format(Protocol protocol, int value, IntegerFormat format)
|
||||
{
|
||||
AbstractIntegerFormatter formatter = mFormatterMap.get(protocol);
|
||||
|
||||
if(formatter == null)
|
||||
{
|
||||
formatter = mFormatterMap.get(Protocol.UNKNOWN);
|
||||
}
|
||||
|
||||
return formatter.format(value, format);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import io.github.dsheirer.protocol.Protocol;
|
||||
/**
|
||||
* Integer radio identifier range of values with protocol.
|
||||
*/
|
||||
public class RadioRange extends AliasID
|
||||
public class RadioRange extends AliasID implements Comparable<RadioRange>
|
||||
{
|
||||
private Protocol mProtocol = Protocol.UNKNOWN;
|
||||
private int mMinRadio;
|
||||
@@ -41,6 +41,12 @@ public class RadioRange extends AliasID
|
||||
//No arg JAXB constructor
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a radio range of from - to radio values (inclusive) for the specified protocol
|
||||
* @param protocol for the radio range
|
||||
@@ -63,6 +69,7 @@ public class RadioRange extends AliasID
|
||||
public void setMinRadio(int minRadio)
|
||||
{
|
||||
mMinRadio = minRadio;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "max")
|
||||
@@ -74,6 +81,7 @@ public class RadioRange extends AliasID
|
||||
public void setMaxRadio(int maxRadio)
|
||||
{
|
||||
mMaxRadio = maxRadio;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "protocol")
|
||||
@@ -99,7 +107,8 @@ public class RadioRange extends AliasID
|
||||
return radioFormat.getMinimumValidValue() <= mMinRadio &&
|
||||
mMinRadio <= radioFormat.getMaximumValidValue() &&
|
||||
radioFormat.getMinimumValidValue() <= mMaxRadio &&
|
||||
mMaxRadio <= radioFormat.getMaximumValidValue();
|
||||
mMaxRadio <= radioFormat.getMaximumValidValue() &&
|
||||
mMinRadio < mMaxRadio;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
@@ -142,6 +151,13 @@ public class RadioRange extends AliasID
|
||||
return getMinRadio() <= radioValue && radioValue <= getMaxRadio();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean overlaps(AliasID other)
|
||||
{
|
||||
return other instanceof RadioRange && overlaps((RadioRange)other);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Indicates if this talkgroup range overlaps the talkgroup range argument.
|
||||
* @param radioRange to check for overlap
|
||||
@@ -163,4 +179,30 @@ public class RadioRange extends AliasID
|
||||
{
|
||||
return AliasIDType.RADIO_ID_RANGE;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int compareTo(RadioRange other)
|
||||
{
|
||||
if(other == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(getProtocol().equals(other.getProtocol()))
|
||||
{
|
||||
if(getMinRadio() == other.getMinRadio())
|
||||
{
|
||||
return Integer.compare(getMaxRadio(), other.getMaxRadio());
|
||||
}
|
||||
else
|
||||
{
|
||||
return Integer.compare(getMinRadio(), other.getMinRadio());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return getProtocol().compareTo(other.getProtocol());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,273 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.alias.id.radio;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JFormattedTextField;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.text.MaskFormatter;
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.text.ParseException;
|
||||
|
||||
public class RadioRangeEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RadioRangeEditor.class);
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String VALID_CHARACTERS_FOR_ASTERISK_MASKS = "0123456789 ";
|
||||
private MaskFormatter mMaskFormatter = new MaskFormatter();
|
||||
private JComboBox<Protocol> mComboProtocol;
|
||||
private JFormattedTextField mMinRadioField;
|
||||
private JFormattedTextField mMaxRadioField;
|
||||
|
||||
public RadioRangeEditor(AliasID aliasID)
|
||||
{
|
||||
initGUI();
|
||||
|
||||
setItem(aliasID);
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 2", "[right][left]", "[][]"));
|
||||
|
||||
add(new JLabel("Protocol:"));
|
||||
|
||||
mComboProtocol = new JComboBox<Protocol>();
|
||||
|
||||
DefaultComboBoxModel<Protocol> model = new DefaultComboBoxModel<>();
|
||||
|
||||
for(Protocol protocol : Protocol.RADIO_ID_PROTOCOLS)
|
||||
{
|
||||
model.addElement(protocol);
|
||||
}
|
||||
|
||||
mComboProtocol.setModel(model);
|
||||
mComboProtocol.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
Protocol protocol = (Protocol)mComboProtocol.getSelectedItem();
|
||||
updateEditor(protocol);
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
|
||||
add(mComboProtocol);
|
||||
|
||||
add(new JLabel("Min:"));
|
||||
|
||||
mMinRadioField = new JFormattedTextField(mMaskFormatter);
|
||||
mMinRadioField.getDocument().addDocumentListener(this);
|
||||
add(mMinRadioField, "growx,push");
|
||||
|
||||
add(new JLabel("Max:"));
|
||||
|
||||
mMaxRadioField = new JFormattedTextField(mMaskFormatter);
|
||||
mMaxRadioField.getDocument().addDocumentListener(this);
|
||||
add(mMaxRadioField, "growx,push");
|
||||
|
||||
JLabel help = new JLabel("Help ...");
|
||||
help.setForeground(Color.BLUE.brighter());
|
||||
help.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
help.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
RadioFormat radioFormat = RadioFormat.get(getCurrentProtocol());
|
||||
JOptionPane.showMessageDialog(RadioRangeEditor.this,
|
||||
radioFormat.getValidRangeHelpText(), "Help", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
});
|
||||
add(help, "align left");
|
||||
}
|
||||
|
||||
private void updateEditor(Protocol protocol)
|
||||
{
|
||||
RadioFormat mask = RadioFormat.get(protocol);
|
||||
int currentMinValue = 0;
|
||||
int currentMaxValue = 0;
|
||||
RadioRange radioRange = getRadioRange();
|
||||
|
||||
if(radioRange != null)
|
||||
{
|
||||
currentMinValue = radioRange.getMinRadio();
|
||||
currentMaxValue = radioRange.getMaxRadio();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
mMinRadioField.setValue(null);
|
||||
mMaxRadioField.setValue(null);
|
||||
|
||||
mMinRadioField.setToolTipText(mask.getValidRangeHelpText());
|
||||
mMaxRadioField.setToolTipText(mask.getValidRangeHelpText());
|
||||
mMaskFormatter.setMask(mask.getMask());
|
||||
if(mask.getMask().contains("*"))
|
||||
{
|
||||
mMaskFormatter.setValidCharacters(VALID_CHARACTERS_FOR_ASTERISK_MASKS);
|
||||
}
|
||||
|
||||
mMinRadioField.setValue(RadioFormatter.format(protocol, currentMinValue));
|
||||
mMaxRadioField.setValue(RadioFormatter.format(protocol, currentMaxValue));
|
||||
}
|
||||
catch(ParseException pe)
|
||||
{
|
||||
mLog.error("Error applying radio editor mask to mask formatter [" + mask.getMask() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private Protocol getCurrentProtocol()
|
||||
{
|
||||
return mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public RadioRange getRadioRange()
|
||||
{
|
||||
if(getItem() instanceof RadioRange)
|
||||
{
|
||||
return (RadioRange)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(AliasID aliasID)
|
||||
{
|
||||
super.setItem(aliasID);
|
||||
|
||||
RadioRange radioRange = getRadioRange();
|
||||
|
||||
updateEditor(radioRange.getProtocol());
|
||||
|
||||
if(radioRange != null)
|
||||
{
|
||||
mComboProtocol.getModel().setSelectedItem(radioRange.getProtocol());
|
||||
String minFormatted = RadioFormatter.format(radioRange.getProtocol(), radioRange.getMinRadio());
|
||||
mMinRadioField.setValue(minFormatted);
|
||||
String maxFormatted = RadioFormatter.format(radioRange.getProtocol(), radioRange.getMaxRadio());
|
||||
mMaxRadioField.setValue(maxFormatted);
|
||||
}
|
||||
else
|
||||
{
|
||||
mComboProtocol.getModel().setSelectedItem(Protocol.UNKNOWN);
|
||||
mMinRadioField.setValue(RadioFormatter.format(getCurrentProtocol(), 0));
|
||||
mMaxRadioField.setValue(RadioFormatter.format(getCurrentProtocol(), 0));
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
RadioRange radioRange = getRadioRange();
|
||||
|
||||
if(radioRange != null)
|
||||
{
|
||||
Protocol protocol = mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
|
||||
|
||||
if(protocol == null)
|
||||
{
|
||||
protocol = Protocol.UNKNOWN;
|
||||
}
|
||||
|
||||
radioRange.setProtocol(protocol);
|
||||
|
||||
int minValue = -1;
|
||||
int maxValue = -1;
|
||||
|
||||
try
|
||||
{
|
||||
minValue = RadioFormatter.parse(protocol, mMinRadioField.getText());
|
||||
}
|
||||
catch(ParseException pe)
|
||||
{
|
||||
//ignore ... value is still -1 and outside valid value range
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
maxValue = RadioFormatter.parse(protocol, mMaxRadioField.getText());
|
||||
}
|
||||
catch(ParseException pe)
|
||||
{
|
||||
//ignore ... value is still -1 and outside valid value range
|
||||
}
|
||||
|
||||
RadioFormat mask = RadioFormat.get(protocol);
|
||||
|
||||
//Check for valid value within range ... notify user but allow value to persist
|
||||
if(minValue < mask.getMinimumValidValue() || minValue > mask.getMaximumValidValue())
|
||||
{
|
||||
String message = "Invalid minimum value [" + mMinRadioField.getText() + "]. " + protocol.name() +
|
||||
" valid range is [" + mask.getValidRangeDescription() + "]";
|
||||
|
||||
JOptionPane.showMessageDialog(RadioRangeEditor.this, message,
|
||||
"Invalid Radio Value", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
radioRange.setMinRadio(minValue);
|
||||
}
|
||||
|
||||
//Check for valid value within range ... notify user but allow value to persist
|
||||
if(maxValue < mask.getMinimumValidValue() || maxValue > mask.getMaximumValidValue())
|
||||
{
|
||||
String message = "Invalid maximum value [" + mMaxRadioField.getText() + "]. " + protocol.name() +
|
||||
" valid range is [" + mask.getValidRangeDescription() + "]";
|
||||
|
||||
JOptionPane.showMessageDialog(RadioRangeEditor.this, message,
|
||||
"Invalid Radio Value", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
radioRange.setMaxRadio(maxValue);
|
||||
}
|
||||
|
||||
radioRange.setProtocol(protocol);
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,12 @@ public class Record extends AliasID
|
||||
return AliasIDType.RECORD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias.id.record;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
public class RecordEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String HELP_TEXT = "<html>"
|
||||
+ "<h3>Record Audio</h3>"
|
||||
+ "Any audio associated with this alias will be recorded.<br><br>";
|
||||
|
||||
public RecordEditor(AliasID aliasID )
|
||||
{
|
||||
initGUI();
|
||||
|
||||
setItem( aliasID );
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 1", "[]", "[][]" ) );
|
||||
|
||||
add( new JLabel( "Record Audio" ), "wrap" );
|
||||
|
||||
JLabel help = new JLabel( "Help ..." );
|
||||
help.setForeground( Color.BLUE.brighter() );
|
||||
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
|
||||
help.addMouseListener( new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e )
|
||||
{
|
||||
JOptionPane.showMessageDialog( RecordEditor.this,
|
||||
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
|
||||
}
|
||||
} );
|
||||
add( help, "align left" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias.id.status;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.alias.id.AliasIDType;
|
||||
|
||||
public class StatusID extends AliasID
|
||||
{
|
||||
private int mStatus;
|
||||
|
||||
public StatusID()
|
||||
{
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "status")
|
||||
public int getStatus()
|
||||
{
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
public void setStatus(int status)
|
||||
{
|
||||
this.mStatus = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return "Status: " + String.format("%03d", mStatus);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(AliasID id)
|
||||
{
|
||||
boolean retVal = false;
|
||||
|
||||
if(id instanceof StatusID)
|
||||
{
|
||||
StatusID statusId = (StatusID)id;
|
||||
|
||||
retVal = (mStatus == statusId.getStatus());
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
{
|
||||
return AliasIDType.STATUS;
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.alias.id.status;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.MaskFormatter;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
public class StatusIDEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String HELP_TEXT = "<html>"
|
||||
+ "<h3>Status Identifier</h3>"
|
||||
+ "Status numbers are used in some protocols like Fleetsync.<br><br>"
|
||||
+ "The status number is assigned a meaning in the radio. You<br>"
|
||||
+ "can assign a 3 digit status code (use leading zeros) to an<br>"
|
||||
+ "alias where the alias contains the status meaning.<br><br>"
|
||||
+ "<b>Status:</b> <u>001</u> engine start"
|
||||
+ "</html>";
|
||||
|
||||
private JTextField mTextField;
|
||||
|
||||
public StatusIDEditor( AliasID aliasID )
|
||||
{
|
||||
initGUI();
|
||||
|
||||
setItem( aliasID );
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2", "[right][left]", "[][]" ) );
|
||||
|
||||
add( new JLabel( "Status:" ) );
|
||||
|
||||
MaskFormatter formatter = null;
|
||||
|
||||
try
|
||||
{
|
||||
//Mask: 3 digits
|
||||
formatter = new MaskFormatter( "###" );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
//Do nothing, the mask was invalid
|
||||
}
|
||||
|
||||
mTextField = new JFormattedTextField( formatter );
|
||||
mTextField.getDocument().addDocumentListener( this );
|
||||
mTextField.setToolTipText( HELP_TEXT );
|
||||
add( mTextField, "growx,push" );
|
||||
|
||||
JLabel help = new JLabel( "Help ..." );
|
||||
help.setForeground( Color.BLUE.brighter() );
|
||||
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
|
||||
help.addMouseListener( new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e )
|
||||
{
|
||||
JOptionPane.showMessageDialog( StatusIDEditor.this,
|
||||
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
|
||||
}
|
||||
} );
|
||||
add( help, "align left" );
|
||||
}
|
||||
|
||||
public StatusID getStatusID()
|
||||
{
|
||||
if( getItem() instanceof StatusID )
|
||||
{
|
||||
return (StatusID)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem( AliasID aliasID )
|
||||
{
|
||||
super.setItem( aliasID );
|
||||
|
||||
StatusID statusID = getStatusID();
|
||||
|
||||
if( statusID != null )
|
||||
{
|
||||
mTextField.setText( String.format( "%03d", statusID.getStatus() ) );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
StatusID statusID = getStatusID();
|
||||
|
||||
if( statusID != null )
|
||||
{
|
||||
int status = 0;
|
||||
|
||||
try
|
||||
{
|
||||
status = Integer.valueOf( mTextField.getText() );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
//Do nothing, we couldn't parse the value
|
||||
}
|
||||
|
||||
statusID.setStatus( status );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias.id.status;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.alias.id.AliasIDType;
|
||||
|
||||
/**
|
||||
* Unit Status
|
||||
*/
|
||||
public class UnitStatusID extends AliasID
|
||||
{
|
||||
private int mStatus;
|
||||
|
||||
public UnitStatusID()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "status")
|
||||
public int getStatus()
|
||||
{
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
public void setStatus(int status)
|
||||
{
|
||||
mStatus = status;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Unit Status: ").append(String.format("%03d", mStatus));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(AliasID id)
|
||||
{
|
||||
boolean retVal = false;
|
||||
|
||||
if(id instanceof UnitStatusID)
|
||||
{
|
||||
UnitStatusID userStatusId = (UnitStatusID)id;
|
||||
|
||||
retVal = (mStatus == userStatusId.getStatus());
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
{
|
||||
return AliasIDType.UNIT_STATUS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias.id.status;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.alias.id.AliasIDType;
|
||||
|
||||
/**
|
||||
* User Status
|
||||
*/
|
||||
public class UserStatusID extends AliasID
|
||||
{
|
||||
private int mStatus;
|
||||
|
||||
public UserStatusID()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "status")
|
||||
public int getStatus()
|
||||
{
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
public void setStatus(int status)
|
||||
{
|
||||
mStatus = status;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("User Status: ").append(String.format("%03d", mStatus));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(AliasID id)
|
||||
{
|
||||
boolean retVal = false;
|
||||
|
||||
if(id instanceof UserStatusID)
|
||||
{
|
||||
UserStatusID userStatusId = (UserStatusID)id;
|
||||
|
||||
retVal = (mStatus == userStatusId.getStatus());
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
{
|
||||
return AliasIDType.STATUS;
|
||||
}
|
||||
}
|
||||
@@ -24,12 +24,15 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.alias.id.AliasIDType;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Integer talkgroup identifier with protocol.
|
||||
*/
|
||||
public class Talkgroup extends AliasID
|
||||
public class Talkgroup extends AliasID implements Comparable<Talkgroup>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(Talkgroup.class);
|
||||
private Protocol mProtocol = Protocol.UNKNOWN;
|
||||
private int mValue;
|
||||
|
||||
@@ -44,6 +47,12 @@ public class Talkgroup extends AliasID
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "value")
|
||||
public int getValue()
|
||||
{
|
||||
@@ -53,6 +62,7 @@ public class Talkgroup extends AliasID
|
||||
public void setValue(int value)
|
||||
{
|
||||
mValue = value;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "protocol")
|
||||
@@ -64,6 +74,7 @@ public class Talkgroup extends AliasID
|
||||
public void setProtocol(Protocol protocol)
|
||||
{
|
||||
mProtocol = protocol;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,7 +93,18 @@ public class Talkgroup extends AliasID
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("Talkgroup:").append(TalkgroupFormatter.format(mProtocol, mValue));
|
||||
String talkgroup = null;
|
||||
|
||||
try
|
||||
{
|
||||
talkgroup = TalkgroupFormatter.format(mProtocol, mValue);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error formatting Talkgroup Protocol [" + mProtocol + "] value [" + mValue + "]", e);
|
||||
}
|
||||
|
||||
sb.append("Talkgroup:").append(talkgroup != null ? talkgroup : "error");
|
||||
sb.append(" Protocol:").append((mProtocol));
|
||||
|
||||
if(!isValid())
|
||||
@@ -111,4 +133,22 @@ public class Talkgroup extends AliasID
|
||||
{
|
||||
return AliasIDType.TALKGROUP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Talkgroup other)
|
||||
{
|
||||
if(other == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(getProtocol().equals(other.getProtocol()))
|
||||
{
|
||||
return Integer.compare(getValue(), other.getValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
return getProtocol().compareTo(other.getProtocol());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias.id.talkgroup;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JFormattedTextField;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.text.MaskFormatter;
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.text.ParseException;
|
||||
|
||||
public class TalkgroupEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(TalkgroupEditor.class);
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String VALID_CHARACTERS_FOR_ASTERISK_MASKS = "0123456789 ";
|
||||
private MaskFormatter mMaskFormatter = new MaskFormatter();
|
||||
private JComboBox<Protocol> mComboProtocol;
|
||||
private JFormattedTextField mTalkgroupField;
|
||||
|
||||
|
||||
public TalkgroupEditor(AliasID aliasID)
|
||||
{
|
||||
initGUI();
|
||||
setItem(aliasID);
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 2", "[right][left]", "[][]"));
|
||||
|
||||
add(new JLabel("Protocol:"));
|
||||
|
||||
mComboProtocol = new JComboBox<>();
|
||||
|
||||
DefaultComboBoxModel<Protocol> model = new DefaultComboBoxModel<>();
|
||||
|
||||
for(Protocol protocol : Protocol.TALKGROUP_PROTOCOLS)
|
||||
{
|
||||
model.addElement(protocol);
|
||||
}
|
||||
|
||||
mComboProtocol.setModel(model);
|
||||
mComboProtocol.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
Protocol protocol = (Protocol)mComboProtocol.getSelectedItem();
|
||||
updateEditor(protocol);
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
|
||||
add(mComboProtocol);
|
||||
|
||||
add(new JLabel("Value:"));
|
||||
|
||||
mTalkgroupField = new JFormattedTextField(mMaskFormatter);
|
||||
mTalkgroupField.getDocument().addDocumentListener(this);
|
||||
add(mTalkgroupField, "growx,push");
|
||||
|
||||
JLabel help = new JLabel("Help ...");
|
||||
help.setForeground(Color.BLUE.brighter());
|
||||
help.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
help.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
TalkgroupFormat talkgroupFormat = TalkgroupFormat.get(getCurrentProtocol());
|
||||
JOptionPane.showMessageDialog(TalkgroupEditor.this,
|
||||
talkgroupFormat.getValidRangeHelpText(), "Help", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
});
|
||||
add(help, "align left");
|
||||
}
|
||||
|
||||
private void updateEditor(Protocol protocol)
|
||||
{
|
||||
TalkgroupFormat mask = TalkgroupFormat.get(protocol);
|
||||
int currentValue = 0;
|
||||
Talkgroup talkgroup = getTalkgroup();
|
||||
|
||||
if(talkgroup != null)
|
||||
{
|
||||
currentValue = talkgroup.getValue();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
mTalkgroupField.setValue(null);
|
||||
mTalkgroupField.setToolTipText(mask.getValidRangeHelpText());
|
||||
mMaskFormatter.setMask(mask.getMask());
|
||||
if(mask.getMask().contains("*"))
|
||||
{
|
||||
mMaskFormatter.setValidCharacters(VALID_CHARACTERS_FOR_ASTERISK_MASKS);
|
||||
}
|
||||
mTalkgroupField.setValue(TalkgroupFormatter.format(protocol, currentValue));
|
||||
}
|
||||
catch(ParseException pe)
|
||||
{
|
||||
mLog.error("Error applying talkgroup editor mask to mask formatter [" + mask.getMask() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private Protocol getCurrentProtocol()
|
||||
{
|
||||
return mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
|
||||
}
|
||||
|
||||
/**
|
||||
* Current talkgroup alias id
|
||||
*/
|
||||
public Talkgroup getTalkgroup()
|
||||
{
|
||||
if(getItem() instanceof Talkgroup)
|
||||
{
|
||||
return (Talkgroup)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(AliasID aliasID)
|
||||
{
|
||||
super.setItem(aliasID);
|
||||
|
||||
Talkgroup talkgroup = getTalkgroup();
|
||||
|
||||
if(talkgroup != null)
|
||||
{
|
||||
Protocol currentProtocol = getCurrentProtocol();
|
||||
if(currentProtocol == talkgroup.getProtocol())
|
||||
{
|
||||
updateEditor(talkgroup.getProtocol());
|
||||
}
|
||||
mComboProtocol.getModel().setSelectedItem(talkgroup.getProtocol());
|
||||
|
||||
String formatted = TalkgroupFormatter.format(talkgroup.getProtocol(), talkgroup.getValue());
|
||||
mTalkgroupField.setValue(formatted);
|
||||
}
|
||||
else
|
||||
{
|
||||
mComboProtocol.getModel().setSelectedItem(Protocol.UNKNOWN);
|
||||
mTalkgroupField.setValue(TalkgroupFormatter.format(getCurrentProtocol(), 0));
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
Talkgroup talkgroup = getTalkgroup();
|
||||
|
||||
if(talkgroup != null)
|
||||
{
|
||||
Protocol protocol = mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
|
||||
|
||||
if(protocol == null)
|
||||
{
|
||||
protocol = Protocol.UNKNOWN;
|
||||
}
|
||||
|
||||
int value = -1;
|
||||
|
||||
try
|
||||
{
|
||||
value = TalkgroupFormatter.parse(protocol, mTalkgroupField.getText());
|
||||
}
|
||||
catch(ParseException pe)
|
||||
{
|
||||
//ignore ... value is still -1 and outside valid value range
|
||||
}
|
||||
|
||||
TalkgroupFormat mask = TalkgroupFormat.get(protocol);
|
||||
|
||||
//Check for valid value within range ... notify user but allow value to persist
|
||||
if(value < mask.getMinimumValidValue() || value > mask.getMaximumValidValue())
|
||||
{
|
||||
String message = "Invalid value [" + mTalkgroupField.getText() + "]. " + protocol.name() +
|
||||
" valid range is [" + mask.getValidRangeDescription() + "]";
|
||||
|
||||
JOptionPane.showMessageDialog(TalkgroupEditor.this, message, "Invalid Talkgroup Value",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
talkgroup.setValue(value);
|
||||
}
|
||||
|
||||
talkgroup.setProtocol(protocol);
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
}
|
||||
@@ -34,16 +34,20 @@ import io.github.dsheirer.protocol.Protocol;
|
||||
*/
|
||||
public enum TalkgroupFormat
|
||||
{
|
||||
APCO25("********", 0, 0xFFFFFF, "0 to 65,535",
|
||||
APCO25("********", 0, 0xFFFF, "0 to 65,535",
|
||||
"<html>APCO25 talkgroup valid range is 0 to 65,535"),
|
||||
FLEETSYNC("###-####", 1, 0x7FFFFF, "001-0001 to 127-8192",
|
||||
DMR("********", 0, 0xFFFFFF, "0 to 16,777,215",
|
||||
"<html>DMR talkgroup valid range is 0 to 16,777,215"),
|
||||
FLEETSYNC("###-####", 0, 0x7FFFFF, "001-0001 to 127-8192",
|
||||
"<html>Fleetsync valid ranges are 1-127(prefix)<br>and 1-8192(ident) (ie. 001-0001 to 127-8192)"),
|
||||
LTR("##-###", 257, 5375, "01-001 to 20-255",
|
||||
LTR("##-###", 0x101, 0x3FFF, "01-001 to 20-255",
|
||||
"<html>LTR valid ranges are 1-20(repeater) and 1-255(talkgroup) (ie. 01-001 to 20-255)"),
|
||||
MDC1200("*****", 1, 0xFFFF, "1 to 65,535",
|
||||
"MDC-1200 valid value range is 1-65,535"),
|
||||
MPT1327("###-####", 1, 0x7FFFFF, "000-0001 to 127-8192",
|
||||
"<html>MPT-1327 valid ranges are 0-127(prefix)<br>and 1-8192(ident) (ie. 000-0001 to 127-8192)"),
|
||||
NBFM("*****", 1, 0xFFFF, "1 to 65,535",
|
||||
"NBFM valid value range is 1-65,535"),
|
||||
PASSPORT("*****", 1, 0xFFFF, "1 to 65,535",
|
||||
"Passport valid value range is 1-65,535"),
|
||||
UNKNOWN("********", 1, 0xFFFFFF, "1 to 16,777,215",
|
||||
@@ -124,12 +128,13 @@ public enum TalkgroupFormat
|
||||
return FLEETSYNC;
|
||||
case LTR:
|
||||
case LTR_NET:
|
||||
case LTR_STANDARD:
|
||||
return LTR;
|
||||
case MDC1200:
|
||||
return MDC1200;
|
||||
case MPT1327:
|
||||
return MPT1327;
|
||||
case NBFM:
|
||||
return NBFM;
|
||||
case PASSPORT:
|
||||
return PASSPORT;
|
||||
default:
|
||||
|
||||
@@ -20,19 +20,23 @@
|
||||
|
||||
package io.github.dsheirer.alias.id.talkgroup;
|
||||
|
||||
import io.github.dsheirer.preference.identifier.IntegerFormat;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.AbstractIntegerFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.DMRTalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.FleetsyncTalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.IntegerFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.LTRTalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.MDC1200TalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.MPT1327TalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.NBFMTalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.PassportTalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.HashMap;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -41,20 +45,21 @@ import java.util.Map;
|
||||
public class TalkgroupFormatter
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(TalkgroupFormatter.class);
|
||||
private static Map<Protocol,IntegerFormatter> mFormatterMap = new HashMap<>();
|
||||
private static Map<Protocol,AbstractIntegerFormatter> mFormatterMap = new EnumMap<>(Protocol.class);
|
||||
|
||||
static
|
||||
{
|
||||
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.DMR, new DMRTalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.FLEETSYNC, new FleetsyncTalkgroupFormatter());
|
||||
LTRTalkgroupFormatter ltr = new LTRTalkgroupFormatter();
|
||||
mFormatterMap.put(Protocol.LTR, ltr);
|
||||
mFormatterMap.put(Protocol.LTR_NET, ltr);
|
||||
mFormatterMap.put(Protocol.LTR_STANDARD, ltr);
|
||||
mFormatterMap.put(Protocol.MDC1200, new MDC1200TalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.MPT1327, new MPT1327TalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.NBFM, new NBFMTalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.PASSPORT, new PassportTalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.UNKNOWN, new IntegerFormatter());
|
||||
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
|
||||
}
|
||||
|
||||
public TalkgroupFormatter()
|
||||
@@ -66,7 +71,7 @@ public class TalkgroupFormatter
|
||||
*/
|
||||
public static int parse(Protocol protocol, String value) throws ParseException
|
||||
{
|
||||
IntegerFormatter formatter = mFormatterMap.get(protocol);
|
||||
AbstractIntegerFormatter formatter = mFormatterMap.get(protocol);
|
||||
|
||||
if(formatter == null)
|
||||
{
|
||||
@@ -81,7 +86,7 @@ public class TalkgroupFormatter
|
||||
*/
|
||||
public static String format(Protocol protocol, int value)
|
||||
{
|
||||
IntegerFormatter formatter = mFormatterMap.get(protocol);
|
||||
AbstractIntegerFormatter formatter = mFormatterMap.get(protocol);
|
||||
|
||||
if(formatter == null)
|
||||
{
|
||||
@@ -90,4 +95,19 @@ public class TalkgroupFormatter
|
||||
|
||||
return formatter.format(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the integer value to the specified integer format using the protocol specific formatter
|
||||
*/
|
||||
public static String format(Protocol protocol, int value, IntegerFormat format)
|
||||
{
|
||||
AbstractIntegerFormatter formatter = mFormatterMap.get(protocol);
|
||||
|
||||
if(formatter == null)
|
||||
{
|
||||
formatter = mFormatterMap.get(Protocol.UNKNOWN);
|
||||
}
|
||||
|
||||
return formatter.format(value, format);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import io.github.dsheirer.protocol.Protocol;
|
||||
/**
|
||||
* Integer talkgroup identifier range of values with protocol.
|
||||
*/
|
||||
public class TalkgroupRange extends AliasID
|
||||
public class TalkgroupRange extends AliasID implements Comparable<TalkgroupRange>
|
||||
{
|
||||
private Protocol mProtocol = Protocol.UNKNOWN;
|
||||
private int mMinTalkgroup;
|
||||
@@ -52,6 +52,12 @@ public class TalkgroupRange extends AliasID
|
||||
mMaxTalkgroup = maxTalkgroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "min")
|
||||
public int getMinTalkgroup()
|
||||
{
|
||||
@@ -61,6 +67,7 @@ public class TalkgroupRange extends AliasID
|
||||
public void setMinTalkgroup(int minTalkgroup)
|
||||
{
|
||||
mMinTalkgroup = minTalkgroup;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "max")
|
||||
@@ -72,6 +79,7 @@ public class TalkgroupRange extends AliasID
|
||||
public void setMaxTalkgroup(int maxTalkgroup)
|
||||
{
|
||||
mMaxTalkgroup = maxTalkgroup;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "protocol")
|
||||
@@ -83,6 +91,7 @@ public class TalkgroupRange extends AliasID
|
||||
public void setProtocol(Protocol protocol)
|
||||
{
|
||||
mProtocol = protocol;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -97,7 +106,8 @@ public class TalkgroupRange extends AliasID
|
||||
return talkgroupFormat.getMinimumValidValue() <= mMinTalkgroup &&
|
||||
mMinTalkgroup <= talkgroupFormat.getMaximumValidValue() &&
|
||||
talkgroupFormat.getMinimumValidValue() <= mMaxTalkgroup &&
|
||||
mMaxTalkgroup <= talkgroupFormat.getMaximumValidValue();
|
||||
mMaxTalkgroup <= talkgroupFormat.getMaximumValidValue() &&
|
||||
mMinTalkgroup < mMaxTalkgroup;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
@@ -140,6 +150,12 @@ public class TalkgroupRange extends AliasID
|
||||
return getMinTalkgroup() <= talkgroupValue && talkgroupValue <= getMaxTalkgroup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean overlaps(AliasID other)
|
||||
{
|
||||
return other instanceof TalkgroupRange && overlaps((TalkgroupRange)other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this talkgroup range overlaps the talkgroup range argument.
|
||||
* @param talkgroupRange to check for overlap
|
||||
@@ -161,4 +177,29 @@ public class TalkgroupRange extends AliasID
|
||||
{
|
||||
return AliasIDType.TALKGROUP_RANGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(TalkgroupRange other)
|
||||
{
|
||||
if(other == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(getProtocol().equals(other.getProtocol()))
|
||||
{
|
||||
if(getMinTalkgroup() == other.getMinTalkgroup())
|
||||
{
|
||||
return Integer.compare(getMaxTalkgroup(), other.getMaxTalkgroup());
|
||||
}
|
||||
else
|
||||
{
|
||||
return Integer.compare(getMinTalkgroup(), other.getMinTalkgroup());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return getProtocol().compareTo(other.getProtocol());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias.id.talkgroup;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JFormattedTextField;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.text.MaskFormatter;
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.text.ParseException;
|
||||
|
||||
public class TalkgroupRangeEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(TalkgroupRangeEditor.class);
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String VALID_CHARACTERS_FOR_ASTERISK_MASKS = "0123456789 ";
|
||||
private MaskFormatter mMaskFormatter = new MaskFormatter();
|
||||
private JComboBox<Protocol> mComboProtocol;
|
||||
private JFormattedTextField mMinTalkgroupField;
|
||||
private JFormattedTextField mMaxTalkgroupField;
|
||||
|
||||
public TalkgroupRangeEditor(AliasID aliasID)
|
||||
{
|
||||
initGUI();
|
||||
|
||||
setItem(aliasID);
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 2", "[right][left]", "[][]"));
|
||||
|
||||
add(new JLabel("Protocol:"));
|
||||
|
||||
mComboProtocol = new JComboBox<Protocol>();
|
||||
|
||||
DefaultComboBoxModel<Protocol> model = new DefaultComboBoxModel<>();
|
||||
|
||||
for(Protocol protocol : Protocol.TALKGROUP_PROTOCOLS)
|
||||
{
|
||||
model.addElement(protocol);
|
||||
}
|
||||
|
||||
mComboProtocol.setModel(model);
|
||||
mComboProtocol.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
Protocol protocol = (Protocol)mComboProtocol.getSelectedItem();
|
||||
updateEditor(protocol);
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
|
||||
add(mComboProtocol);
|
||||
|
||||
add(new JLabel("Min:"));
|
||||
|
||||
mMinTalkgroupField = new JFormattedTextField(mMaskFormatter);
|
||||
mMinTalkgroupField.getDocument().addDocumentListener(this);
|
||||
add(mMinTalkgroupField, "growx,push");
|
||||
|
||||
add(new JLabel("Max:"));
|
||||
|
||||
mMaxTalkgroupField = new JFormattedTextField(mMaskFormatter);
|
||||
mMaxTalkgroupField.getDocument().addDocumentListener(this);
|
||||
add(mMaxTalkgroupField, "growx,push");
|
||||
|
||||
JLabel help = new JLabel("Help ...");
|
||||
help.setForeground(Color.BLUE.brighter());
|
||||
help.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
help.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
TalkgroupFormat talkgroupFormat = TalkgroupFormat.get(getCurrentProtocol());
|
||||
JOptionPane.showMessageDialog(TalkgroupRangeEditor.this,
|
||||
talkgroupFormat.getValidRangeHelpText(), "Help", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
});
|
||||
add(help, "align left");
|
||||
}
|
||||
|
||||
private void updateEditor(Protocol protocol)
|
||||
{
|
||||
TalkgroupFormat mask = TalkgroupFormat.get(protocol);
|
||||
int currentMinValue = 0;
|
||||
int currentMaxValue = 0;
|
||||
TalkgroupRange talkgroupRange = getTalkgroupRange();
|
||||
|
||||
if(talkgroupRange != null)
|
||||
{
|
||||
currentMinValue = talkgroupRange.getMinTalkgroup();
|
||||
currentMaxValue = talkgroupRange.getMaxTalkgroup();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
mMinTalkgroupField.setValue(null);
|
||||
mMaxTalkgroupField.setValue(null);
|
||||
|
||||
mMinTalkgroupField.setToolTipText(mask.getValidRangeHelpText());
|
||||
mMaxTalkgroupField.setToolTipText(mask.getValidRangeHelpText());
|
||||
mMaskFormatter.setMask(mask.getMask());
|
||||
if(mask.getMask().contains("*"))
|
||||
{
|
||||
mMaskFormatter.setValidCharacters(VALID_CHARACTERS_FOR_ASTERISK_MASKS);
|
||||
}
|
||||
|
||||
mMinTalkgroupField.setValue(TalkgroupFormatter.format(protocol, currentMinValue));
|
||||
mMaxTalkgroupField.setValue(TalkgroupFormatter.format(protocol, currentMaxValue));
|
||||
}
|
||||
catch(ParseException pe)
|
||||
{
|
||||
mLog.error("Error applying talkgroup editor mask to mask formatter [" + mask.getMask() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private Protocol getCurrentProtocol()
|
||||
{
|
||||
return mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public TalkgroupRange getTalkgroupRange()
|
||||
{
|
||||
if(getItem() instanceof TalkgroupRange)
|
||||
{
|
||||
return (TalkgroupRange)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(AliasID aliasID)
|
||||
{
|
||||
super.setItem(aliasID);
|
||||
|
||||
TalkgroupRange talkgroupRange = getTalkgroupRange();
|
||||
|
||||
updateEditor(talkgroupRange.getProtocol());
|
||||
|
||||
if(talkgroupRange != null)
|
||||
{
|
||||
mComboProtocol.getModel().setSelectedItem(talkgroupRange.getProtocol());
|
||||
String minFormatted = TalkgroupFormatter.format(talkgroupRange.getProtocol(), talkgroupRange.getMinTalkgroup());
|
||||
mMinTalkgroupField.setValue(minFormatted);
|
||||
String maxFormatted = TalkgroupFormatter.format(talkgroupRange.getProtocol(), talkgroupRange.getMaxTalkgroup());
|
||||
mMaxTalkgroupField.setValue(maxFormatted);
|
||||
}
|
||||
else
|
||||
{
|
||||
mComboProtocol.getModel().setSelectedItem(Protocol.UNKNOWN);
|
||||
mMinTalkgroupField.setValue(TalkgroupFormatter.format(getCurrentProtocol(), 0));
|
||||
mMaxTalkgroupField.setValue(TalkgroupFormatter.format(getCurrentProtocol(), 0));
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
TalkgroupRange talkgroupRange = getTalkgroupRange();
|
||||
|
||||
if(talkgroupRange != null)
|
||||
{
|
||||
Protocol protocol = mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
|
||||
|
||||
if(protocol == null)
|
||||
{
|
||||
protocol = Protocol.UNKNOWN;
|
||||
}
|
||||
|
||||
talkgroupRange.setProtocol(protocol);
|
||||
|
||||
int minValue = -1;
|
||||
int maxValue = -1;
|
||||
|
||||
try
|
||||
{
|
||||
minValue = TalkgroupFormatter.parse(protocol, mMinTalkgroupField.getText());
|
||||
}
|
||||
catch(ParseException pe)
|
||||
{
|
||||
//ignore ... value is still -1 and outside valid value range
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
maxValue = TalkgroupFormatter.parse(protocol, mMaxTalkgroupField.getText());
|
||||
}
|
||||
catch(ParseException pe)
|
||||
{
|
||||
//ignore ... value is still -1 and outside valid value range
|
||||
}
|
||||
|
||||
TalkgroupFormat mask = TalkgroupFormat.get(protocol);
|
||||
|
||||
//Check for valid value within range ... notify user but allow value to persist
|
||||
if(minValue < mask.getMinimumValidValue() || minValue > mask.getMaximumValidValue())
|
||||
{
|
||||
String message = "Invalid minimum value [" + mMinTalkgroupField.getText() + "]. " + protocol.name() +
|
||||
" valid range is [" + mask.getValidRangeDescription() + "]";
|
||||
|
||||
JOptionPane.showMessageDialog(TalkgroupRangeEditor.this, message,
|
||||
"Invalid Talkgroup Value", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
talkgroupRange.setMinTalkgroup(minValue);
|
||||
}
|
||||
|
||||
//Check for valid value within range ... notify user but allow value to persist
|
||||
if(maxValue < mask.getMinimumValidValue() || maxValue > mask.getMaximumValidValue())
|
||||
{
|
||||
String message = "Invalid maximum value [" + mMaxTalkgroupField.getText() + "]. " + protocol.name() +
|
||||
" valid range is [" + mask.getValidRangeDescription() + "]";
|
||||
|
||||
JOptionPane.showMessageDialog(TalkgroupRangeEditor.this, message,
|
||||
"Invalid Talkgroup Value", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
talkgroupRange.setMaxTalkgroup(maxValue);
|
||||
}
|
||||
|
||||
talkgroupRange.setProtocol(protocol);
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
}
|
||||
150
src/main/java/io/github/dsheirer/alias/id/tone/TonesID.java
Normal file
150
src/main/java/io/github/dsheirer/alias/id/tone/TonesID.java
Normal file
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.alias.id.tone;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.alias.id.AliasIDType;
|
||||
import io.github.dsheirer.identifier.tone.Tone;
|
||||
import io.github.dsheirer.identifier.tone.ToneSequence;
|
||||
import javafx.collections.ListChangeListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Tone sequence alias identifier. This is used for matching tone sequences produced by the AMBE audio CODEC. Some
|
||||
* examples include DTMF dialed numbers, KNOX tones, 2-Tone, Tone-outs, etc.
|
||||
*/
|
||||
public class TonesID extends AliasID implements ListChangeListener<Tone>
|
||||
{
|
||||
private ToneSequence mToneSequence;
|
||||
|
||||
public TonesID()
|
||||
{
|
||||
setToneSequence(new ToneSequence());
|
||||
//Empty serialization constructor
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance with the specified tone sequence
|
||||
*/
|
||||
public TonesID(ToneSequence toneSequence)
|
||||
{
|
||||
setToneSequence(toneSequence);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
{
|
||||
return AliasIDType.TONES;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of tones that define this identifier
|
||||
*/
|
||||
|
||||
public ToneSequence getToneSequence()
|
||||
{
|
||||
return mToneSequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tone sequence(s) for this ID
|
||||
*/
|
||||
public void setToneSequence(ToneSequence toneSequence)
|
||||
{
|
||||
if(mToneSequence != null)
|
||||
{
|
||||
mToneSequence.tonesProperty().removeListener(this);
|
||||
}
|
||||
|
||||
mToneSequence = toneSequence;
|
||||
mToneSequence.tonesProperty().addListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the other id is a tones identifier and has the exact same sequence of tones. However, it does not
|
||||
* compare each of the tone duration value.
|
||||
* @param id to check for match
|
||||
* @return true if the identifiers match
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(AliasID id)
|
||||
{
|
||||
boolean match = true;
|
||||
|
||||
if(mToneSequence.getTones().isEmpty() || !(id instanceof TonesID))
|
||||
{
|
||||
match = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Tone> otherTones = ((TonesID)id).getToneSequence().getTones();
|
||||
List<Tone> thisTones = mToneSequence.getTones();
|
||||
|
||||
if(thisTones.size() == otherTones.size())
|
||||
{
|
||||
for(int x = 0; x < thisTones.size(); x++)
|
||||
{
|
||||
if(thisTones.get(x).getAmbeTone() != otherTones.get(x).getAmbeTone())
|
||||
{
|
||||
match = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
match = false;
|
||||
}
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
return getToneSequence() != null && getToneSequence().hasTones();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
//This is not an audio identifier in the context that this method is used for.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
if(getToneSequence() != null)
|
||||
{
|
||||
return "Tones: " + getToneSequence().toString();
|
||||
}
|
||||
|
||||
return "Tones: (empty)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(Change<? extends Tone> c)
|
||||
{
|
||||
updateValueProperty();
|
||||
}
|
||||
}
|
||||
@@ -36,26 +36,48 @@ import org.slf4j.LoggerFactory;
|
||||
public abstract class AbstractAudioModule extends Module implements IAudioSegmentProvider, IdentifierUpdateListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AbstractAudioModule.class);
|
||||
private static final int MAX_SEGMENT_AUDIO_SAMPLE_LENGTH = 8000 * 60 * 1; //8 kHz - 1 minute
|
||||
|
||||
public static final long DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH = 60 * 8000; // 1 minute @ 8kHz
|
||||
public static final int DEFAULT_TIMESLOT = 0;
|
||||
private final int mMaxSegmentAudioSampleLength;
|
||||
private Listener<AudioSegment> mAudioSegmentListener;
|
||||
protected MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
|
||||
protected MutableIdentifierCollection mIdentifierCollection;
|
||||
private Broadcaster<IdentifierUpdateNotification> mIdentifierUpdateNotificationBroadcaster = new Broadcaster<>();
|
||||
private AliasList mAliasList;
|
||||
private AudioSegment mAudioSegment;
|
||||
private int mAudioSampleCount = 0;
|
||||
private boolean mRecordAudioOverride;
|
||||
private int mTimeslot;
|
||||
|
||||
/**
|
||||
* Constructs an abstract audio module
|
||||
*
|
||||
* @param aliasList for aliasing identifiers
|
||||
* @param maxSegmentAudioSampleLength in milliseconds
|
||||
*/
|
||||
public AbstractAudioModule(AliasList aliasList)
|
||||
public AbstractAudioModule(AliasList aliasList, int timeslot, long maxSegmentAudioSampleLength)
|
||||
{
|
||||
mAliasList = aliasList;
|
||||
mMaxSegmentAudioSampleLength = (int)(maxSegmentAudioSampleLength * 8); //Convert milliseconds to samples
|
||||
mTimeslot = timeslot;
|
||||
mIdentifierCollection = new MutableIdentifierCollection(getTimeslot());
|
||||
mIdentifierUpdateNotificationBroadcaster.addListener(mIdentifierCollection);
|
||||
}
|
||||
|
||||
protected abstract int getTimeslot();
|
||||
/**
|
||||
* Constructs an abstract audio module with a default maximum audio segment length and a default timeslot 0.
|
||||
*/
|
||||
public AbstractAudioModule(AliasList aliasList)
|
||||
{
|
||||
this(aliasList, DEFAULT_TIMESLOT, DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Timeslot for this audio module
|
||||
*/
|
||||
protected int getTimeslot()
|
||||
{
|
||||
return mTimeslot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the current audio segment
|
||||
@@ -68,6 +90,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
||||
{
|
||||
mAudioSegment.completeProperty().set(true);
|
||||
mIdentifierUpdateNotificationBroadcaster.removeListener(mAudioSegment);
|
||||
mAudioSegment.decrementConsumerCount();
|
||||
mAudioSegment = null;
|
||||
}
|
||||
}
|
||||
@@ -90,6 +113,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
||||
if(mAudioSegment == null)
|
||||
{
|
||||
mAudioSegment = new AudioSegment(mAliasList, getTimeslot());
|
||||
mAudioSegment.incrementConsumerCount();
|
||||
mAudioSegment.addIdentifiers(mIdentifierCollection.getIdentifiers());
|
||||
mIdentifierUpdateNotificationBroadcaster.addListener(mAudioSegment);
|
||||
|
||||
@@ -117,7 +141,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
||||
|
||||
//If the current segment exceeds the max samples length, close it so that a new segment gets generated
|
||||
//and then link the segments together
|
||||
if(mAudioSampleCount >= MAX_SEGMENT_AUDIO_SAMPLE_LENGTH)
|
||||
if(mAudioSampleCount >= mMaxSegmentAudioSampleLength)
|
||||
{
|
||||
AudioSegment previous = getAudioSegment();
|
||||
closeAudioSegment();
|
||||
@@ -125,8 +149,15 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
||||
audioSegment.linkTo(previous);
|
||||
}
|
||||
|
||||
audioSegment.addAudio(audioBuffer);
|
||||
mAudioSampleCount += audioBuffer.length;
|
||||
try
|
||||
{
|
||||
audioSegment.addAudio(audioBuffer);
|
||||
mAudioSampleCount += audioBuffer.length;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
closeAudioSegment();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
@@ -5,81 +24,107 @@ import javax.sound.sampled.DataLine;
|
||||
import javax.sound.sampled.Line;
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
|
||||
/**
|
||||
* Audio formats
|
||||
*/
|
||||
public class AudioFormats
|
||||
{
|
||||
public static final AudioFormat.Encoding IMBE_ENCODING =
|
||||
new AudioFormat.Encoding( "IMBE" );
|
||||
|
||||
public static final boolean LITTLE_ENDIAN = false;
|
||||
public static final boolean BIG_ENDIAN = true;
|
||||
|
||||
public static final float IMBE_FRAME_RATE = 50;
|
||||
public static final float IMBE_SAMPLE_RATE = 50;
|
||||
public static final float PCM_8KHZ_RATE = 8000;
|
||||
public static final float PCM_48KHZ_RATE = 48000;
|
||||
|
||||
public static final int IMBE_FRAME_SIZE_BYTES = 18;
|
||||
public static final int IMBE_SAMPLE_SIZE_BITS = 144;
|
||||
|
||||
public static final float PCM_8_KHZ_RATE = 8000;
|
||||
public static final float PCM_16_KHZ_RATE = 16000;
|
||||
public static final float PCM_22050_HZ_RATE = 22050;
|
||||
public static final float PCM_44100_HZ_RATE = 44100;
|
||||
|
||||
public static final int ONE_CHANNEL = 1;
|
||||
public static final int TWO_CHANNELS = 2;
|
||||
public static final int PCM_SAMPLE_SIZE_BITS = 16;
|
||||
public static final int PCM_FRAME_SIZE_BYTES_MONO = 2;
|
||||
public static final int PCM_FRAME_SIZE_BYTES_STEREO = 4;
|
||||
|
||||
public static AudioFormat IMBE_AUDIO_FORMAT =
|
||||
new AudioFormat( IMBE_ENCODING,
|
||||
IMBE_SAMPLE_RATE,
|
||||
IMBE_SAMPLE_SIZE_BITS,
|
||||
ONE_CHANNEL,
|
||||
IMBE_FRAME_SIZE_BYTES,
|
||||
IMBE_FRAME_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_8KHZ_16BITS_MONO =
|
||||
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_8KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_MONO,
|
||||
PCM_8KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
public static final int PCM_SAMPLE_SIZE_16_BITS = 16;
|
||||
public static final int PCM_SAMPLE_SIZE_32_BITS = 32;
|
||||
public static final int PCM_FRAME_SIZE_BYTES_16_BIT_MONO = 2;
|
||||
public static final int PCM_FRAME_SIZE_BYTES_32_BIT_MONO = 4;
|
||||
public static final int PCM_FRAME_SIZE_BYTES_16_BIT_STEREO = 4;
|
||||
|
||||
public static AudioFormat PCM_SIGNED_8KHZ_16BITS_STEREO =
|
||||
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_8KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_BITS,
|
||||
TWO_CHANNELS,
|
||||
PCM_FRAME_SIZE_BYTES_STEREO,
|
||||
PCM_8KHZ_RATE,
|
||||
public static AudioFormat PCM_SIGNED_8000_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_8_KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_32_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
|
||||
PCM_8_KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_8000_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_8_KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_16_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
|
||||
PCM_8_KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_16000_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_16_KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_16_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
|
||||
PCM_16_KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_16000_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_16_KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_32_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
|
||||
PCM_16_KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_22050_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_22050_HZ_RATE,
|
||||
PCM_SAMPLE_SIZE_16_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
|
||||
PCM_22050_HZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_22050_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_22050_HZ_RATE,
|
||||
PCM_SAMPLE_SIZE_32_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
|
||||
PCM_22050_HZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_44100_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_44100_HZ_RATE,
|
||||
PCM_SAMPLE_SIZE_16_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
|
||||
PCM_44100_HZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_44100_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_44100_HZ_RATE,
|
||||
PCM_SAMPLE_SIZE_32_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
|
||||
PCM_44100_HZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_8000_HZ_16BITS_STEREO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_8_KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_16_BITS,
|
||||
TWO_CHANNELS,
|
||||
PCM_FRAME_SIZE_BYTES_16_BIT_STEREO,
|
||||
PCM_8_KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_48KHZ_16BITS_MONO =
|
||||
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_48KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_MONO,
|
||||
PCM_48KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_48KHZ_16BITS_STEREO =
|
||||
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_48KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_BITS,
|
||||
TWO_CHANNELS,
|
||||
PCM_FRAME_SIZE_BYTES_STEREO,
|
||||
PCM_48KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
/**
|
||||
* Source Data Line Info for a 48 kHz, 16-bits signed PCM one channel
|
||||
*/
|
||||
public static final Line.Info MONO_SOURCE_DATALINE_INFO =
|
||||
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8KHZ_16BITS_MONO);
|
||||
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8000_HZ_16_BIT_MONO);
|
||||
|
||||
/**
|
||||
* Source Data Line Info for a 48 kHz, 16-bits signed PCM two channels
|
||||
*/
|
||||
public static final Line.Info STEREO_SOURCE_DATALINE_INFO =
|
||||
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8KHZ_16BITS_STEREO);
|
||||
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8000_HZ_16BITS_STEREO);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
@@ -25,13 +22,13 @@ import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.audio.squelch.ISquelchStateListener;
|
||||
import io.github.dsheirer.audio.squelch.SquelchState;
|
||||
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
|
||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.sample.real.IRealBufferListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -41,8 +38,8 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* Incorporates audio squelch state listener to control if audio packets are broadcast or ignored.
|
||||
*/
|
||||
public class AudioModule extends AbstractAudioModule implements ISquelchStateListener, IReusableBufferListener,
|
||||
Listener<ReusableFloatBuffer>
|
||||
public class AudioModule extends AbstractAudioModule implements ISquelchStateListener, IRealBufferListener,
|
||||
Listener<float[]>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
|
||||
private static float[] sHighPassFilterCoefficients;
|
||||
@@ -73,10 +70,21 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
||||
}
|
||||
}
|
||||
|
||||
private RealFIRFilter2 mHighPassFilter = new RealFIRFilter2(sHighPassFilterCoefficients);
|
||||
private IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
|
||||
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
||||
private SquelchState mSquelchState = SquelchState.SQUELCH;
|
||||
|
||||
/**
|
||||
* Creates an Audio Module.
|
||||
*
|
||||
* @param aliasList for aliasing identifiers
|
||||
* @param maxAudioSegmentLength in milliseconds
|
||||
*/
|
||||
public AudioModule(AliasList aliasList, int timeslot, long maxAudioSegmentLength)
|
||||
{
|
||||
super(aliasList, timeslot, maxAudioSegmentLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an Audio Module.
|
||||
*/
|
||||
@@ -91,13 +99,6 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
removeAudioSegmentListener();
|
||||
mSquelchStateListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
@@ -116,22 +117,17 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableFloatBuffer reusableFloatBuffer)
|
||||
public void receive(float[] audioBuffer)
|
||||
{
|
||||
if(mSquelchState == SquelchState.UNSQUELCH)
|
||||
{
|
||||
ReusableFloatBuffer buffer = mHighPassFilter.filter(reusableFloatBuffer);
|
||||
addAudio(buffer.getSamples());
|
||||
buffer.decrementUserCount();
|
||||
}
|
||||
else
|
||||
{
|
||||
reusableFloatBuffer.decrementUserCount();
|
||||
audioBuffer = mHighPassFilter.filter(audioBuffer);
|
||||
addAudio(audioBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ReusableFloatBuffer> getReusableBufferListener()
|
||||
public Listener<float[]> getBufferListener()
|
||||
{
|
||||
//Redirect received reusable buffers to the receive(buffer) method
|
||||
return this;
|
||||
|
||||
@@ -65,6 +65,7 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AudioSegment.class);
|
||||
private BooleanProperty mComplete = new SimpleBooleanProperty(false);
|
||||
private BooleanProperty mDuplicate = new SimpleBooleanProperty(false);
|
||||
private BooleanProperty mRecordAudio = new SimpleBooleanProperty(false);
|
||||
private IntegerProperty mMonitorPriority = new SimpleIntegerProperty(Priority.DEFAULT_PRIORITY);
|
||||
private ObservableSet<BroadcastChannel> mBroadcastChannels = FXCollections.observableSet(new HashSet<>());
|
||||
@@ -74,6 +75,7 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
||||
private AtomicInteger mConsumerCount = new AtomicInteger();
|
||||
private AliasList mAliasList;
|
||||
private long mStartTimestamp = System.currentTimeMillis();
|
||||
private long mSampleCount = 0;
|
||||
private boolean mDisposing = false;
|
||||
private AudioSegment mLinkedAudioSegment;
|
||||
private int mTimeslot;
|
||||
@@ -107,6 +109,24 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
||||
return mStartTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* End timestamp as calculated from start timestamp and current sample count
|
||||
*/
|
||||
public long getEndTimestamp()
|
||||
{
|
||||
return mStartTimestamp + getDuration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Current duration of this audio segment. Note: this is a dynamic value until the complete property is set to true.
|
||||
*
|
||||
* @return duration in milliseconds
|
||||
*/
|
||||
public long getDuration()
|
||||
{
|
||||
return (mSampleCount / 8); //8 kHz audio generates 8 samples per millisecond
|
||||
}
|
||||
|
||||
/**
|
||||
* The complete property is used by the audio segment producer to signal that the segment is complete and no
|
||||
* additional audio or identifiers will be added to the segment.
|
||||
@@ -118,6 +138,15 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
||||
return mComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate call audio property. This flag is set to true whenever a duplicate call detection function detects
|
||||
* an audio segment is a duplicate.
|
||||
*/
|
||||
public BooleanProperty duplicateProperty()
|
||||
{
|
||||
return mDuplicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* An observable list of broadcast channels for this audio segment. Broadcast channels are added to this segment
|
||||
* across the life-cycle of the segment. As each new alias identifier is added to the segment, any broadcast
|
||||
@@ -347,6 +376,7 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
||||
}
|
||||
|
||||
mAudioBuffers.add(audioBuffer);
|
||||
mSampleCount += audioBuffer.length;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -384,10 +414,7 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
||||
}
|
||||
|
||||
//Add all broadcast channels for the alias ... let the set handle duplication.
|
||||
for(BroadcastChannel broadcastChannel: alias.getBroadcastChannels())
|
||||
{
|
||||
mBroadcastChannels.add(broadcastChannel);
|
||||
}
|
||||
mBroadcastChannels.addAll(alias.getBroadcastChannels());
|
||||
|
||||
//Only assign a playback priority if it is lower priority than the current setting.
|
||||
int playbackPriority = alias.getPlaybackPriority();
|
||||
@@ -399,6 +426,23 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this audio segment has been flagged as a duplicate audio call
|
||||
*/
|
||||
public boolean isDuplicate()
|
||||
{
|
||||
return mDuplicate.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the duplicate audio call flag for this audio segment
|
||||
* @param duplicate true if this is a duplicate audio segment
|
||||
*/
|
||||
public void setDuplicate(boolean duplicate)
|
||||
{
|
||||
mDuplicate.set(duplicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes identifier update notifications and updates audio segment properties using the associated aliases
|
||||
* from the alias list.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* 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
|
||||
@@ -20,7 +20,6 @@ package io.github.dsheirer.audio;
|
||||
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.AbstractReusableBuffer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -40,7 +39,7 @@ public class AudioSegmentBroadcaster<T extends AudioSegment> extends Broadcaster
|
||||
@Override
|
||||
public void broadcast(T audioSegment)
|
||||
{
|
||||
for(Listener<T> listener : mListeners)
|
||||
for(Listener<T> listener : getListeners())
|
||||
{
|
||||
audioSegment.incrementConsumerCount();
|
||||
listener.receive(audioSegment);
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* 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 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
* 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 3 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.
|
||||
* 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, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
import io.github.dsheirer.sample.ConversionUtils;
|
||||
@@ -21,7 +24,10 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.Float;
|
||||
import java.lang.Math;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AudioUtils
|
||||
@@ -76,4 +82,42 @@ public class AudioUtils
|
||||
return stream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize audio samples
|
||||
*/
|
||||
public static List<float[]> normalize(List<float[]> audioBuffers)
|
||||
{
|
||||
float targetMax = 0.95f; // -0.44dBFS
|
||||
float max = 0.1f; // Initialize at 0.1 (-20dBFS) to limit maximum gain
|
||||
|
||||
for(float[] audioBuffer: audioBuffers)
|
||||
{
|
||||
for(float value: audioBuffer)
|
||||
{
|
||||
float newMax = Math.max(max, Math.abs(value));
|
||||
if(!Float.isNaN(newMax))
|
||||
{
|
||||
max = newMax;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(targetMax == max)
|
||||
{
|
||||
return audioBuffers;
|
||||
}
|
||||
|
||||
float gain = targetMax / max;
|
||||
|
||||
for(float[] audioBuffer: audioBuffers)
|
||||
{
|
||||
for(int i=0;i<audioBuffer.length;i++)
|
||||
{
|
||||
audioBuffer[i] *= gain;
|
||||
}
|
||||
}
|
||||
|
||||
return audioBuffers;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.duplicate.DuplicateCallDetectionPreference;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Detects duplicate calls that occur within the same system. This detector is thread safe for the receive() method.
|
||||
*
|
||||
* Note: system in this context refers to the system name value that is used in channel configurations. All decoder
|
||||
* channels must share the same system name for call duplication detection.
|
||||
*/
|
||||
public class DuplicateCallDetector implements Listener<AudioSegment>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallDetector.class);
|
||||
private DuplicateCallDetectionPreference mDuplicateCallDetectionPreference;
|
||||
private Map<String,SystemDuplicateCallDetector> mDetectorMap = new HashMap();
|
||||
|
||||
public DuplicateCallDetector(UserPreferences userPreferences)
|
||||
{
|
||||
mDuplicateCallDetectionPreference = userPreferences.getDuplicateCallDetectionPreference();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(AudioSegment audioSegment)
|
||||
{
|
||||
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionEnabled())
|
||||
{
|
||||
Identifier identifier = audioSegment.getIdentifierCollection()
|
||||
.getIdentifier(IdentifierClass.CONFIGURATION, Form.SYSTEM, Role.ANY);
|
||||
|
||||
if(identifier instanceof SystemConfigurationIdentifier)
|
||||
{
|
||||
String system = ((SystemConfigurationIdentifier)identifier).getValue();
|
||||
|
||||
synchronized(mDetectorMap)
|
||||
{
|
||||
SystemDuplicateCallDetector detector = mDetectorMap.get(system);
|
||||
|
||||
if(detector == null)
|
||||
{
|
||||
detector = new SystemDuplicateCallDetector();
|
||||
mDetectorMap.put(system, detector);
|
||||
}
|
||||
|
||||
detector.add(audioSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SystemDuplicateCallDetector
|
||||
{
|
||||
private LinkedTransferQueue<AudioSegment> mAudioSegmentQueue = new LinkedTransferQueue<>();
|
||||
private List<AudioSegment> mAudioSegments = new ArrayList<>();
|
||||
private AtomicBoolean mMonitoring = new AtomicBoolean();
|
||||
private ScheduledFuture<?> mProcessorFuture;
|
||||
|
||||
public SystemDuplicateCallDetector()
|
||||
{
|
||||
}
|
||||
|
||||
public void add(AudioSegment audioSegment)
|
||||
{
|
||||
//Block on audio segment queue so that we don't interfere with monitoring shutdown
|
||||
synchronized(mAudioSegmentQueue)
|
||||
{
|
||||
mAudioSegmentQueue.add(audioSegment);
|
||||
startMonitoring();
|
||||
}
|
||||
}
|
||||
|
||||
private void startMonitoring()
|
||||
{
|
||||
if(mMonitoring.compareAndSet(false, true))
|
||||
{
|
||||
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(() -> process(),
|
||||
0, 25, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopMonitoring()
|
||||
{
|
||||
if(mMonitoring.compareAndSet(true, false))
|
||||
{
|
||||
if(mProcessorFuture != null)
|
||||
{
|
||||
mProcessorFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if either the TO or FROM integer identifiers for the audio segments are the same
|
||||
* @param segment1 to compare
|
||||
* @param segment2 to compare
|
||||
* @return true if integer identifiers are the same
|
||||
*/
|
||||
private boolean isDuplicate(AudioSegment segment1, AudioSegment segment2)
|
||||
{
|
||||
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionByTalkgroupEnabled())
|
||||
{
|
||||
//Step 1 check for duplicate TO values
|
||||
List<Identifier> to1 = segment1.getIdentifierCollection().getIdentifiers(Role.TO);
|
||||
List<Identifier> to2 = segment2.getIdentifierCollection().getIdentifiers(Role.TO);
|
||||
|
||||
if(isDuplicate(to1, to2))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionByRadioEnabled())
|
||||
{
|
||||
//Step 2 check for duplicate FROM values
|
||||
List<Identifier> from1 = segment1.getIdentifierCollection().getIdentifiers(Role.FROM);
|
||||
List<Identifier> from2 = segment2.getIdentifierCollection().getIdentifiers(Role.FROM);
|
||||
|
||||
return isDuplicate(from1, from2);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks both lists of identifiers to determine if there are talkgroups or radio identifiers that are
|
||||
* the same in both lists. Note: talkgroup check compares both talkgroups and patch groups.
|
||||
*
|
||||
* @param identifiers1
|
||||
* @param identifiers2
|
||||
* @return
|
||||
*/
|
||||
private boolean isDuplicate(List<Identifier> identifiers1, List<Identifier> identifiers2)
|
||||
{
|
||||
for(Identifier identifier1: identifiers1)
|
||||
{
|
||||
if(identifier1 instanceof TalkgroupIdentifier)
|
||||
{
|
||||
int talkgroup1 = ((TalkgroupIdentifier)identifier1).getValue();
|
||||
|
||||
for(Identifier identifier2: identifiers2)
|
||||
{
|
||||
if(identifier2 instanceof TalkgroupIdentifier &&
|
||||
((TalkgroupIdentifier)identifier2).getValue() == talkgroup1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if(identifier2 instanceof PatchGroupIdentifier &&
|
||||
((PatchGroupIdentifier)identifier2).getValue().getPatchGroup().getValue() == talkgroup1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(identifier1 instanceof PatchGroupIdentifier)
|
||||
{
|
||||
int talkgroup1 = ((PatchGroupIdentifier)identifier1).getValue().getPatchGroup().getValue();
|
||||
|
||||
for(Identifier identifier2: identifiers2)
|
||||
{
|
||||
if(identifier2 instanceof TalkgroupIdentifier &&
|
||||
((TalkgroupIdentifier)identifier2).getValue() == talkgroup1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if(identifier2 instanceof PatchGroupIdentifier &&
|
||||
((PatchGroupIdentifier)identifier2).getValue().getPatchGroup().getValue() == talkgroup1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(identifier1 instanceof RadioIdentifier)
|
||||
{
|
||||
int radio1 = ((RadioIdentifier)identifier1).getValue();
|
||||
|
||||
for(Identifier identifier2: identifiers2)
|
||||
{
|
||||
if(identifier2 instanceof RadioIdentifier && ((RadioIdentifier)identifier2).getValue() == radio1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes audio segments to detect duplicates
|
||||
*/
|
||||
private void process()
|
||||
{
|
||||
try
|
||||
{
|
||||
//Transfer in newly arrived audio segments
|
||||
mAudioSegmentQueue.drainTo(mAudioSegments);
|
||||
|
||||
//Remove any completed audio segments.
|
||||
mAudioSegments.removeIf(audioSegment -> {
|
||||
boolean complete = audioSegment.completeProperty().get();
|
||||
|
||||
if(complete)
|
||||
{
|
||||
audioSegment.decrementConsumerCount();
|
||||
}
|
||||
|
||||
return complete;
|
||||
});
|
||||
|
||||
//Only check for duplicates if there is more than one call
|
||||
if(mAudioSegments.size() > 1)
|
||||
{
|
||||
List<AudioSegment> duplicates = new ArrayList<>();
|
||||
|
||||
int currentIndex = 0;
|
||||
while(currentIndex < mAudioSegments.size() - 1)
|
||||
{
|
||||
AudioSegment current = mAudioSegments.get(currentIndex);
|
||||
|
||||
if(!current.isDuplicate())
|
||||
{
|
||||
int checkIndex = currentIndex + 1;
|
||||
|
||||
while(checkIndex < mAudioSegments.size())
|
||||
{
|
||||
AudioSegment toCheck = mAudioSegments.get(checkIndex);
|
||||
|
||||
if(!toCheck.isDuplicate())
|
||||
{
|
||||
if(isDuplicate(current, toCheck))
|
||||
{
|
||||
toCheck.setDuplicate(true);
|
||||
toCheck.decrementConsumerCount();
|
||||
duplicates.add(toCheck);
|
||||
}
|
||||
}
|
||||
|
||||
checkIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
}
|
||||
|
||||
mAudioSegments.removeAll(duplicates);
|
||||
}
|
||||
|
||||
//Finally, if the audio segment queue is empty, shutdown montitoring until a new segment arrives
|
||||
if(mAudioSegments.isEmpty())
|
||||
{
|
||||
//Block on the audio segment queue so that we can shutdown before any new segments are added, and
|
||||
//allow the add(segment) to restart monitoring as soon as needed.
|
||||
synchronized(mAudioSegmentQueue)
|
||||
{
|
||||
if(mAudioSegmentQueue.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
stopMonitoring();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Unexpected error during duplicate audio segment monitoring shutdown", e);
|
||||
//Do nothing, we got interrupted
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Unknown error while processing audio segments for duplicate call detection. Please report " +
|
||||
"this to the developer.", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Base Audio Broadcaster class
|
||||
*/
|
||||
public abstract class AbstractAudioBroadcaster<T extends BroadcastConfiguration> implements Listener<AudioRecording>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AbstractAudioBroadcaster.class);
|
||||
private Listener<BroadcastEvent> mBroadcastEventListener;
|
||||
private T mBroadcastConfiguration;
|
||||
protected ObjectProperty<BroadcastState> mBroadcastState = new SimpleObjectProperty<>(BroadcastState.READY);
|
||||
protected int mStreamedAudioCount = 0;
|
||||
protected int mErrorAudioCount = 0;
|
||||
protected int mAgedOffAudioCount = 0;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param broadcastConfiguration to use for this broadcaster
|
||||
*/
|
||||
public AbstractAudioBroadcaster(T broadcastConfiguration)
|
||||
{
|
||||
mBroadcastConfiguration = broadcastConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Observable broadcast state property
|
||||
*/
|
||||
public ObjectProperty<BroadcastState> broadcastStateProperty()
|
||||
{
|
||||
return mBroadcastState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current state of the broadcastAudio connection
|
||||
*/
|
||||
public BroadcastState getBroadcastState()
|
||||
{
|
||||
return mBroadcastState.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or changes the broadcast state
|
||||
*/
|
||||
public void setBroadcastState(BroadcastState broadcastState)
|
||||
{
|
||||
mBroadcastState.setValue(broadcastState);
|
||||
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the broadcaster
|
||||
*/
|
||||
public abstract void start();
|
||||
|
||||
/**
|
||||
* Stops the broadcaster
|
||||
*/
|
||||
public abstract void stop();
|
||||
|
||||
/**
|
||||
* Perform any shutdown related tasks.
|
||||
*/
|
||||
public abstract void dispose();
|
||||
|
||||
/**
|
||||
* Access the configuration for the broadcaster
|
||||
*/
|
||||
public T getBroadcastConfiguration()
|
||||
{
|
||||
return mBroadcastConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive broadcast events/state changes
|
||||
*/
|
||||
public void setListener(Listener<BroadcastEvent> listener)
|
||||
{
|
||||
mBroadcastEventListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving broadcast events/state changes
|
||||
*/
|
||||
public void removeListener()
|
||||
{
|
||||
mBroadcastEventListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the event to any registered listener
|
||||
*/
|
||||
protected void broadcast(BroadcastEvent event)
|
||||
{
|
||||
if(mBroadcastEventListener != null)
|
||||
{
|
||||
mBroadcastEventListener.receive(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of audio recordings streamed to remote server
|
||||
*/
|
||||
public int getStreamedAudioCount()
|
||||
{
|
||||
return mStreamedAudioCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the streamed or uploaded audio count by one
|
||||
*/
|
||||
public void incrementStreamedAudioCount()
|
||||
{
|
||||
mStreamedAudioCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of audio recordings that were removed for exceeding age limit
|
||||
*/
|
||||
public int getAgedOffAudioCount()
|
||||
{
|
||||
return mAgedOffAudioCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the aged-off audio count by one
|
||||
*/
|
||||
public void incrementAgedOffAudioCount()
|
||||
{
|
||||
mAgedOffAudioCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of audio recordings awaiting streaming or upload
|
||||
*/
|
||||
public abstract int getAudioQueueSize();
|
||||
|
||||
/**
|
||||
* Total audio upload/stream error count
|
||||
*/
|
||||
public int getAudioErrorCount()
|
||||
{
|
||||
return mErrorAudioCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the error audio count by one
|
||||
*/
|
||||
public void incrementErrorAudioCount()
|
||||
{
|
||||
mErrorAudioCount++;
|
||||
}
|
||||
}
|
||||
@@ -138,4 +138,10 @@ public class AudioRecording implements Comparable<AudioRecording>
|
||||
{
|
||||
return mPendingReplayCount.get() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof AudioRecording)) return false;
|
||||
return compareTo((AudioRecording) o) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,30 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
import io.github.dsheirer.audio.convert.AudioFrames;
|
||||
import io.github.dsheirer.audio.convert.ISilenceGenerator;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3FrameTools;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Queue;
|
||||
@@ -36,30 +32,31 @@ import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class AudioBroadcaster implements Listener<AudioRecording>
|
||||
public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration> extends AbstractAudioBroadcaster<T>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AudioBroadcaster.class);
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AudioStreamingBroadcaster.class);
|
||||
|
||||
public static final int PROCESSOR_RUN_INTERVAL_MS = 1000;
|
||||
|
||||
private ScheduledFuture mRecordingQueueProcessorFuture;
|
||||
private ScheduledFuture<?> mRecordingQueueProcessorFuture;
|
||||
|
||||
private RecordingQueueProcessor mRecordingQueueProcessor = new RecordingQueueProcessor();
|
||||
private Queue<AudioRecording> mAudioRecordingQueue = new LinkedTransferQueue<>();
|
||||
|
||||
private ISilenceGenerator mSilenceGenerator;
|
||||
|
||||
private Listener<BroadcastEvent> mBroadcastEventListener;
|
||||
private BroadcastState mBroadcastState = BroadcastState.READY;
|
||||
|
||||
private int mStreamedAudioCount = 0;
|
||||
private int mAgedOffAudioCount = 0;
|
||||
private BroadcastConfiguration mBroadcastConfiguration;
|
||||
private BroadcastFormat mBroadcastFormat;
|
||||
private long mDelay;
|
||||
private long mMaximumRecordingAge;
|
||||
private AtomicBoolean mStreaming = new AtomicBoolean();
|
||||
|
||||
protected boolean mInlineActive = false;
|
||||
protected int mInlineInterval;
|
||||
protected int mInlineRemaining = -1;
|
||||
|
||||
private int mTimeOverrun = 0;
|
||||
|
||||
/**
|
||||
* AudioBroadcaster for streaming audio recordings to a remote streaming audio server. Audio recordings are
|
||||
* generated by an internal StreamManager that converts an inbound stream of AudioPackets into a recording of the
|
||||
@@ -81,18 +78,24 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
|
||||
* The last audio packet's metadata is automatically attached to the closed audio recording when it is enqueued for
|
||||
* broadcast. That metadata will be updated on the remote server once the audio recording is opened for streaming.
|
||||
*/
|
||||
public AudioBroadcaster(BroadcastConfiguration broadcastConfiguration)
|
||||
public AudioStreamingBroadcaster(T broadcastConfiguration, InputAudioFormat inputAudioFormat, MP3Setting mp3Setting)
|
||||
{
|
||||
super(broadcastConfiguration);
|
||||
mBroadcastFormat = broadcastConfiguration.getBroadcastFormat();
|
||||
mDelay = getBroadcastConfiguration().getDelay();
|
||||
mMaximumRecordingAge = getBroadcastConfiguration().getMaximumRecordingAge();
|
||||
mSilenceGenerator = BroadcastFactory.getSilenceGenerator(broadcastConfiguration.getBroadcastFormat(),
|
||||
inputAudioFormat, mp3Setting);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mBroadcastConfiguration = broadcastConfiguration;
|
||||
mDelay = mBroadcastConfiguration.getDelay();
|
||||
mMaximumRecordingAge = mBroadcastConfiguration.getMaximumRecordingAge();
|
||||
mSilenceGenerator = BroadcastFactory.getSilenceGenerator(broadcastConfiguration.getBroadcastFormat());
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast binary audio data frames or sequences.
|
||||
*/
|
||||
protected abstract void broadcastAudio(byte[] audio);
|
||||
protected abstract void broadcastAudio(byte[] audio, IdentifierCollection identifierCollection);
|
||||
|
||||
/**
|
||||
* Protocol-specific metadata updater
|
||||
@@ -171,27 +174,11 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
|
||||
/**
|
||||
* Size of recording queue for recordings awaiting streaming
|
||||
*/
|
||||
public int getQueueSize()
|
||||
public int getAudioQueueSize()
|
||||
{
|
||||
return mAudioRecordingQueue.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of audio recordings streamed to remote server
|
||||
*/
|
||||
public int getStreamedAudioCount()
|
||||
{
|
||||
return mStreamedAudioCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of audio recordings that were removed for exceeding age limit
|
||||
*/
|
||||
public int getAgedOffAudioCount()
|
||||
{
|
||||
return mAgedOffAudioCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary insert method for the stream manager to nominate completed audio recordings for broadcast.
|
||||
*
|
||||
@@ -210,64 +197,29 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast configuration used by this broadcaster
|
||||
*/
|
||||
public BroadcastConfiguration getBroadcastConfiguration()
|
||||
{
|
||||
return mBroadcastConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive broadcast events/state changes
|
||||
*/
|
||||
public void setListener(Listener<BroadcastEvent> listener)
|
||||
{
|
||||
mBroadcastEventListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving broadcast events/state changes
|
||||
*/
|
||||
public void removeListener()
|
||||
{
|
||||
mBroadcastEventListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the event to any registered listener
|
||||
*/
|
||||
public void broadcast(BroadcastEvent event)
|
||||
{
|
||||
if(mBroadcastEventListener != null)
|
||||
{
|
||||
mBroadcastEventListener.receive(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of the broadcastAudio connection
|
||||
*/
|
||||
protected void setBroadcastState(BroadcastState state)
|
||||
public void setBroadcastState(BroadcastState state)
|
||||
{
|
||||
if(mBroadcastState != state)
|
||||
if(mBroadcastState.get() != state)
|
||||
{
|
||||
if(state == BroadcastState.CONNECTED || state == BroadcastState.DISCONNECTED)
|
||||
{
|
||||
mLog.info("[" + getStreamName() + "] status: " + state);
|
||||
}
|
||||
|
||||
mBroadcastState = state;
|
||||
super.setBroadcastState(state);
|
||||
|
||||
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));
|
||||
|
||||
if(mBroadcastState.isErrorState())
|
||||
if(mBroadcastState.get() != null && mBroadcastState.get().isErrorState())
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
if(!connected())
|
||||
{
|
||||
//Reset inline metadata
|
||||
mInlineRemaining = -1;
|
||||
//Remove all pending audio recordings
|
||||
while(!mAudioRecordingQueue.isEmpty())
|
||||
{
|
||||
@@ -285,14 +237,6 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Current state of the broadcastAudio connection
|
||||
*/
|
||||
public BroadcastState getBroadcastState()
|
||||
{
|
||||
return mBroadcastState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the broadcaster is currently connected to the remote server
|
||||
*/
|
||||
@@ -307,7 +251,6 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
|
||||
public boolean canConnect()
|
||||
{
|
||||
BroadcastState state = getBroadcastState();
|
||||
|
||||
return state != BroadcastState.CONNECTED && !state.isErrorState();
|
||||
}
|
||||
|
||||
@@ -328,10 +271,8 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
|
||||
public class RecordingQueueProcessor implements Runnable
|
||||
{
|
||||
private AtomicBoolean mProcessing = new AtomicBoolean();
|
||||
private ByteArrayInputStream mInputStream;
|
||||
private long mFinalSilencePadding = 0;
|
||||
private int mBytesStreamedActual = 0;
|
||||
private int mBytesStreamedRequired = 0;
|
||||
private AudioFrames mInputFrames;
|
||||
private IdentifierCollection mInputIdentifierCollection;
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
@@ -340,45 +281,35 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
|
||||
{
|
||||
try
|
||||
{
|
||||
if(mInputStream == null || mInputStream.available() <= 0)
|
||||
{
|
||||
if(mFinalSilencePadding > 0)
|
||||
{
|
||||
broadcastAudio(mSilenceGenerator.generate(mFinalSilencePadding));
|
||||
mFinalSilencePadding = 0;
|
||||
}
|
||||
int timeSent = 0;
|
||||
|
||||
if(mInputFrames == null || !mInputFrames.hasNextFrame())
|
||||
{
|
||||
nextRecording();
|
||||
}
|
||||
|
||||
if(mInputStream != null)
|
||||
if(mInputFrames != null && mInputFrames.hasNextFrame())
|
||||
{
|
||||
//We need to stream at 13.888 fps (144 byte frame) to achieve 2000 Bps or 16 kbps
|
||||
mBytesStreamedRequired += 2000; //2000 bytes per second for 16 kbps data rate
|
||||
int bytesToStream = mBytesStreamedRequired - mBytesStreamedActual;
|
||||
|
||||
//Trim length to whole-frame intervals (144 byte frame)
|
||||
bytesToStream -= (bytesToStream % 144);
|
||||
|
||||
int length = Math.min(bytesToStream, mInputStream.available());
|
||||
|
||||
byte[] audio = new byte[length];
|
||||
|
||||
try
|
||||
while(mInputFrames.hasNextFrame() && timeSent < PROCESSOR_RUN_INTERVAL_MS)
|
||||
{
|
||||
mBytesStreamedActual += mInputStream.read(audio);
|
||||
|
||||
broadcastAudio(audio);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error reading from in-memory audio recording input stream", ioe);
|
||||
mInputFrames.nextFrame();
|
||||
broadcastAudio(mInputFrames.getCurrentFrame(), mInputIdentifierCollection);
|
||||
timeSent += mInputFrames.getCurrentFrameDuration();
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if((mInputFrames == null || !mInputFrames.hasNextFrame()) && timeSent < PROCESSOR_RUN_INTERVAL_MS)
|
||||
{
|
||||
broadcastAudio(mSilenceGenerator.generate(PROCESSOR_RUN_INTERVAL_MS));
|
||||
AudioFrames silenceFrames = mSilenceGenerator.generate(PROCESSOR_RUN_INTERVAL_MS - mTimeOverrun - timeSent);
|
||||
while(silenceFrames.hasNextFrame())
|
||||
{
|
||||
silenceFrames.nextFrame();
|
||||
broadcastAudio(silenceFrames.getCurrentFrame(), null);
|
||||
timeSent += silenceFrames.getCurrentFrameDuration();
|
||||
}
|
||||
}
|
||||
|
||||
mTimeOverrun += timeSent - PROCESSOR_RUN_INTERVAL_MS;
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
@@ -394,20 +325,18 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
|
||||
*/
|
||||
private void nextRecording()
|
||||
{
|
||||
mBytesStreamedActual = 0;
|
||||
mBytesStreamedRequired = 0;
|
||||
|
||||
boolean metadataUpdateRequired = false;
|
||||
|
||||
if(mInputStream != null)
|
||||
if(mInputFrames != null)
|
||||
{
|
||||
mStreamedAudioCount++;
|
||||
broadcast(new BroadcastEvent(AudioBroadcaster.this,
|
||||
broadcast(new BroadcastEvent(AudioStreamingBroadcaster.this,
|
||||
BroadcastEvent.Event.BROADCASTER_STREAMED_COUNT_CHANGE));
|
||||
metadataUpdateRequired = true;
|
||||
}
|
||||
|
||||
mInputStream = null;
|
||||
mInputFrames = null;
|
||||
mInputIdentifierCollection = null;
|
||||
|
||||
//Peek at the next recording but don't remove it from the queue yet, so we can inspect the start time for
|
||||
//age limits and/or delay elapsed
|
||||
@@ -420,7 +349,7 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
|
||||
nextRecording = mAudioRecordingQueue.remove();
|
||||
nextRecording.removePendingReplay();
|
||||
mAgedOffAudioCount++;
|
||||
broadcast(new BroadcastEvent(AudioBroadcaster.this,
|
||||
broadcast(new BroadcastEvent(AudioStreamingBroadcaster.this,
|
||||
BroadcastEvent.Event.BROADCASTER_AGED_OFF_COUNT_CHANGE));
|
||||
nextRecording = mAudioRecordingQueue.peek();
|
||||
}
|
||||
@@ -435,17 +364,17 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
|
||||
{
|
||||
byte[] audio = Files.readAllBytes(nextRecording.getPath());
|
||||
|
||||
if(audio != null && audio.length > 0)
|
||||
if(audio.length > 0)
|
||||
{
|
||||
mInputStream = new ByteArrayInputStream(audio);
|
||||
|
||||
mFinalSilencePadding = PROCESSOR_RUN_INTERVAL_MS -
|
||||
(nextRecording.getRecordingLength() % PROCESSOR_RUN_INTERVAL_MS);
|
||||
|
||||
while(mFinalSilencePadding >= PROCESSOR_RUN_INTERVAL_MS)
|
||||
switch(mBroadcastFormat)
|
||||
{
|
||||
mFinalSilencePadding -= PROCESSOR_RUN_INTERVAL_MS;
|
||||
case MP3:
|
||||
mInputFrames = MP3FrameTools.split(audio);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported broadcast format [" + mBroadcastFormat + "]");
|
||||
}
|
||||
mInputIdentifierCollection = nextRecording.getIdentifierCollection();
|
||||
|
||||
if(connected())
|
||||
{
|
||||
@@ -461,13 +390,14 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
|
||||
mLog.error("Stream [" + getBroadcastConfiguration().getName() + "] error reading temporary audio " +
|
||||
"stream recording [" + nextRecording.getPath().toString() + "] - skipping recording - ", ioe);
|
||||
|
||||
mInputStream = null;
|
||||
mInputFrames = null;
|
||||
mInputIdentifierCollection = null;
|
||||
metadataUpdateRequired = false;
|
||||
}
|
||||
|
||||
nextRecording.removePendingReplay();
|
||||
|
||||
broadcast(new BroadcastEvent(AudioBroadcaster.this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
|
||||
broadcast(new BroadcastEvent(AudioStreamingBroadcaster.this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
|
||||
}
|
||||
|
||||
//If we closed out a recording and don't have a new/next recording, send an empty metadata update
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* 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
|
||||
@@ -127,7 +127,12 @@ public class AudioStreamingManager implements Listener<AudioSegment>
|
||||
{
|
||||
audioSegment = it.next();
|
||||
|
||||
if(audioSegment.completeProperty().get())
|
||||
if(audioSegment.isDuplicate() && mUserPreferences.getDuplicateCallDetectionPreference().isDuplicateStreamingSuppressionEnabled())
|
||||
{
|
||||
it.remove();
|
||||
audioSegment.decrementConsumerCount();
|
||||
}
|
||||
else if(audioSegment.completeProperty().get())
|
||||
{
|
||||
it.remove();
|
||||
|
||||
@@ -145,7 +150,7 @@ public class AudioStreamingManager implements Listener<AudioSegment>
|
||||
|
||||
try
|
||||
{
|
||||
AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3);
|
||||
AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3, mUserPreferences);
|
||||
IdentifierCollection identifierCollectionCopy =
|
||||
new IdentifierCollection(audioSegment.getIdentifierCollection().getIdentifiers());
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* 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
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
@@ -23,15 +23,29 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.util.Callback;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = BroadcastifyCallConfiguration.class, name="broadcastifyCallConfiguration"),
|
||||
@JsonSubTypes.Type(value = IcecastConfiguration.class, name="icecastConfiguration"),
|
||||
@JsonSubTypes.Type(value = ShoutcastV1Configuration.class, name="shoutcastV1Configuration"),
|
||||
@JsonSubTypes.Type(value = ShoutcastV2Configuration.class, name="shoutcastV2Configuration"),
|
||||
@@ -39,18 +53,33 @@ import java.net.SocketAddress;
|
||||
@JacksonXmlRootElement(localName = "stream")
|
||||
public abstract class BroadcastConfiguration
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(BroadcastConfiguration.class);
|
||||
// Static unique channel identifier tracking
|
||||
private static int UNIQUE_ID = 0;
|
||||
|
||||
private BroadcastFormat mBroadcastFormat = BroadcastFormat.MP3;
|
||||
private String mName;
|
||||
private String mHost;
|
||||
private int mPort;
|
||||
private String mPassword;
|
||||
private long mDelay;
|
||||
private long mMaximumRecordingAge = 10 * 60 * 1000; //10 minutes default
|
||||
private boolean mEnabled = true;
|
||||
protected StringProperty mName = new SimpleStringProperty();
|
||||
protected StringProperty mHost = new SimpleStringProperty();
|
||||
protected IntegerProperty mPort = new SimpleIntegerProperty(80);
|
||||
protected StringProperty mPassword = new SimpleStringProperty();
|
||||
protected LongProperty mDelay = new SimpleLongProperty();
|
||||
protected LongProperty mMaximumRecordingAge = new SimpleLongProperty(10 * 60 * 1000); //10 minutes default
|
||||
protected BooleanProperty mEnabled = new SimpleBooleanProperty(false);
|
||||
protected BooleanProperty mValid = new SimpleBooleanProperty();
|
||||
private int mId = ++UNIQUE_ID;
|
||||
|
||||
public BroadcastConfiguration()
|
||||
{
|
||||
//No-arg constructor required for JAXB
|
||||
}
|
||||
|
||||
/**
|
||||
* Unique identifier for this configuration.
|
||||
* Note: unique ids are generated at runtime and have no persistent context across sessions.
|
||||
*/
|
||||
@JsonIgnore
|
||||
public int getId()
|
||||
{
|
||||
return mId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,6 +95,70 @@ public abstract class BroadcastConfiguration
|
||||
mBroadcastFormat = format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream name
|
||||
*/
|
||||
public StringProperty nameProperty()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Server host name
|
||||
*/
|
||||
public StringProperty hostProperty()
|
||||
{
|
||||
return mHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Feed port
|
||||
*/
|
||||
public IntegerProperty portProperty()
|
||||
{
|
||||
return mPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Feed password
|
||||
*/
|
||||
public StringProperty passwordProperty()
|
||||
{
|
||||
return mPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delay introduced for each recording before streaming
|
||||
*/
|
||||
public LongProperty delayProperty()
|
||||
{
|
||||
return mDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum allowable age of a recording to stream. Recordings that exceed this threshold will be aged off.
|
||||
*/
|
||||
public LongProperty maximumRecordingAgeProperty()
|
||||
{
|
||||
return mMaximumRecordingAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream enabled property
|
||||
*/
|
||||
public BooleanProperty enabledProperty()
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration valid property
|
||||
*/
|
||||
public BooleanProperty validProperty()
|
||||
{
|
||||
return mValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast server type
|
||||
*/
|
||||
@@ -83,7 +176,7 @@ public abstract class BroadcastConfiguration
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "name")
|
||||
public String getName()
|
||||
{
|
||||
return mName;
|
||||
return mName.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +186,7 @@ public abstract class BroadcastConfiguration
|
||||
*/
|
||||
public void setName(String name)
|
||||
{
|
||||
mName = name;
|
||||
mName.set(name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,7 +203,7 @@ public abstract class BroadcastConfiguration
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "host")
|
||||
public String getHost()
|
||||
{
|
||||
return mHost;
|
||||
return mHost.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,7 +213,7 @@ public abstract class BroadcastConfiguration
|
||||
*/
|
||||
public void setHost(String host)
|
||||
{
|
||||
mHost = host;
|
||||
mHost.set(host);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,7 +230,7 @@ public abstract class BroadcastConfiguration
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "port")
|
||||
public int getPort()
|
||||
{
|
||||
return mPort;
|
||||
return mPort.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,7 +240,7 @@ public abstract class BroadcastConfiguration
|
||||
*/
|
||||
public void setPort(int port)
|
||||
{
|
||||
mPort = port;
|
||||
mPort.set(port);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,7 +248,7 @@ public abstract class BroadcastConfiguration
|
||||
*/
|
||||
public boolean hasPort()
|
||||
{
|
||||
return mPort > 0;
|
||||
return mPort.get() > 0;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@@ -170,7 +263,7 @@ public abstract class BroadcastConfiguration
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "password")
|
||||
public String getPassword()
|
||||
{
|
||||
return mPassword;
|
||||
return mPassword.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,7 +273,7 @@ public abstract class BroadcastConfiguration
|
||||
*/
|
||||
public void setPassword(String password)
|
||||
{
|
||||
mPassword = password;
|
||||
mPassword.set(password);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,7 +304,7 @@ public abstract class BroadcastConfiguration
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "delay")
|
||||
public long getDelay()
|
||||
{
|
||||
return mDelay;
|
||||
return mDelay.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,7 +313,7 @@ public abstract class BroadcastConfiguration
|
||||
*/
|
||||
public void setDelay(long delay)
|
||||
{
|
||||
mDelay = delay;
|
||||
mDelay.set(delay);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,7 +324,7 @@ public abstract class BroadcastConfiguration
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "maximum_recording_age")
|
||||
public long getMaximumRecordingAge()
|
||||
{
|
||||
return mMaximumRecordingAge;
|
||||
return mMaximumRecordingAge.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,7 +334,7 @@ public abstract class BroadcastConfiguration
|
||||
*/
|
||||
public void setMaximumRecordingAge(long age)
|
||||
{
|
||||
mMaximumRecordingAge = age;
|
||||
mMaximumRecordingAge.set(age);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,12 +343,12 @@ public abstract class BroadcastConfiguration
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "enabled")
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return mEnabled;
|
||||
return mEnabled.get();
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled)
|
||||
{
|
||||
mEnabled = enabled;
|
||||
mEnabled.set(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -302,6 +395,15 @@ public abstract class BroadcastConfiguration
|
||||
@JsonIgnore
|
||||
public boolean isValid()
|
||||
{
|
||||
return mHost != null && mPort > 0;
|
||||
return mValid.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an observable property extractor for use with observable lists to detect changes internal to this object.
|
||||
*/
|
||||
public static Callback<BroadcastConfiguration, Observable[]> extractor()
|
||||
{
|
||||
return (BroadcastConfiguration b) -> new Observable[] {b.nameProperty(), b.hostProperty(), b.portProperty(),
|
||||
b.passwordProperty(), b.maximumRecordingAgeProperty(), b.delayProperty(), b.enabledProperty()};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import io.github.dsheirer.icon.IconManager;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public abstract class BroadcastConfigurationEditor extends DocumentListenerEditor<BroadcastConfiguration>
|
||||
{
|
||||
protected IconManager mIconManager;
|
||||
protected BroadcastModel mBroadcastModel;
|
||||
protected AliasModel mAliasModel;
|
||||
|
||||
public BroadcastConfigurationEditor(BroadcastModel broadcastModel, AliasModel aliasModel, IconManager iconManager)
|
||||
{
|
||||
mBroadcastModel = broadcastModel;
|
||||
mAliasModel = aliasModel;
|
||||
mIconManager = iconManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the configuration with the new name and prompts the user to update any aliases that have a broadcast
|
||||
* channel alias id with the old name to update to the new name.
|
||||
*
|
||||
* @param broadcastConfiguration to assign a new channel name
|
||||
* @param newName to assign to the broadcast configuration. Note: assumes that validateConfiguration() has been
|
||||
* invoked to verify that newName is non-null and non-empty
|
||||
*/
|
||||
protected void updateConfigurationName(BroadcastConfiguration broadcastConfiguration, String newName)
|
||||
{
|
||||
String previousName = broadcastConfiguration.getName();
|
||||
|
||||
if(previousName == null || previousName.isEmpty())
|
||||
{
|
||||
broadcastConfiguration.setName(newName);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!previousName.equals(newName))
|
||||
{
|
||||
mAliasModel.renameBroadcastChannel(previousName, newName);
|
||||
broadcastConfiguration.setName(newName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a text field control for a non-null, non-empty value
|
||||
* @param field to validate
|
||||
* @param title to use for error dialog
|
||||
* @param message to use for error dialog
|
||||
* @return true if field contains a non-null, non-empty value
|
||||
*/
|
||||
protected boolean validateTextField(JTextField field, String title, String message)
|
||||
{
|
||||
String text = field.getText();
|
||||
|
||||
if(text == null || text.isEmpty())
|
||||
{
|
||||
JOptionPane.showMessageDialog(BroadcastConfigurationEditor.this, message, title,
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
|
||||
field.requestFocus();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the text field control that contains an integer value for non-null, non-empty and within the
|
||||
* specified min/max valid range.
|
||||
* @param field to validate
|
||||
* @param title to use for error dialog
|
||||
* @param message to use for error dialog
|
||||
* @param minValid value
|
||||
* @param maxValid value
|
||||
* @return true if field contains a non-null, non-empty value within the valid min/max range
|
||||
*/
|
||||
protected boolean validateIntegerTextField(JTextField field, String title, String message, int minValid, int maxValid)
|
||||
{
|
||||
if(validateTextField(field, title, message))
|
||||
{
|
||||
String text = field.getText();
|
||||
|
||||
try
|
||||
{
|
||||
int value = Integer.parseInt(text);
|
||||
|
||||
if(minValid <= value && value <= maxValid)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//Do nothing, we couldn't parse the number value
|
||||
}
|
||||
|
||||
JOptionPane.showMessageDialog(BroadcastConfigurationEditor.this, message, title,
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
|
||||
field.requestFocus();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ package io.github.dsheirer.audio.broadcast;
|
||||
public class BroadcastEvent
|
||||
{
|
||||
private BroadcastConfiguration mBroadcastConfiguration;
|
||||
private AudioBroadcaster mAudioBroadcaster;
|
||||
private AbstractAudioBroadcaster mAudioBroadcaster;
|
||||
private Event mEvent;
|
||||
|
||||
/**
|
||||
@@ -36,7 +36,7 @@ public class BroadcastEvent
|
||||
mEvent = event;
|
||||
}
|
||||
|
||||
public BroadcastEvent(AudioBroadcaster audioBroadcaster, Event event)
|
||||
public BroadcastEvent(AbstractAudioBroadcaster audioBroadcaster, Event event)
|
||||
{
|
||||
mAudioBroadcaster = audioBroadcaster;
|
||||
mEvent = event;
|
||||
@@ -47,7 +47,7 @@ public class BroadcastEvent
|
||||
return mBroadcastConfiguration;
|
||||
}
|
||||
|
||||
public AudioBroadcaster getAudioBroadcaster()
|
||||
public AbstractAudioBroadcaster getAudioBroadcaster()
|
||||
{
|
||||
return mAudioBroadcaster;
|
||||
}
|
||||
@@ -83,6 +83,7 @@ public class BroadcastEvent
|
||||
BROADCASTER_STATE_CHANGE,
|
||||
BROADCASTER_STREAMED_COUNT_CHANGE,
|
||||
BROADCASTER_AGED_OFF_COUNT_CHANGE,
|
||||
BROADCASTER_ERROR_COUNT_CHANGE,
|
||||
BROADCASTER_DELETE,
|
||||
|
||||
CONFIGURATION_ADD,
|
||||
|
||||
@@ -1,48 +1,39 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyConfigurationEditor;
|
||||
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallBroadcaster;
|
||||
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyFeedConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPAudioBroadcaster;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPConfigurationEditor;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPAudioBroadcaster;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPConfigurationEditor;
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1AudioBroadcaster;
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1ConfigurationEditor;
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2AudioBroadcaster;
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2AudioStreamingBroadcaster;
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2ConfigurationEditor;
|
||||
import io.github.dsheirer.audio.convert.IAudioConverter;
|
||||
import io.github.dsheirer.audio.convert.ISilenceGenerator;
|
||||
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.audio.convert.MP3SilenceGenerator;
|
||||
import io.github.dsheirer.gui.editor.Editor;
|
||||
import io.github.dsheirer.gui.editor.EmptyEditor;
|
||||
import io.github.dsheirer.icon.IconManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -51,65 +42,50 @@ public class BroadcastFactory
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(BroadcastFactory.class);
|
||||
|
||||
public static final int MP3_MONO_16_KHZ_BITRATE = 16;
|
||||
public static final boolean MP3_CONSTANT_BITRATE = false;
|
||||
|
||||
/**
|
||||
* Creates an audio streaming broadcaster for the configuration
|
||||
*
|
||||
* @param configuration describing the server and audio types
|
||||
* @return configured broadcaster or null
|
||||
*/
|
||||
public static AudioBroadcaster getBroadcaster(BroadcastConfiguration configuration, AliasModel aliasModel)
|
||||
public static AbstractAudioBroadcaster getBroadcaster(BroadcastConfiguration configuration, AliasModel aliasModel,
|
||||
UserPreferences userPreferences)
|
||||
{
|
||||
if(configuration != null)
|
||||
{
|
||||
IAudioConverter converter = getAudioConverter(configuration);
|
||||
InputAudioFormat inputAudioFormat = userPreferences.getMP3Preference().getAudioSampleRate();
|
||||
MP3Setting mp3Setting = userPreferences.getMP3Preference().getMP3Setting();
|
||||
|
||||
if(converter != null)
|
||||
switch(configuration.getBroadcastServerType())
|
||||
{
|
||||
switch(configuration.getBroadcastServerType())
|
||||
{
|
||||
case BROADCASTIFY:
|
||||
return new IcecastTCPAudioBroadcaster((BroadcastifyConfiguration) configuration, aliasModel);
|
||||
case ICECAST_TCP:
|
||||
return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, aliasModel);
|
||||
case ICECAST_HTTP:
|
||||
return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration, aliasModel);
|
||||
case SHOUTCAST_V1:
|
||||
return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, aliasModel);
|
||||
case SHOUTCAST_V2:
|
||||
return new ShoutcastV2AudioBroadcaster((ShoutcastV2Configuration) configuration, aliasModel);
|
||||
case UNKNOWN:
|
||||
default:
|
||||
mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name());
|
||||
break;
|
||||
}
|
||||
case BROADCASTIFY_CALL:
|
||||
return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration,
|
||||
inputAudioFormat, mp3Setting, aliasModel);
|
||||
case BROADCASTIFY:
|
||||
return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration,
|
||||
inputAudioFormat, mp3Setting, aliasModel);
|
||||
case ICECAST_TCP:
|
||||
return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, inputAudioFormat,
|
||||
mp3Setting, aliasModel);
|
||||
case ICECAST_HTTP:
|
||||
return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration, inputAudioFormat,
|
||||
mp3Setting, aliasModel);
|
||||
case SHOUTCAST_V1:
|
||||
return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, inputAudioFormat,
|
||||
mp3Setting, aliasModel);
|
||||
case SHOUTCAST_V2:
|
||||
return new ShoutcastV2AudioStreamingBroadcaster((ShoutcastV2Configuration) configuration,
|
||||
inputAudioFormat, mp3Setting, aliasModel);
|
||||
case UNKNOWN:
|
||||
default:
|
||||
mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an audio convert to convert from 8 kHz PCM audio to the specified format
|
||||
*
|
||||
* @param configuration containing the requested output audio format
|
||||
* @return audio convert or null
|
||||
*/
|
||||
public static IAudioConverter getAudioConverter(BroadcastConfiguration configuration)
|
||||
{
|
||||
switch(configuration.getBroadcastFormat())
|
||||
{
|
||||
case MP3:
|
||||
return new MP3AudioConverter(MP3_MONO_16_KHZ_BITRATE, MP3_CONSTANT_BITRATE);
|
||||
default:
|
||||
mLog.info("Unrecognized broadcastAudio format: " + configuration.getBroadcastFormat().name());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a broadcastAudio configuration for the specified server type and format
|
||||
*
|
||||
@@ -121,8 +97,10 @@ public class BroadcastFactory
|
||||
{
|
||||
switch(serverType)
|
||||
{
|
||||
case BROADCASTIFY_CALL:
|
||||
return new BroadcastifyCallConfiguration(format);
|
||||
case BROADCASTIFY:
|
||||
return new BroadcastifyConfiguration(format);
|
||||
return new BroadcastifyFeedConfiguration(format);
|
||||
case ICECAST_HTTP:
|
||||
return new IcecastHTTPConfiguration(format);
|
||||
case ICECAST_TCP:
|
||||
@@ -140,54 +118,12 @@ public class BroadcastFactory
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an editor for the specified broadcastAudio configuration
|
||||
*
|
||||
* @param configuration to modify or view
|
||||
* @param broadcastModel model for broadcastAudio configurations
|
||||
* @return an editor for the specified broadcastAudio configuration
|
||||
*/
|
||||
public static Editor<BroadcastConfiguration> getEditor(UserPreferences userPreferences,
|
||||
BroadcastConfiguration configuration,
|
||||
BroadcastModel broadcastModel,
|
||||
AliasModel aliasModel,
|
||||
IconManager iconManager)
|
||||
{
|
||||
Editor<BroadcastConfiguration> editor;
|
||||
|
||||
switch(configuration.getBroadcastServerType())
|
||||
{
|
||||
case BROADCASTIFY:
|
||||
editor = new BroadcastifyConfigurationEditor(userPreferences, broadcastModel, aliasModel, iconManager);
|
||||
break;
|
||||
case ICECAST_TCP:
|
||||
editor = new IcecastTCPConfigurationEditor(broadcastModel, aliasModel, iconManager);
|
||||
break;
|
||||
case ICECAST_HTTP:
|
||||
editor = new IcecastHTTPConfigurationEditor(broadcastModel, aliasModel, iconManager);
|
||||
break;
|
||||
case SHOUTCAST_V1:
|
||||
editor = new ShoutcastV1ConfigurationEditor(broadcastModel, aliasModel, iconManager);
|
||||
break;
|
||||
case SHOUTCAST_V2:
|
||||
editor = new ShoutcastV2ConfigurationEditor(broadcastModel, aliasModel, iconManager);
|
||||
break;
|
||||
default:
|
||||
editor = new EmptyEditor<BroadcastConfiguration>();
|
||||
break;
|
||||
}
|
||||
|
||||
editor.setItem(configuration);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
public static ISilenceGenerator getSilenceGenerator(BroadcastFormat format)
|
||||
public static ISilenceGenerator getSilenceGenerator(BroadcastFormat format, InputAudioFormat inputAudioFormat, MP3Setting mp3Setting)
|
||||
{
|
||||
switch(format)
|
||||
{
|
||||
case MP3:
|
||||
return new MP3SilenceGenerator();
|
||||
return new MP3SilenceGenerator(inputAudioFormat, mp3Setting);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized broadcast format [" + format +
|
||||
"] can't create silence generator");
|
||||
|
||||
@@ -1,44 +1,37 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||
import io.github.dsheirer.icon.IconManager;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.properties.SystemProperties;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -48,6 +41,12 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
public class BroadcastModel extends AbstractTableModel implements Listener<AudioRecording>
|
||||
{
|
||||
@@ -58,32 +57,35 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
|
||||
private static final String UNIQUE_NAME_REGEX = "(.*)\\((\\d*)\\)";
|
||||
|
||||
public static final int COLUMN_SERVER_ICON = 0;
|
||||
public static final int COLUMN_BROADCAST_SERVER_TYPE = 0;
|
||||
public static final int COLUMN_STREAM_NAME = 1;
|
||||
public static final int COLUMN_BROADCASTER_STATUS = 2;
|
||||
public static final int COLUMN_BROADCASTER_QUEUE_SIZE = 3;
|
||||
public static final int COLUMN_BROADCASTER_STREAMED_COUNT = 4;
|
||||
public static final int COLUMN_BROADCASTER_AGED_OFF_COUNT = 5;
|
||||
public static final int COLUMN_BROADCASTER_ERROR_COUNT = 6;
|
||||
|
||||
public static final String[] COLUMN_NAMES = new String[]
|
||||
{"Streaming", "Name", "Status", "Queued", "Streamed", "Aged Off"};
|
||||
{"Stream Type", "Name", "Status", "Queued", "Streamed/Uploaded", "Aged Off", "Upload Error"};
|
||||
|
||||
private List<BroadcastConfiguration> mBroadcastConfigurations = new CopyOnWriteArrayList<>();
|
||||
private ObservableList<ConfiguredBroadcast> mConfiguredBroadcasts =
|
||||
FXCollections.observableArrayList(ConfiguredBroadcast.extractor());
|
||||
private List<AudioRecording> mRecordingQueue = new CopyOnWriteArrayList<>();
|
||||
|
||||
private Map<String,BroadcastConfiguration> mBroadcastConfigurationMap = new HashMap<>();
|
||||
private Map<String,AudioBroadcaster> mBroadcasterMap = new HashMap<>();
|
||||
private IconManager mIconManager;
|
||||
private Map<Integer,AbstractAudioBroadcaster> mBroadcasterMap = new HashMap<>();
|
||||
private IconModel mIconModel;
|
||||
private AliasModel mAliasModel;
|
||||
private Broadcaster<BroadcastEvent> mBroadcastEventBroadcaster = new Broadcaster<>();
|
||||
private BroadcastEventListener mBroadcastEventListener = new BroadcastEventListener();
|
||||
private UserPreferences mUserPreferences;
|
||||
|
||||
/**
|
||||
* Model for managing Broadcast configurations and any associated broadcaster instances.
|
||||
*/
|
||||
public BroadcastModel(AliasModel aliasModel, IconManager iconManager, UserPreferences userPreferences)
|
||||
public BroadcastModel(AliasModel aliasModel, IconModel iconModel, UserPreferences userPreferences)
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
mIconManager = iconManager;
|
||||
mIconModel = iconModel;
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
//Monitor to remove temporary recording files that have been streamed by all audio broadcasters
|
||||
ThreadPool.SCHEDULED.scheduleAtFixedRate(new RecordingDeletionMonitor(), 15l, 15l, TimeUnit.SECONDS);
|
||||
@@ -91,6 +93,27 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
removeOrphanedTemporaryRecordings();
|
||||
}
|
||||
|
||||
/**
|
||||
* List of broadcast configurations with (optional) audio broadcasters created from each configuration
|
||||
*/
|
||||
public ObservableList<ConfiguredBroadcast> getConfiguredBroadcasts()
|
||||
{
|
||||
return mConfiguredBroadcasts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all broadcast configurations and shuts down any running broadcasters.
|
||||
*/
|
||||
public void clear()
|
||||
{
|
||||
List<ConfiguredBroadcast> configuredBroadcasts = new ArrayList<>(mConfiguredBroadcasts);
|
||||
|
||||
for(ConfiguredBroadcast configuredBroadcast: configuredBroadcasts)
|
||||
{
|
||||
removeBroadcastConfiguration(configuredBroadcast.getBroadcastConfiguration());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List of broadcastAudio configuration names
|
||||
*/
|
||||
@@ -98,9 +121,9 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
{
|
||||
List<String> names = new ArrayList<>();
|
||||
|
||||
for(BroadcastConfiguration configuration : mBroadcastConfigurations)
|
||||
for(ConfiguredBroadcast configuredBroadcast: mConfiguredBroadcasts)
|
||||
{
|
||||
names.add(configuration.getName());
|
||||
names.add(configuredBroadcast.getBroadcastConfiguration().getName());
|
||||
}
|
||||
|
||||
return names;
|
||||
@@ -111,7 +134,14 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
*/
|
||||
public List<BroadcastConfiguration> getBroadcastConfigurations()
|
||||
{
|
||||
return mBroadcastConfigurations;
|
||||
List<BroadcastConfiguration> configs = new ArrayList<>();
|
||||
|
||||
for(ConfiguredBroadcast configuredBroadcast: mConfiguredBroadcasts)
|
||||
{
|
||||
configs.add(configuredBroadcast.getBroadcastConfiguration());
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(configs);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,38 +158,24 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
/**
|
||||
* Adds the broadcastAudio configuration to this model
|
||||
*/
|
||||
public void addBroadcastConfiguration(BroadcastConfiguration configuration)
|
||||
public ConfiguredBroadcast addBroadcastConfiguration(BroadcastConfiguration configuration)
|
||||
{
|
||||
if(configuration != null)
|
||||
{
|
||||
ensureUniqueName(configuration);
|
||||
|
||||
if(!mBroadcastConfigurations.contains(configuration))
|
||||
ConfiguredBroadcast configuredBroadcast = getConfiguredBroadcast(configuration);
|
||||
|
||||
if(configuredBroadcast == null)
|
||||
{
|
||||
mBroadcastConfigurations.add(configuration);
|
||||
|
||||
int index = mBroadcastConfigurations.size() - 1;
|
||||
|
||||
configuredBroadcast = new ConfiguredBroadcast(configuration);
|
||||
mConfiguredBroadcasts.add(configuredBroadcast);
|
||||
int index = mConfiguredBroadcasts.indexOf(configuredBroadcast);
|
||||
fireTableRowsInserted(index, index);
|
||||
|
||||
mBroadcastConfigurationMap.put(configuration.getName(), configuration);
|
||||
|
||||
process(new BroadcastEvent(configuration, BroadcastEvent.Event.CONFIGURATION_ADD));
|
||||
return configuredBroadcast;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the configuration and adds it this model with a unique configuration name
|
||||
*/
|
||||
public BroadcastConfiguration cloneBroadcastConfiguration(BroadcastConfiguration configuration)
|
||||
{
|
||||
if(configuration != null)
|
||||
{
|
||||
BroadcastConfiguration clone = configuration.copyOf();
|
||||
addBroadcastConfiguration(clone);
|
||||
return clone;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -238,11 +254,11 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
return false;
|
||||
}
|
||||
|
||||
for(BroadcastConfiguration configurationToCompare : mBroadcastConfigurations)
|
||||
for(ConfiguredBroadcast configuredBroadcast: mConfiguredBroadcasts)
|
||||
{
|
||||
if(configurationToCompare != configuration &&
|
||||
configurationToCompare.getName() != null &&
|
||||
configurationToCompare.getName().equals(name))
|
||||
BroadcastConfiguration toCompare = configuredBroadcast.getBroadcastConfiguration();
|
||||
|
||||
if(toCompare != configuration && toCompare.getName() != null && toCompare.getName().equals(name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -253,13 +269,20 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
|
||||
public void removeBroadcastConfiguration(BroadcastConfiguration broadcastConfiguration)
|
||||
{
|
||||
if(broadcastConfiguration != null && mBroadcastConfigurations.contains(broadcastConfiguration))
|
||||
ConfiguredBroadcast configuredBroadcast = getConfiguredBroadcast(broadcastConfiguration);
|
||||
|
||||
if(configuredBroadcast != null)
|
||||
{
|
||||
int index = mBroadcastConfigurations.indexOf(broadcastConfiguration);
|
||||
int index = mConfiguredBroadcasts.indexOf(configuredBroadcast);
|
||||
|
||||
mBroadcastConfigurations.remove(broadcastConfiguration);
|
||||
mConfiguredBroadcasts.remove(configuredBroadcast);
|
||||
|
||||
mBroadcastConfigurationMap.remove(broadcastConfiguration.getName());
|
||||
if(configuredBroadcast.hasAudioBroadcaster())
|
||||
{
|
||||
mBroadcasterMap.remove(broadcastConfiguration.getId());
|
||||
configuredBroadcast.getAudioBroadcaster().stop();;
|
||||
configuredBroadcast.setAudioBroadcaster(null);
|
||||
}
|
||||
|
||||
process(new BroadcastEvent(broadcastConfiguration, BroadcastEvent.Event.CONFIGURATION_DELETE));
|
||||
|
||||
@@ -270,9 +293,16 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
/**
|
||||
* Returns the broadcaster associated with the stream name or null if there is no broadcaster setup for the name.
|
||||
*/
|
||||
public AudioBroadcaster getBroadcaster(String streamName)
|
||||
public AbstractAudioBroadcaster getBroadcaster(String streamName)
|
||||
{
|
||||
return mBroadcasterMap.get(streamName);
|
||||
BroadcastConfiguration broadcastConfiguration = getBroadcastConfiguration(streamName);
|
||||
|
||||
if(broadcastConfiguration != null)
|
||||
{
|
||||
return mBroadcasterMap.get(broadcastConfiguration.getId());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -286,7 +316,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
|
||||
if(channelName != null)
|
||||
{
|
||||
AudioBroadcaster audioBroadcaster = getBroadcaster(channelName);
|
||||
AbstractAudioBroadcaster audioBroadcaster = getBroadcaster(channelName);
|
||||
|
||||
if(audioBroadcaster != null)
|
||||
{
|
||||
@@ -305,17 +335,26 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
*/
|
||||
private void createBroadcaster(BroadcastConfiguration broadcastConfiguration)
|
||||
{
|
||||
if(broadcastConfiguration != null && broadcastConfiguration.isEnabled() && broadcastConfiguration.isValid() &&
|
||||
!mBroadcasterMap.containsKey(broadcastConfiguration.getName()))
|
||||
ConfiguredBroadcast configuredBroadcast = getConfiguredBroadcast(broadcastConfiguration);
|
||||
|
||||
if(configuredBroadcast != null && broadcastConfiguration.isEnabled())
|
||||
{
|
||||
AudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration, mAliasModel);
|
||||
if(configuredBroadcast.hasAudioBroadcaster())
|
||||
{
|
||||
deleteBroadcaster(configuredBroadcast);
|
||||
}
|
||||
|
||||
AbstractAudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration,
|
||||
mAliasModel, mUserPreferences);
|
||||
|
||||
if(audioBroadcaster != null)
|
||||
{
|
||||
audioBroadcaster.setListener(broadcastEvent -> process(broadcastEvent));
|
||||
configuredBroadcast.setAudioBroadcaster(audioBroadcaster);
|
||||
audioBroadcaster.setListener(mBroadcastEventListener);
|
||||
audioBroadcaster.start();
|
||||
mBroadcasterMap.put(audioBroadcaster.getBroadcastConfiguration().getName(), audioBroadcaster);
|
||||
int index = mBroadcastConfigurations.indexOf(audioBroadcaster.getBroadcastConfiguration());
|
||||
mBroadcasterMap.put(audioBroadcaster.getBroadcastConfiguration().getId(), audioBroadcaster);
|
||||
|
||||
int index = mConfiguredBroadcasts.indexOf(configuredBroadcast);
|
||||
|
||||
if(index >= 0)
|
||||
{
|
||||
@@ -330,26 +369,27 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
/**
|
||||
* Shut down a broadcaster created from the configuration and remove it from this model
|
||||
*/
|
||||
private void deleteBroadcaster(String name)
|
||||
private void deleteBroadcaster(ConfiguredBroadcast configuredBroadcast)
|
||||
{
|
||||
if(name != null && mBroadcasterMap.containsKey(name))
|
||||
if(configuredBroadcast != null && configuredBroadcast.hasAudioBroadcaster())
|
||||
{
|
||||
AudioBroadcaster audioBroadcaster = mBroadcasterMap.remove(name);
|
||||
mBroadcasterMap.remove(configuredBroadcast.getBroadcastConfiguration().getId());
|
||||
|
||||
if(audioBroadcaster != null)
|
||||
AbstractAudioBroadcaster broadcaster = configuredBroadcast.getAudioBroadcaster();
|
||||
configuredBroadcast.setAudioBroadcaster(null);
|
||||
|
||||
broadcaster.stop();
|
||||
broadcaster.removeListener();
|
||||
broadcaster.dispose();
|
||||
|
||||
int index = mConfiguredBroadcasts.indexOf(configuredBroadcast);
|
||||
|
||||
if(index >= 0)
|
||||
{
|
||||
audioBroadcaster.stop();
|
||||
audioBroadcaster.removeListener();
|
||||
|
||||
int index = mBroadcastConfigurations.indexOf(audioBroadcaster.getBroadcastConfiguration());
|
||||
|
||||
if(index >= 0)
|
||||
{
|
||||
fireTableRowsUpdated(index, index);
|
||||
}
|
||||
|
||||
broadcast(new BroadcastEvent(audioBroadcaster, BroadcastEvent.Event.BROADCASTER_DELETE));
|
||||
fireTableRowsUpdated(index, index);
|
||||
}
|
||||
|
||||
broadcast(new BroadcastEvent(broadcaster, BroadcastEvent.Event.BROADCASTER_DELETE));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,7 +398,16 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
*/
|
||||
public BroadcastConfiguration getBroadcastConfiguration(String streamName)
|
||||
{
|
||||
return mBroadcastConfigurationMap.get(streamName);
|
||||
for(ConfiguredBroadcast configuredBroadcast: mConfiguredBroadcasts)
|
||||
{
|
||||
if(configuredBroadcast.getBroadcastConfiguration().getName() != null &&
|
||||
configuredBroadcast.getBroadcastConfiguration().getName().equals(streamName))
|
||||
{
|
||||
return configuredBroadcast.getBroadcastConfiguration();
|
||||
}
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -377,6 +426,43 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
mBroadcastEventBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the configured broadcast that contains the specified configuration
|
||||
* @param configuration to search for
|
||||
* @return configured broadcast that matches, or null
|
||||
*/
|
||||
private ConfiguredBroadcast getConfiguredBroadcast(BroadcastConfiguration configuration)
|
||||
{
|
||||
for(ConfiguredBroadcast configuredBroadcast: mConfiguredBroadcasts)
|
||||
{
|
||||
if(configuredBroadcast.getBroadcastConfiguration().equals(configuration))
|
||||
{
|
||||
return configuredBroadcast;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the configured broadcast that contains the specified broadcaster
|
||||
* @param broadcaster to search for
|
||||
* @return configured broadcast that matches, or null
|
||||
*/
|
||||
private ConfiguredBroadcast getConfiguredBroadcast(AbstractAudioBroadcaster broadcaster)
|
||||
{
|
||||
for(ConfiguredBroadcast configuredBroadcast: mConfiguredBroadcasts)
|
||||
{
|
||||
if(configuredBroadcast.getAudioBroadcaster() != null &&
|
||||
configuredBroadcast.getAudioBroadcaster().equals(broadcaster))
|
||||
{
|
||||
return configuredBroadcast;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the broadcastAudio configuration change event
|
||||
*/
|
||||
@@ -399,30 +485,31 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
break;
|
||||
case CONFIGURATION_CHANGE:
|
||||
BroadcastConfiguration broadcastConfiguration = broadcastEvent.getBroadcastConfiguration();
|
||||
int index = mBroadcastConfigurations.indexOf(broadcastConfiguration);
|
||||
ConfiguredBroadcast configuredBroadcast = getConfiguredBroadcast(broadcastConfiguration);
|
||||
|
||||
//Delete the existing broadcaster for any broadcast configuration changes
|
||||
String previousChannelName = cleanupMapAssociations(broadcastConfiguration);
|
||||
deleteBroadcaster(previousChannelName);
|
||||
//Delete the broadcaster if it exists
|
||||
deleteBroadcaster(configuredBroadcast);
|
||||
|
||||
//If the configuration is enabled, create a new broadcaster after a brief delay
|
||||
if(broadcastConfiguration.isEnabled())
|
||||
{
|
||||
//Delay restarting the broadcaster to allow remote server time to cleanup
|
||||
ThreadPool.SCHEDULED.schedule(new DelayedBroadcasterStartup(broadcastConfiguration),
|
||||
3, TimeUnit.SECONDS);
|
||||
1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
int index = mConfiguredBroadcasts.indexOf(configuredBroadcast);
|
||||
fireTableRowsUpdated(index, index);
|
||||
break;
|
||||
case CONFIGURATION_DELETE:
|
||||
deleteBroadcaster(broadcastEvent.getBroadcastConfiguration().getName());
|
||||
deleteBroadcaster(getConfiguredBroadcast(broadcastEvent.getBroadcastConfiguration()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(broadcastEvent.isAudioBroadcasterEvent())
|
||||
{
|
||||
int row = mBroadcastConfigurations.indexOf(broadcastEvent.getAudioBroadcaster().getBroadcastConfiguration());
|
||||
ConfiguredBroadcast configuredBroadcast = getConfiguredBroadcast(broadcastEvent.getAudioBroadcaster());
|
||||
int row = mConfiguredBroadcasts.indexOf(configuredBroadcast);
|
||||
|
||||
switch(broadcastEvent.getEvent())
|
||||
{
|
||||
@@ -449,6 +536,11 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
{
|
||||
fireTableCellUpdated(row, COLUMN_BROADCASTER_AGED_OFF_COUNT);
|
||||
}
|
||||
case BROADCASTER_ERROR_COUNT_CHANGE:
|
||||
if(row >= 0)
|
||||
{
|
||||
fireTableCellUpdated(row, COLUMN_BROADCASTER_ERROR_COUNT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -457,41 +549,10 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
broadcast(broadcastEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the name of a broadcast configuration changes, remove the old map associations so that they can be replaced
|
||||
* with the new map associations. Remove any broadcaster that is associated with the old configuration name.
|
||||
*
|
||||
* @param broadcastConfiguration
|
||||
* @return previous or current channel name that has been replaced with the current configuration channel name so
|
||||
* that anything else associated with the previous name can be cleaned up.
|
||||
*/
|
||||
private String cleanupMapAssociations(BroadcastConfiguration broadcastConfiguration)
|
||||
{
|
||||
String oldName = null;
|
||||
|
||||
for(Map.Entry<String,BroadcastConfiguration> entry : mBroadcastConfigurationMap.entrySet())
|
||||
{
|
||||
if(entry.getValue() == broadcastConfiguration)
|
||||
{
|
||||
oldName = entry.getKey();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if(oldName != null)
|
||||
{
|
||||
mBroadcastConfigurationMap.remove(oldName);
|
||||
}
|
||||
|
||||
mBroadcastConfigurationMap.put(broadcastConfiguration.getName(), broadcastConfiguration);
|
||||
|
||||
return oldName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount()
|
||||
{
|
||||
return mBroadcastConfigurations.size();
|
||||
return mConfiguredBroadcasts.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -505,61 +566,57 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
{
|
||||
try
|
||||
{
|
||||
if(rowIndex <= mBroadcastConfigurations.size())
|
||||
if(rowIndex <= mConfiguredBroadcasts.size())
|
||||
{
|
||||
BroadcastConfiguration configuration = mBroadcastConfigurations.get(rowIndex);
|
||||
ConfiguredBroadcast configuredBroadcast = mConfiguredBroadcasts.get(rowIndex);
|
||||
|
||||
if(configuration != null)
|
||||
if(configuredBroadcast != null)
|
||||
{
|
||||
switch(columnIndex)
|
||||
{
|
||||
case COLUMN_SERVER_ICON:
|
||||
String iconPath = configuration.getBroadcastServerType().getIconPath();
|
||||
|
||||
if(iconPath != null && mIconManager != null)
|
||||
{
|
||||
return mIconManager.getScaledIcon(iconPath, 14);
|
||||
}
|
||||
break;
|
||||
case COLUMN_BROADCAST_SERVER_TYPE:
|
||||
return configuredBroadcast.getBroadcastServerType();
|
||||
case COLUMN_STREAM_NAME:
|
||||
return configuration.getName();
|
||||
return configuredBroadcast.getBroadcastConfiguration().getName();
|
||||
case COLUMN_BROADCASTER_STATUS:
|
||||
AudioBroadcaster audioBroadcasterA = mBroadcasterMap.get(configuration.getName());
|
||||
|
||||
if(audioBroadcasterA != null)
|
||||
if(configuredBroadcast.hasAudioBroadcaster())
|
||||
{
|
||||
return audioBroadcasterA.getBroadcastState();
|
||||
return configuredBroadcast.getAudioBroadcaster().getBroadcastState();
|
||||
}
|
||||
else if(!configuration.isEnabled())
|
||||
else if(!configuredBroadcast.getBroadcastConfiguration().isEnabled())
|
||||
{
|
||||
return BroadcastState.DISABLED;
|
||||
}
|
||||
else if(!configuration.isValid())
|
||||
else if(!configuredBroadcast.getBroadcastConfiguration().isValid())
|
||||
{
|
||||
return BroadcastState.INVALID_SETTINGS;
|
||||
}
|
||||
case COLUMN_BROADCASTER_QUEUE_SIZE:
|
||||
AudioBroadcaster audioBroadcasterB = mBroadcasterMap.get(configuration.getName());
|
||||
|
||||
if(audioBroadcasterB != null)
|
||||
else
|
||||
{
|
||||
return audioBroadcasterB.getQueueSize();
|
||||
return BroadcastState.ERROR;
|
||||
}
|
||||
case COLUMN_BROADCASTER_QUEUE_SIZE:
|
||||
if(configuredBroadcast.hasAudioBroadcaster())
|
||||
{
|
||||
return configuredBroadcast.getAudioBroadcaster().getAudioQueueSize();
|
||||
}
|
||||
break;
|
||||
case COLUMN_BROADCASTER_STREAMED_COUNT:
|
||||
AudioBroadcaster audioBroadcasterC = mBroadcasterMap.get(configuration.getName());
|
||||
|
||||
if(audioBroadcasterC != null)
|
||||
if(configuredBroadcast.hasAudioBroadcaster())
|
||||
{
|
||||
return audioBroadcasterC.getStreamedAudioCount();
|
||||
return configuredBroadcast.getAudioBroadcaster().getStreamedAudioCount();
|
||||
}
|
||||
break;
|
||||
case COLUMN_BROADCASTER_AGED_OFF_COUNT:
|
||||
AudioBroadcaster audioBroadcasterD = mBroadcasterMap.get(configuration.getName());
|
||||
|
||||
if(audioBroadcasterD != null)
|
||||
if(configuredBroadcast.hasAudioBroadcaster())
|
||||
{
|
||||
return audioBroadcasterD.getAgedOffAudioCount();
|
||||
return configuredBroadcast.getAudioBroadcaster().getAgedOffAudioCount();
|
||||
}
|
||||
break;
|
||||
case COLUMN_BROADCASTER_ERROR_COUNT:
|
||||
if(configuredBroadcast.hasAudioBroadcaster())
|
||||
{
|
||||
return configuredBroadcast.getAudioBroadcaster().getAudioErrorCount();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -586,31 +643,16 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
case COLUMN_BROADCASTER_AGED_OFF_COUNT:
|
||||
case COLUMN_BROADCASTER_QUEUE_SIZE:
|
||||
case COLUMN_BROADCASTER_STREAMED_COUNT:
|
||||
case COLUMN_BROADCASTER_ERROR_COUNT:
|
||||
return Integer.class;
|
||||
case COLUMN_SERVER_ICON:
|
||||
return ImageIcon.class;
|
||||
case COLUMN_BROADCAST_SERVER_TYPE:
|
||||
return BroadcastServerType.class;
|
||||
case COLUMN_STREAM_NAME:
|
||||
default:
|
||||
return String.class;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast configuration at the specified model row
|
||||
*/
|
||||
public BroadcastConfiguration getConfigurationAt(int rowIndex)
|
||||
{
|
||||
return mBroadcastConfigurations.get(rowIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Model row number for the specified configuration
|
||||
*/
|
||||
public int getRowForConfiguration(BroadcastConfiguration configuration)
|
||||
{
|
||||
return mBroadcastConfigurations.indexOf(configuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int column)
|
||||
{
|
||||
@@ -648,37 +690,51 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
*/
|
||||
private void removeOrphanedTemporaryRecordings()
|
||||
{
|
||||
Path path = SystemProperties.getInstance().getApplicationFolder(BroadcastModel.TEMPORARY_STREAM_DIRECTORY);
|
||||
|
||||
if(path != null && Files.isDirectory(path))
|
||||
ThreadPool.SCHEDULED.submit(new Runnable()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(TEMPORARY_STREAM_FILE_SUFFIX);
|
||||
sb.append("*.*");
|
||||
|
||||
try(DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path, sb.toString()))
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
directoryStream.forEach(new Consumer<Path>()
|
||||
try
|
||||
{
|
||||
@Override
|
||||
public void accept(Path path)
|
||||
Path path = SystemProperties.getInstance().getApplicationFolder(BroadcastModel.TEMPORARY_STREAM_DIRECTORY);
|
||||
|
||||
if(path != null && Files.isDirectory(path))
|
||||
{
|
||||
try
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(TEMPORARY_STREAM_FILE_SUFFIX);
|
||||
sb.append("*.*");
|
||||
|
||||
try(DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path, sb.toString()))
|
||||
{
|
||||
Files.delete(path);
|
||||
directoryStream.forEach(new Consumer<Path>()
|
||||
{
|
||||
@Override
|
||||
public void accept(Path path)
|
||||
{
|
||||
try
|
||||
{
|
||||
Files.delete(path);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Couldn't delete orphaned temporary recording: " + path.toString(), ioe);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Couldn't delete orphaned temporary recording: " + path.toString(), ioe);
|
||||
mLog.error("Error discovering orphaned temporary stream recording files", ioe);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error during cleanup of orphaned temporary streaming recording files");
|
||||
}
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error discovering orphaned temporary stream recording files", ioe);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -742,4 +798,16 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter to receive and process broadcast events from constructed audio broadcasters
|
||||
*/
|
||||
public class BroadcastEventListener implements Listener<BroadcastEvent>
|
||||
{
|
||||
@Override
|
||||
public void receive(BroadcastEvent broadcastEvent)
|
||||
{
|
||||
process(broadcastEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
import com.jidesoft.swing.JideSplitPane;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.gui.editor.Editor;
|
||||
import io.github.dsheirer.gui.editor.EmptyEditor;
|
||||
import io.github.dsheirer.icon.IconManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
public class BroadcastPanel extends JPanel implements ActionListener, ListSelectionListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( BroadcastPanel.class );
|
||||
|
||||
private static final String NEW_BROADCAST_CONFIGURATION = "New";
|
||||
private static final String COPY_BROADCAST_CONFIGURATION = "Copy";
|
||||
private static final String DELETE_BROADCAST_CONFIGURATION = "Delete";
|
||||
|
||||
private BroadcastModel mBroadcastModel;
|
||||
private AliasModel mAliasModel;
|
||||
private IconManager mIconManager;
|
||||
private UserPreferences mUserPreferences;
|
||||
|
||||
private BroadcastStatusPanel mBroadcastStatusPanel;
|
||||
private JideSplitPane mSplitPane;
|
||||
private Editor<BroadcastConfiguration> mEmptyEditor = new EmptyEditor<>("Configuration:");
|
||||
private Editor<BroadcastConfiguration> mEditor;
|
||||
private JButton mNewButton = new JButton(NEW_BROADCAST_CONFIGURATION);
|
||||
private JButton mCopyButton = new JButton(COPY_BROADCAST_CONFIGURATION);
|
||||
private JButton mDeleteButton = new JButton(DELETE_BROADCAST_CONFIGURATION);
|
||||
|
||||
public BroadcastPanel(BroadcastModel broadcastModel, AliasModel aliasModel, IconManager iconManager,
|
||||
UserPreferences userPreferences)
|
||||
{
|
||||
mBroadcastModel = broadcastModel;
|
||||
mAliasModel = aliasModel;
|
||||
mIconManager = iconManager;
|
||||
mEditor = mEmptyEditor;
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]"));
|
||||
|
||||
mBroadcastStatusPanel = new BroadcastStatusPanel(mBroadcastModel, mUserPreferences,
|
||||
"broadcast.panel.broadcast.status.panel");
|
||||
|
||||
mBroadcastStatusPanel.getTable().setAutoCreateRowSorter(true);
|
||||
mBroadcastStatusPanel.getTable().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
mBroadcastStatusPanel.getTable().getSelectionModel().addListSelectionListener(BroadcastPanel.this);
|
||||
|
||||
JPanel buttonsPanel = new JPanel();
|
||||
buttonsPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill][grow,fill][grow,fill]", "[]"));
|
||||
|
||||
mNewButton = new JButton("New ...");
|
||||
mNewButton.setToolTipText("Create a new broadcast audio configuration");
|
||||
mNewButton.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
|
||||
menu.add(new AddBroadcastConfigurationItem(BroadcastServerType.BROADCASTIFY));
|
||||
menu.add(new AddBroadcastConfigurationItem(BroadcastServerType.ICECAST_TCP));
|
||||
menu.add(new AddBroadcastConfigurationItem(BroadcastServerType.ICECAST_HTTP));
|
||||
menu.add(new AddBroadcastConfigurationItem(BroadcastServerType.SHOUTCAST_V1));
|
||||
menu.add(new AddBroadcastConfigurationItem(BroadcastServerType.SHOUTCAST_V2));
|
||||
|
||||
menu.show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
});
|
||||
|
||||
buttonsPanel.add(mNewButton);
|
||||
|
||||
mCopyButton.addActionListener(this);
|
||||
mCopyButton.setEnabled(false);
|
||||
mCopyButton.setToolTipText("Creates a copy of the currently selected streaming configuration and adds it");
|
||||
buttonsPanel.add(mCopyButton);
|
||||
|
||||
mDeleteButton.addActionListener(this);
|
||||
mDeleteButton.setEnabled(false);
|
||||
mDeleteButton.setToolTipText("Deletes the currently selected streaming configuration");
|
||||
buttonsPanel.add(mDeleteButton);
|
||||
|
||||
JPanel listAndButtonsPanel = new JPanel();
|
||||
|
||||
listAndButtonsPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill][]"));
|
||||
|
||||
listAndButtonsPanel.add(mBroadcastStatusPanel, "wrap");
|
||||
listAndButtonsPanel.add(buttonsPanel);
|
||||
|
||||
mSplitPane = new JideSplitPane(JideSplitPane.HORIZONTAL_SPLIT);
|
||||
mSplitPane.setDividerSize(5);
|
||||
mSplitPane.setShowGripper(true);
|
||||
mSplitPane.add(listAndButtonsPanel);
|
||||
|
||||
mSplitPane.add(mEditor);
|
||||
|
||||
add(mSplitPane);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
switch(e.getActionCommand())
|
||||
{
|
||||
case COPY_BROADCAST_CONFIGURATION:
|
||||
BroadcastConfiguration copySelected = getSelectedBroadcastConfiguration();
|
||||
mBroadcastModel.cloneBroadcastConfiguration(copySelected);
|
||||
break;
|
||||
case DELETE_BROADCAST_CONFIGURATION:
|
||||
BroadcastConfiguration deleteSelected = getSelectedBroadcastConfiguration();
|
||||
|
||||
if(deleteSelected != null)
|
||||
{
|
||||
int choice = JOptionPane.showConfirmDialog(BroadcastPanel.this, "Do you want to delete broadcast\n" +
|
||||
" configuration " + deleteSelected.getName() + "?", "Delete Broadcast Configuration?",
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
|
||||
if(choice == JOptionPane.YES_OPTION)
|
||||
{
|
||||
mBroadcastModel.removeBroadcastConfiguration(deleteSelected);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the selected configuration
|
||||
*/
|
||||
private BroadcastConfiguration getSelectedBroadcastConfiguration()
|
||||
{
|
||||
int viewRow = mBroadcastStatusPanel.getTable().getSelectedRow();
|
||||
|
||||
if(viewRow >= 0)
|
||||
{
|
||||
int modelRow = mBroadcastStatusPanel.getTable().convertRowIndexToModel(viewRow);
|
||||
|
||||
if(modelRow >= 0)
|
||||
{
|
||||
return mBroadcastModel.getConfigurationAt(modelRow);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e)
|
||||
{
|
||||
if (!e.getValueIsAdjusting())
|
||||
{
|
||||
BroadcastConfiguration selectedConfiguration = getSelectedBroadcastConfiguration();
|
||||
|
||||
//Enable buttons accordingly
|
||||
mCopyButton.setEnabled(selectedConfiguration != null);
|
||||
mDeleteButton.setEnabled(selectedConfiguration != null);
|
||||
|
||||
if (mEditor.isModified())
|
||||
{
|
||||
int option = JOptionPane.showConfirmDialog(
|
||||
BroadcastPanel.this,
|
||||
"Configuration settings have changed. Do you want to save these changes?",
|
||||
"Save Changes?",
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.QUESTION_MESSAGE);
|
||||
|
||||
if (option == JOptionPane.YES_OPTION)
|
||||
{
|
||||
mEditor.save();
|
||||
}
|
||||
}
|
||||
|
||||
mSplitPane.removePane(mEditor);
|
||||
|
||||
if (selectedConfiguration != null)
|
||||
{
|
||||
mEditor = BroadcastFactory.getEditor(mUserPreferences, selectedConfiguration, mBroadcastModel,
|
||||
mAliasModel, mIconManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
mEditor = mEmptyEditor;
|
||||
}
|
||||
|
||||
mSplitPane.add(mEditor);
|
||||
|
||||
revalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public class AddBroadcastConfigurationItem extends JMenuItem
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private BroadcastServerType mBroadcastServerType;
|
||||
|
||||
public AddBroadcastConfigurationItem(BroadcastServerType type)
|
||||
{
|
||||
super(type.toString(), mIconManager.getScaledIcon(type.getIconPath(), 14));
|
||||
|
||||
mBroadcastServerType = type;
|
||||
|
||||
setToolTipText("Add a new " + type.toString() + " audio stream configuration");
|
||||
|
||||
addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
final BroadcastConfiguration configuration =
|
||||
BroadcastFactory.getConfiguration(mBroadcastServerType, BroadcastFormat.MP3);
|
||||
|
||||
if(configuration != null)
|
||||
{
|
||||
mBroadcastModel.addBroadcastConfiguration(configuration);
|
||||
|
||||
int modelRow = mBroadcastModel.getRowForConfiguration(configuration);
|
||||
int tableRow = mBroadcastStatusPanel.getTable().convertRowIndexToView(modelRow);
|
||||
mBroadcastStatusPanel.getTable().changeSelection(tableRow, 0, false, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* 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
|
||||
@@ -14,17 +14,26 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
public enum BroadcastServerType
|
||||
{
|
||||
BROADCASTIFY("Broadcastify", "/images/broadcastify.png"), //Icecast Server 2.3.2
|
||||
ICECAST_HTTP("Icecast 2 (v2.4+)", "/images/icecast.png"),
|
||||
ICECAST_TCP("Icecast (v2.3)", "/images/icecast.png"),
|
||||
SHOUTCAST_V1("Shoutcast v1.x", "/images/shoutcast.png"),
|
||||
SHOUTCAST_V2("Shoutcast v2.x", "/images/shoutcast.png"),
|
||||
/**
|
||||
* Broadcastify feeds (ie streaming) service
|
||||
*/
|
||||
BROADCASTIFY("Broadcastify Feed", "images/broadcastify.png"), //Icecast Server 2.3.2
|
||||
|
||||
/**
|
||||
* Broadcastify calls - completed audio recording push service
|
||||
*/
|
||||
BROADCASTIFY_CALL("Broadcastify Call", "images/broadcastify.png"),
|
||||
|
||||
ICECAST_HTTP("Icecast 2 (v2.4+)", "images/icecast.png"),
|
||||
ICECAST_TCP("Icecast (v2.3)", "images/icecast.png"),
|
||||
SHOUTCAST_V1("Shoutcast v1.x", "images/shoutcast.png"),
|
||||
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),
|
||||
UNKNOWN("Unknown", null);
|
||||
|
||||
private String mLabel;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* 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
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
public enum BroadcastState
|
||||
@@ -68,7 +68,7 @@ public enum BroadcastState
|
||||
/**
|
||||
* Specified mount point is already in use
|
||||
*/
|
||||
MOUNT_POINT_IN_USE("Mount Point In Use", false),
|
||||
MOUNT_POINT_IN_USE("Mount Point In Use", true),
|
||||
|
||||
/**
|
||||
* Network is unavailable or the server address cannot be resolved
|
||||
@@ -103,10 +103,10 @@ public enum BroadcastState
|
||||
private String mLabel;
|
||||
private boolean mErrorState;
|
||||
|
||||
private BroadcastState(String label, boolean connected)
|
||||
BroadcastState(String label, boolean errorState)
|
||||
{
|
||||
mLabel = label;
|
||||
mErrorState = connected;
|
||||
mErrorState = errorState;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* 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
|
||||
@@ -15,23 +14,29 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
import io.github.dsheirer.icon.Icon;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
|
||||
/**
|
||||
* Table of broadcast streams and statuses.
|
||||
*/
|
||||
public class BroadcastStatusPanel extends JPanel
|
||||
{
|
||||
private JTable mTable;
|
||||
@@ -41,6 +46,12 @@ public class BroadcastStatusPanel extends JPanel
|
||||
private UserPreferences mUserPreferences;
|
||||
private String mPreferenceKey;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param broadcastModel to access the streams
|
||||
* @param userPreferences for configuring the panel
|
||||
* @param preferenceKey to store column preferences for this panel.
|
||||
*/
|
||||
public BroadcastStatusPanel(BroadcastModel broadcastModel, UserPreferences userPreferences, String preferenceKey)
|
||||
{
|
||||
mBroadcastModel = broadcastModel;
|
||||
@@ -65,6 +76,7 @@ public class BroadcastStatusPanel extends JPanel
|
||||
renderer.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
|
||||
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS).setCellRenderer(new StatusCellRenderer());
|
||||
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCAST_SERVER_TYPE).setCellRenderer(new ServerTypeRenderer());
|
||||
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, mPreferenceKey);
|
||||
|
||||
mScrollPane = new JScrollPane(mTable);
|
||||
@@ -72,6 +84,37 @@ public class BroadcastStatusPanel extends JPanel
|
||||
add(mScrollPane);
|
||||
}
|
||||
|
||||
public class ServerTypeRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
public ServerTypeRenderer()
|
||||
{
|
||||
setOpaque(true);
|
||||
setHorizontalAlignment(SwingConstants.CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
|
||||
{
|
||||
JLabel component = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
if(value instanceof BroadcastServerType broadcastServerType)
|
||||
{
|
||||
component.setText(broadcastServerType.toString());
|
||||
Icon icon = new Icon("empty", broadcastServerType.getIconPath());
|
||||
ImageIcon imageIcon = icon.getIcon();
|
||||
ImageIcon scaledIcon = IconModel.getScaledIcon(imageIcon, 13);
|
||||
component.setIcon(scaledIcon);
|
||||
}
|
||||
else
|
||||
{
|
||||
component.setText(null);
|
||||
component.setIcon(null);
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom cell renderer for the broadcast state column.
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user