#5 Playlist Editor With Radio Reference Support (#760)

* WIP - Adds support for aliasing P25 Phase 2 tones.Playlist editor - adds editor support for alias actions

* Playlist editor implementation and integration into application is complete. Adds JavaFX window monitoring to persist window size/location across application runs.

Co-authored-by: Denny <denny@denny-desktop>
This commit is contained in:
Denny Sheirer
2020-04-26 11:53:43 +00:00
committed by GitHub
parent 39f6d6c831
commit cc52ae5bd7
290 changed files with 31251 additions and 15306 deletions

View File

@@ -23,6 +23,7 @@
<w>shoutcast</w>
<w>streamable</w>
<w>talkgroup</w>
<w>talkgroups</w>
<w>tgid</w>
<w>ultravox</w>
<w>wacn</w>

2
.idea/misc.xml generated
View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="14" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_13" default="false" project-jdk-name="14" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

10
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdr-trunk.iml" filepath="$PROJECT_DIR$/.idea/modules/sdr-trunk.iml" group="sdr-trunk" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdr-trunk_main.iml" filepath="$PROJECT_DIR$/.idea/modules/sdr-trunk_main.iml" group="sdr-trunk" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdr-trunk_test.iml" filepath="$PROJECT_DIR$/.idea/modules/sdr-trunk_test.iml" group="sdr-trunk" />
</modules>
</component>
</project>

View File

@@ -24,7 +24,7 @@ plugins {
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.8.1'
}
repositories {
@@ -33,7 +33,7 @@ repositories {
maven { url "https://jitpack.io" }
}
version = '0.4.0'
version = '0.5.0-alpha1'
sourceCompatibility = '13'
sourceSets {
@@ -65,7 +65,7 @@ dependencies {
implementation 'com.miglayout:miglayout-swing:5.2'
implementation 'com.mpatric:mp3agic:0.9.1'
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.4'
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'
@@ -124,7 +124,7 @@ jar {
}
/**
* Java Development Kit (JDK) locations. In order to build OS-specific images, these paths must point to the 'bin'
* 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.
@@ -174,7 +174,7 @@ runtime {
}
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
modules = ['java.desktop', 'java.naming', 'jdk.unsupported', 'jdk.unsupported.desktop', 'java.net.http']
modules = ['java.desktop', 'java.naming', 'jdk.unsupported', 'jdk.unsupported.desktop', 'java.net.http', 'java.sql']
imageZip = hasTargetJdk ? file("$buildDir/image/sdr-trunk.zip") : file("$buildDir/image/sdr-trunk-" + version + ".zip")
}

View File

@@ -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!/" />

View File

@@ -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,7 +616,7 @@ public class Alias
return false;
}
for(AliasID id : getId())
for(AliasID id : getAliasIdentifiers())
{
if(id.getType() == AliasIDType.BROADCAST_CHANNEL &&
((BroadcastChannel)id).getChannelName().contentEquals(channel))
@@ -350,4 +627,85 @@ public class Alias
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()};
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -1,7 +1,7 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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
@@ -24,55 +24,37 @@ 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;
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)
{
@@ -182,12 +183,12 @@ public class AliasFactory
public static Alias copyOf(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())
for(AliasID id : original.getAliasIdentifiers())
{
AliasID copyID = copyOf(id);
@@ -197,7 +198,7 @@ public class AliasFactory
}
}
for(AliasAction action : original.getAction())
for(AliasAction action : original.getAliasActions())
{
AliasAction copyAction = copyOf(action);
@@ -210,77 +211,6 @@ public class AliasFactory
return copy;
}
public static Editor<AliasID> getEditor(AliasID aliasID, BroadcastModel broadcastModel)
{
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);
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;
}
}
return new EmptyEditor<AliasID>();
}
public static Editor<AliasAction> getEditor(AliasAction aliasAction)
{
if(aliasAction != null)
{
switch(aliasAction.getType())
{
case BEEP:
return new BeepActionEditor(aliasAction);
case CLIP:
return new ClipActionEditor(aliasAction);
case SCRIPT:
return new ScriptActionEditor(aliasAction);
default:
break;
}
}
return new EmptyEditor<AliasAction>();
}
public static AliasID getAliasID(AliasIDType type)
{
switch(type)
@@ -318,7 +248,7 @@ public class AliasFactory
case SITE:
return new SiteID();
case STATUS:
return new StatusID();
return new UserStatusID();
case LEGACY_TALKGROUP:
return new LegacyTalkgroupID();
default:

View File

@@ -1,7 +1,7 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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
@@ -28,9 +28,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,8 +42,12 @@ 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;
@@ -57,9 +63,12 @@ public class AliasList implements Listener<AliasEvent>
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
@@ -72,6 +81,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
*/
@@ -79,7 +96,7 @@ public class AliasList implements Listener<AliasEvent>
{
if(alias != null)
{
for(AliasID aliasID : alias.getId())
for(AliasID aliasID : alias.getAliasIdentifiers())
{
addAliasID(aliasID, alias);
}
@@ -89,6 +106,8 @@ public class AliasList implements Listener<AliasEvent>
{
mHasAliasActions = true;
}
mAliases.add(alias);
}
/**
@@ -163,7 +182,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))
{
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))
{
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))
{
id.setOverlap(true);
Alias existing = mToneSequenceMap.get(toneSequence);
for(AliasID aliasID: existing.getAliasIdentifiers())
{
if(aliasID instanceof TonesID && ((TonesID)aliasID).equals(id))
{
aliasID.setOverlap(true);
}
}
}
else
{
mToneSequenceMap.put(toneSequence, alias);
}
}
break;
}
}
@@ -174,18 +253,112 @@ public class AliasList implements Listener<AliasEvent>
}
}
/**
* Removes the alias and alias identifier to the internal type mapping.
*/
private void removeAliasID(AliasID id, Alias alias)
{
try
{
switch(id.getType())
{
case TALKGROUP:
Talkgroup talkgroup = (Talkgroup)id;
TalkgroupAliasList talkgroupAliasList = mTalkgroupProtocolMap.get(talkgroup.getProtocol());
if(talkgroupAliasList != null)
{
talkgroupAliasList.remove(talkgroup, alias);
}
break;
case TALKGROUP_RANGE:
TalkgroupRange talkgroupRange = (TalkgroupRange)id;
TalkgroupAliasList talkgroupRangeAliasList = mTalkgroupProtocolMap.get(talkgroupRange.getProtocol());
if(talkgroupRangeAliasList != null)
{
talkgroupRangeAliasList.remove(talkgroupRange, alias);
}
break;
case RADIO_ID:
Radio radio = (Radio)id;
RadioAliasList radioAliasList = mRadioProtocolMap.get(radio.getProtocol());
if(radioAliasList != null)
{
radioAliasList.remove(radio, alias);
}
break;
case RADIO_ID_RANGE:
RadioRange radioRange = (RadioRange)id;
RadioAliasList radioRangeAliasList = mRadioProtocolMap.get(radioRange.getProtocol());
if(radioRangeAliasList != null)
{
radioRangeAliasList.remove(radioRange, alias);
}
break;
case ESN:
String esn = ((Esn) id).getEsn();
if(esn != null && !esn.isEmpty())
{
String key = esn.toLowerCase();
if(mESNMap.containsKey(key) && mESNMap.get(key) == alias)
{
mESNMap.remove(key);
}
}
break;
case STATUS:
int status = ((UserStatusID)id).getStatus();
if(mUserStatusMap.containsKey(status) && mUserStatusMap.get(status) == alias)
{
mUserStatusMap.remove(status);
}
break;
case UNIT_STATUS:
int unitStatus = ((UnitStatusID)id).getStatus();
if(mUnitStatusMap.containsKey(unitStatus) && mUnitStatusMap.get(unitStatus) == alias)
{
mUnitStatusMap.remove(unitStatus);
}
break;
case TONES:
ToneSequence toneSequence = ((TonesID)id).getToneSequence();
if(toneSequence != null && mToneSequenceMap.containsKey(toneSequence) &&
mToneSequenceMap.get(toneSequence) == alias)
{
mToneSequenceMap.remove(toneSequence);
}
break;
}
}
catch(Exception e)
{
mLog.error("Couldn't remove alias ID " + id + " for alias " + alias, e);
}
}
/**
* Removes the alias from this list
*/
public void removeAlias(Alias alias)
{
for(TalkgroupAliasList talkgroupAliasList: mTalkgroupProtocolMap.values())
for(AliasID aliasID: alias.getAliasIdentifiers())
{
talkgroupAliasList.remove(alias);
removeAliasID(aliasID, alias);
}
remove(alias, mStatusMap);
remove(alias, mESNMap);
mAliases.remove(alias);
}
/**
@@ -241,20 +414,13 @@ public class AliasList implements Listener<AliasEvent>
switch(event.getEvent())
{
case ADD:
if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList()))
if(alias.getAliasListName() != null && getName().equalsIgnoreCase(alias.getAliasListName()))
{
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()))
if(alias.getAliasListName() != null && getName().equalsIgnoreCase(alias.getAliasListName()))
{
removeAlias(alias);
}
@@ -336,14 +502,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;
}
@@ -477,13 +660,32 @@ public class AliasList implements Listener<AliasEvent>
}
/**
* Removes the alias (as a value) from the specified map
* Identifies all aliases that have alias identifiers that match the alias ID and are in the same alias list.
* @param alias for the list to search
* @param aliasID to find matches
* @return list of one or more aliases that have matching alias identifiers
*/
public static void remove(Alias alias, Map map)
public List<Alias> getOverlappingAliases(Alias alias, AliasID aliasID)
{
map.entrySet().removeIf(entry -> alias.equals(((Map.Entry)entry).getValue()));
}
List<Alias> aliases = new ArrayList<>();
for(Alias otherAlias: mAliases)
{
if(otherAlias != alias)
{
for(AliasID otherId: otherAlias.getAliasIdentifiers())
{
if(otherId.matches(aliasID) || otherId.overlaps(aliasID))
{
aliases.add(otherAlias);
continue;
}
}
}
}
return aliases;
}
/**
* Listing of talkgroups and ranges for a specific protocol
@@ -520,16 +722,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);
}
}
}
}
@@ -543,32 +751,29 @@ 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)
public void remove(Talkgroup talkgroup, Alias alias)
{
mTalkgroupAliasMap.remove(talkgroup.getValue());
if(mTalkgroupAliasMap.containsKey(talkgroup.getValue()) && mTalkgroupAliasMap.get(talkgroup.getValue()) == alias)
{
mTalkgroupAliasMap.remove(talkgroup.getValue());
}
}
public void remove(TalkgroupRange talkgroupRange)
public void remove(TalkgroupRange talkgroupRange, Alias alias)
{
mTalkgroupRangeAliasMap.remove(talkgroupRange);
}
/**
* Removes the alias from all internal maps
*/
public void remove(Alias alias)
{
AliasList.remove(alias, mTalkgroupAliasMap);
AliasList.remove(alias, mTalkgroupRangeAliasMap);
//Only remove the entry if both the key and the value match
if(mTalkgroupRangeAliasMap.containsKey(talkgroupRange) && mTalkgroupRangeAliasMap.get(talkgroupRange) == alias)
{
mTalkgroupRangeAliasMap.remove(talkgroupRange);
}
}
}
@@ -614,9 +819,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);
}
}
}
}
@@ -630,32 +841,30 @@ 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)
public void remove(Radio radio, Alias alias)
{
mRadioAliasMap.remove(radio.getValue());
//Only remove the entry if both the key and the value match
if(mRadioAliasMap.containsKey(radio.getValue()) && mRadioAliasMap.get(radio.getValue()) == alias)
{
mRadioAliasMap.remove(radio.getValue());
}
}
public void remove(RadioRange radioRange)
public void remove(RadioRange radioRange, Alias alias)
{
mRadioRangeAliasMap.remove(radioRange);
}
/**
* Removes the alias from all internal maps
*/
public void remove(Alias alias)
{
AliasList.remove(alias, mRadioAliasMap);
AliasList.remove(alias, mRadioRangeAliasMap);
//Only remove the entry if both the key and the value match
if(mRadioRangeAliasMap.containsKey(radioRange) && mRadioRangeAliasMap.get(radioRange) == alias)
{
mRadioRangeAliasMap.remove(radioRange);
}
}
}
}

View File

@@ -1,59 +1,73 @@
/*
* ******************************************************************************
* 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;
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 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 Broadcaster<AliasEvent> mAliasEventBroadcaster = new Broadcaster<>();
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 +78,17 @@ 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)
{
return mAliases.get(row);
}
List<Alias> aliasToRemove = new ArrayList<>(mAliases);
return null;
for(Alias alias: aliasToRemove)
{
removeAlias(alias);
}
}
/**
@@ -126,7 +143,7 @@ public class AliasModel extends AbstractTableModel
for(Alias alias : mAliases)
{
if(alias.hasList() && alias.getList().equalsIgnoreCase(name))
if(alias.hasList() && alias.getAliasListName().equalsIgnoreCase(name))
{
aliasList.addAlias(alias);
}
@@ -145,19 +162,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 +185,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
*/
@@ -226,19 +204,33 @@ public class AliasModel extends AbstractTableModel
if(alias != null)
{
mAliases.add(alias);
addAliasList(alias.getAliasListName());
int index = mAliases.size() - 1;
fireTableRowsInserted(index, index);
broadcast(new AliasEvent(alias, AliasEvent.Event.ADD));
return index;
}
return -1;
}
public void addAliasList(String aliasListName)
{
if(aliasListName != null && !aliasListName.isEmpty())
{
if(!mAliasListNames.contains(aliasListName))
{
mAliasListNames.add(aliasListName);
FXCollections.sort(mAliasListNames);
}
}
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
*/
@@ -246,119 +238,94 @@ public class AliasModel extends AbstractTableModel
{
if(alias != null)
{
int index = mAliases.indexOf(alias);
mAliases.remove(alias);
fireTableRowsDeleted(index, index);
broadcast(new AliasEvent(alias, AliasEvent.Event.DELETE));
}
}
/**
* 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())
{
return;
}
List<Alias> aliases = new ArrayList<>();
for(Alias alias : mAliases)
{
if(alias.hasBroadcastChannel(previousName))
if(alias.hasList() && alias.getAliasListName().equalsIgnoreCase(aliasListName))
{
for(BroadcastChannel broadcastChannel : alias.getBroadcastChannels())
for(AliasID aliasID: alias.getAliasIdentifiers())
{
if(broadcastChannel.getChannelName().contentEquals(previousName))
if(aliasID.getType() == type)
{
broadcastChannel.setChannelName(newName);
aliases.add(alias);
break;
}
}
}
}
return aliases;
}
@Override
public int getRowCount()
/**
* 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)
{
return mAliases.size();
}
@Override
public int getColumnCount()
{
return 5;
}
@Override
public String getColumnName(int columnIndex)
{
switch(columnIndex)
if(broadcastChannel == null || broadcastChannel.isEmpty())
{
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";
return false;
}
return null;
}
@Override
public Class<?> getColumnClass(int columnIndex)
{
if(columnIndex == COLUMN_COLOR)
for(Alias alias: mAliases)
{
return Integer.class;
if(alias.hasBroadcastChannel(broadcastChannel))
{
return true;
}
}
return String.class;
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex)
{
return false;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex)
/**
* 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)
{
Alias alias = mAliases.get(rowIndex);
switch(columnIndex)
if(previousStreamName == null || previousStreamName.isEmpty() || updatedStreamName == null || updatedStreamName.isEmpty())
{
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;
}
return null;
}
for(Alias alias: mAliases)
{
if(alias.hasBroadcastChannel(previousStreamName))
{
for(BroadcastChannel broadcastChannel: alias.getBroadcastChannels())
{
if(broadcastChannel.getChannelName().contentEquals(previousStreamName))
{
alias.removeAliasID(broadcastChannel);
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex)
{
throw new IllegalArgumentException("Not yet implemented");
if(!alias.hasBroadcastChannel(updatedStreamName))
{
alias.addAliasID(new BroadcastChannel(updatedStreamName));
}
}
}
}
}
}
public void broadcast(AliasEvent event)
@@ -371,13 +338,6 @@ public class AliasModel extends AbstractTableModel
alias.validate();
}
if(event.getEvent() == AliasEvent.Event.CHANGE)
{
int index = mAliases.indexOf(event.getAlias());
fireTableRowsUpdated(index, index);
}
mAliasEventBroadcaster.broadcast(event);
}
@@ -390,4 +350,40 @@ public class AliasModel extends AbstractTableModel
{
mAliasEventBroadcaster.removeListener(listener);
}
/**
* Observable list change listener for both channels and traffic channels lists
*/
public class AliasListChangeListener implements ListChangeListener<Alias>
{
@Override
public void onChanged(ListChangeListener.Change<? extends Alias> change)
{
while(change.next())
{
if(change.wasAdded())
{
for(Alias alias: change.getAddedSubList())
{
mAliasEventBroadcaster.broadcast(new AliasEvent(alias, AliasEvent.Event.ADD));
}
}
else if(change.wasRemoved())
{
for(Alias alias: change.getRemoved())
{
mAliasEventBroadcaster.broadcast(new AliasEvent(alias, AliasEvent.Event.DELETE));
}
}
else if(change.wasUpdated())
{
for(int x = change.getFrom(); x < change.getTo(); x++)
{
mAliasEventBroadcaster.broadcast(new AliasEvent(change.getList().get(x), AliasEvent.Event.CHANGE));
}
}
}
}
}
}

View File

@@ -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[0])));
;
}
}
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[0])));
;
}
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[0])));
;
}
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));
}
}
}

View File

@@ -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();
}
}

View File

@@ -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 );
}
}

View File

@@ -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[0])));
List<String> groupNames = mAliasModel.getGroupNames();
groupNames.add(0, "");
mGroupCombo.setModel(new DefaultComboBoxModel<String>(
groupNames.toArray(new String[0])));
List<String> streamNames = mBroadcastModel.getBroadcastConfigurationNames();
mStreamCombo.setModel(new DefaultComboBoxModel<String>(streamNames.toArray(new String[0])));
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[0])));
List<String> groupNames = mAliasModel.getGroupNames();
groupNames.add(0, "");
mGroupCombo.setModel(new DefaultComboBoxModel<String>(
groupNames.toArray(new String[0])));
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;
}
}
}

View File

@@ -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()};
}
}

View File

@@ -1,332 +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();
actions.sort(Comparator.comparing(Object::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 );
}
} );
}
}
}

View File

@@ -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

View File

@@ -61,7 +61,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 [");
@@ -120,6 +120,7 @@ public abstract class RecurringAction extends AliasAction
public void setPeriod(int period)
{
mPeriod = period;
updateValueProperty();
}
@JacksonXmlProperty(isAttribute = true, localName = "interval")
@@ -131,14 +132,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;

View File

@@ -24,10 +24,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 +72,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_8KHZ_16BITS_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_8KHZ_16BITS_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();
}
}

View File

@@ -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 );
}
}

View File

@@ -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();
}
}

View File

@@ -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 );
}
}

View File

@@ -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();
}
}

View File

@@ -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 );
}
}

View File

@@ -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()};
}
}

View File

@@ -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;

View File

@@ -1,370 +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();
ids.sort(Comparator.comparing(Object::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);
}
});
}
}
}

View File

@@ -1,120 +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 org.apache.commons.math3.util.FastMath;
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)(FastMath.pow(2, mValue.length() - x - 1)); //Position weight
characterCount++;
}
}
weight += (int)(FastMath.pow(2, characterCount)) * 1000; //Character count weight
return weight;
}
@Override
public int compareTo(WildcardID otherWildcardID)
{
return Integer.compare(this.weight(), otherWildcardID.weight());
}
@Override
public boolean equals(Object o) {
if (!(o instanceof WildcardID)) return false;
return compareTo((WildcardID) o) == 0;
}
}

View File

@@ -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)";
}
}

View File

@@ -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[0]));
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);
}
}
}

View File

@@ -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 );
}
}

View File

@@ -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()

View File

@@ -31,6 +31,12 @@ public class FleetsyncID extends AliasID
{
}
@Override
public boolean isAudioIdentifier()
{
return false;
}
@JacksonXmlProperty(isAttribute = true, localName = "ident")
public String getIdent()
{

View File

@@ -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 );
}
}

View File

@@ -32,6 +32,12 @@ public class MDC1200ID extends AliasID
{
}
@Override
public boolean isAudioIdentifier()
{
return false;
}
@JacksonXmlProperty(isAttribute = true, localName = "ident")
public String getIdent()
{

View File

@@ -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);
}
}

View File

@@ -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 );
}
}

View File

@@ -33,6 +33,12 @@ public class Min extends AliasID
{
}
@Override
public boolean isAudioIdentifier()
{
return false;
}
@JacksonXmlProperty(isAttribute = true, localName = "min")
public String getMin()
{

View File

@@ -32,6 +32,12 @@ public class MPT1327ID extends AliasID
{
}
@Override
public boolean isAudioIdentifier()
{
return false;
}
@JacksonXmlProperty(isAttribute = true, localName = "ident")
public String getIdent()
{

View File

@@ -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 );
}
}

View File

@@ -33,6 +33,12 @@ public class NonRecordable extends AliasID
return AliasIDType.NON_RECORDABLE;
}
@Override
public boolean isAudioIdentifier()
{
return true;
}
@Override
public boolean isValid()
{

View File

@@ -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()
{
}
}

View File

@@ -29,6 +29,12 @@ public class SiteID extends AliasID
{
}
@Override
public boolean isAudioIdentifier()
{
return false;
}
@JacksonXmlProperty(isAttribute = true, localName = "site")
public String getSite()
{

View File

@@ -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 );
}
}

View File

@@ -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()
{

View File

@@ -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);
}
}

View File

@@ -29,6 +29,12 @@ public class UniqueID extends AliasID
{
}
@Override
public boolean isAudioIdentifier()
{
return false;
}
@JacksonXmlProperty(isAttribute = true, localName = "uid")
public int getUid()
{

View File

@@ -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 );
}
}

View File

@@ -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

View File

@@ -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 );
}
}

View File

@@ -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()

View File

@@ -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 );
}
}

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}

View File

@@ -36,6 +36,8 @@ public enum RadioFormat
{
APCO25("********", 0, 0xFFFFFF, "0 to 16,777,215",
"<html>APCO25 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 +112,8 @@ public enum RadioFormat
{
case APCO25:
return APCO25;
case PASSPORT:
return PASSPORT;
default:
return UNKNOWN;
}

View File

@@ -22,8 +22,10 @@
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.UnknownTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,12 +41,12 @@ import java.util.Map;
public class RadioFormatter
{
private final static Logger mLog = LoggerFactory.getLogger(RadioFormatter.class);
private static Map<Protocol,IntegerFormatter> mFormatterMap = new EnumMap<>(Protocol.class);
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.UNKNOWN, new UnknownTalkgroupFormatter());
}
public RadioFormatter()
@@ -56,7 +58,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)
{
@@ -71,7 +73,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)
{
@@ -80,4 +82,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);
}
}

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}

View File

@@ -36,6 +36,12 @@ public class Record extends AliasID
return AliasIDType.RECORD;
}
@Override
public boolean isAudioIdentifier()
{
return true;
}
@Override
public boolean isValid()
{

View File

@@ -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()
{
}
}

View File

@@ -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;
}
}

View File

@@ -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 );
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}

View File

@@ -36,9 +36,9 @@ public enum TalkgroupFormat
{
APCO25("********", 0, 0xFFFFFF, "0 to 65,535",
"<html>APCO25 talkgroup valid range is 0 to 65,535"),
FLEETSYNC("###-####", 1, 0x7FFFFF, "001-0001 to 127-8192",
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"),
@@ -124,7 +124,6 @@ public enum TalkgroupFormat
return FLEETSYNC;
case LTR:
case LTR_NET:
case LTR_STANDARD:
return LTR;
case MDC1200:
return MDC1200;

View File

@@ -20,13 +20,15 @@
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.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.PassportTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,7 +44,7 @@ import java.util.Map;
public class TalkgroupFormatter
{
private final static Logger mLog = LoggerFactory.getLogger(TalkgroupFormatter.class);
private static Map<Protocol,IntegerFormatter> mFormatterMap = new EnumMap<>(Protocol.class);
private static Map<Protocol,AbstractIntegerFormatter> mFormatterMap = new EnumMap<>(Protocol.class);
static
{
@@ -51,11 +53,10 @@ public class TalkgroupFormatter
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.PASSPORT, new PassportTalkgroupFormatter());
mFormatterMap.put(Protocol.UNKNOWN, new IntegerFormatter());
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
}
public TalkgroupFormatter()
@@ -67,7 +68,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)
{
@@ -82,7 +83,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)
{
@@ -91,4 +92,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);
}
}

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}

View 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();
}
}

View File

@@ -125,8 +125,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();
}
}
/**

View File

@@ -25,6 +25,8 @@ import io.github.dsheirer.audio.convert.ISilenceGenerator;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -52,7 +54,7 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
private ISilenceGenerator mSilenceGenerator;
private Listener<BroadcastEvent> mBroadcastEventListener;
private BroadcastState mBroadcastState = BroadcastState.READY;
private ObjectProperty<BroadcastState> mBroadcastState = new SimpleObjectProperty<>(BroadcastState.READY);
private int mStreamedAudioCount = 0;
private int mAgedOffAudioCount = 0;
@@ -90,6 +92,18 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
mSilenceGenerator = BroadcastFactory.getSilenceGenerator(broadcastConfiguration.getBroadcastFormat());
}
/**
* Observable broadcast state property
*/
public ObjectProperty<BroadcastState> broadcastStateProperty()
{
return mBroadcastState;
}
public void dispose()
{
}
/**
* Broadcast binary audio data frames or sequences.
*/
@@ -251,18 +265,18 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
*/
protected 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;
mBroadcastState.setValue(state);
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));
if(mBroadcastState.isErrorState())
if(mBroadcastState.get().isErrorState())
{
stop();
}
@@ -291,7 +305,7 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
*/
public BroadcastState getBroadcastState()
{
return mBroadcastState;
return mBroadcastState.get();
}
/**

View File

@@ -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.audio.broadcast;
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -26,6 +29,17 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
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.binding.Bindings;
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 java.net.InetSocketAddress;
import java.net.SocketAddress;
@@ -39,18 +53,32 @@ import java.net.SocketAddress;
@JacksonXmlRootElement(localName = "stream")
public abstract class BroadcastConfiguration
{
// 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;
private StringProperty mName = new SimpleStringProperty();
private StringProperty mHost = new SimpleStringProperty();
private IntegerProperty mPort = new SimpleIntegerProperty(80);
private StringProperty mPassword = new SimpleStringProperty();
private LongProperty mDelay = new SimpleLongProperty();
private LongProperty mMaximumRecordingAge = new SimpleLongProperty(10 * 60 * 1000); //10 minutes default
private BooleanProperty mEnabled = new SimpleBooleanProperty(false);
private BooleanProperty mValid;
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 +94,76 @@ 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()
{
if(mValid == null)
{
mValid = new SimpleBooleanProperty();
mValid.bind(Bindings.and(Bindings.isNotNull(mHost), Bindings.greaterThan(mPort, 0)));
}
return mValid;
}
/**
* Broadcast server type
*/
@@ -83,7 +181,7 @@ public abstract class BroadcastConfiguration
@JacksonXmlProperty(isAttribute = true, localName = "name")
public String getName()
{
return mName;
return mName.get();
}
/**
@@ -93,7 +191,7 @@ public abstract class BroadcastConfiguration
*/
public void setName(String name)
{
mName = name;
mName.set(name);
}
/**
@@ -110,7 +208,7 @@ public abstract class BroadcastConfiguration
@JacksonXmlProperty(isAttribute = true, localName = "host")
public String getHost()
{
return mHost;
return mHost.get();
}
/**
@@ -120,7 +218,7 @@ public abstract class BroadcastConfiguration
*/
public void setHost(String host)
{
mHost = host;
mHost.set(host);
}
/**
@@ -137,7 +235,7 @@ public abstract class BroadcastConfiguration
@JacksonXmlProperty(isAttribute = true, localName = "port")
public int getPort()
{
return mPort;
return mPort.get();
}
/**
@@ -147,7 +245,7 @@ public abstract class BroadcastConfiguration
*/
public void setPort(int port)
{
mPort = port;
mPort.set(port);
}
/**
@@ -155,7 +253,7 @@ public abstract class BroadcastConfiguration
*/
public boolean hasPort()
{
return mPort > 0;
return mPort.get() > 0;
}
@JsonIgnore
@@ -170,7 +268,7 @@ public abstract class BroadcastConfiguration
@JacksonXmlProperty(isAttribute = true, localName = "password")
public String getPassword()
{
return mPassword;
return mPassword.get();
}
/**
@@ -180,7 +278,7 @@ public abstract class BroadcastConfiguration
*/
public void setPassword(String password)
{
mPassword = password;
mPassword.set(password);
}
/**
@@ -211,7 +309,7 @@ public abstract class BroadcastConfiguration
@JacksonXmlProperty(isAttribute = true, localName = "delay")
public long getDelay()
{
return mDelay;
return mDelay.get();
}
/**
@@ -220,7 +318,7 @@ public abstract class BroadcastConfiguration
*/
public void setDelay(long delay)
{
mDelay = delay;
mDelay.set(delay);
}
/**
@@ -231,7 +329,7 @@ public abstract class BroadcastConfiguration
@JacksonXmlProperty(isAttribute = true, localName = "maximum_recording_age")
public long getMaximumRecordingAge()
{
return mMaximumRecordingAge;
return mMaximumRecordingAge.get();
}
/**
@@ -241,7 +339,7 @@ public abstract class BroadcastConfiguration
*/
public void setMaximumRecordingAge(long age)
{
mMaximumRecordingAge = age;
mMaximumRecordingAge.set(age);
}
/**
@@ -250,12 +348,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 +400,15 @@ public abstract class BroadcastConfiguration
@JsonIgnore
public boolean isValid()
{
return mHost != null && mPort > 0;
return validProperty().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()};
}
}

View File

@@ -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;
}
}

View File

@@ -23,27 +23,18 @@ 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.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.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.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;
@@ -140,48 +131,6 @@ 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)
{
switch(format)

View File

@@ -1,7 +1,7 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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
@@ -29,6 +29,9 @@ 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 javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -68,11 +71,12 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
public static final String[] COLUMN_NAMES = new String[]
{"Streaming", "Name", "Status", "Queued", "Streamed", "Aged Off"};
private List<BroadcastConfiguration> mBroadcastConfigurations = new CopyOnWriteArrayList<>();
private ObservableList<ConfiguredBroadcast> mConfiguredBroadcasts =
FXCollections.observableArrayList(ConfiguredBroadcast.extractor());
private ObservableList<BroadcastConfiguration> mBroadcastConfigurations =
FXCollections.observableArrayList(BroadcastConfiguration.extractor());
private List<AudioRecording> mRecordingQueue = new CopyOnWriteArrayList<>();
private Map<String,BroadcastConfiguration> mBroadcastConfigurationMap = new HashMap<>();
private Map<String,AudioBroadcaster> mBroadcasterMap = new HashMap<>();
private Map<Integer,AudioBroadcaster> mBroadcasterMap = new HashMap<>();
private IconManager mIconManager;
private AliasModel mAliasModel;
private Broadcaster<BroadcastEvent> mBroadcastEventBroadcaster = new Broadcaster<>();
@@ -89,6 +93,31 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
ThreadPool.SCHEDULED.scheduleAtFixedRate(new RecordingDeletionMonitor(), 15l, 15l, TimeUnit.SECONDS);
removeOrphanedTemporaryRecordings();
mBroadcastConfigurations.addListener(new ConfigurationChangeListener());
//TODO: remove broadcast of change events and simply have the playlist manager register as a listener on the configs
//TODO: list to detect changes and execute playlist saves.
}
/**
* 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<BroadcastConfiguration> configsToRemove = new ArrayList<>(mBroadcastConfigurations);
for(BroadcastConfiguration configToRemove: configsToRemove)
{
removeBroadcastConfiguration(configToRemove);
}
}
/**
@@ -109,7 +138,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
/**
* Current list of broadcastAudio configurations
*/
public List<BroadcastConfiguration> getBroadcastConfigurations()
public ObservableList<BroadcastConfiguration> getBroadcastConfigurations()
{
return mBroadcastConfigurations;
}
@@ -128,7 +157,7 @@ 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)
{
@@ -137,16 +166,20 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
if(!mBroadcastConfigurations.contains(configuration))
{
mBroadcastConfigurations.add(configuration);
ConfiguredBroadcast configuredBroadcast = new ConfiguredBroadcast(configuration);
mConfiguredBroadcasts.add(configuredBroadcast);
int index = mBroadcastConfigurations.size() - 1;
fireTableRowsInserted(index, index);
mBroadcastConfigurationMap.put(configuration.getName(), configuration);
process(new BroadcastEvent(configuration, BroadcastEvent.Event.CONFIGURATION_ADD));
return configuredBroadcast;
}
}
return null;
}
/**
@@ -259,7 +292,15 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
mBroadcastConfigurations.remove(broadcastConfiguration);
mBroadcastConfigurationMap.remove(broadcastConfiguration.getName());
Iterator<ConfiguredBroadcast> it = mConfiguredBroadcasts.iterator();
while(it.hasNext())
{
if(it.next().getBroadcastConfiguration() == broadcastConfiguration)
{
it.remove();
break;
}
}
process(new BroadcastEvent(broadcastConfiguration, BroadcastEvent.Event.CONFIGURATION_DELETE));
@@ -305,16 +346,32 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
*/
private void createBroadcaster(BroadcastConfiguration broadcastConfiguration)
{
if(broadcastConfiguration != null && broadcastConfiguration.isEnabled() && broadcastConfiguration.isValid() &&
!mBroadcasterMap.containsKey(broadcastConfiguration.getName()))
if(broadcastConfiguration != null && broadcastConfiguration.isEnabled() && broadcastConfiguration.isValid())
{
//Remove current broadcaster if one exists
deleteBroadcaster(broadcastConfiguration.getId());
AudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration, mAliasModel);
if(audioBroadcaster != null)
{
audioBroadcaster.setListener(broadcastEvent -> process(broadcastEvent));
audioBroadcaster.start();
mBroadcasterMap.put(audioBroadcaster.getBroadcastConfiguration().getName(), audioBroadcaster);
Iterator<ConfiguredBroadcast> it = mConfiguredBroadcasts.iterator();
while(it.hasNext())
{
ConfiguredBroadcast configuredBroadcast = it.next();
if(configuredBroadcast.getBroadcastConfiguration() == broadcastConfiguration)
{
configuredBroadcast.setAudioBroadcaster(audioBroadcaster);
break;
}
}
mBroadcasterMap.put(audioBroadcaster.getBroadcastConfiguration().getId(), audioBroadcaster);
int index = mBroadcastConfigurations.indexOf(audioBroadcaster.getBroadcastConfiguration());
if(index >= 0)
@@ -330,16 +387,29 @@ 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(int id)
{
if(name != null && mBroadcasterMap.containsKey(name))
if(mBroadcasterMap.containsKey(id))
{
AudioBroadcaster audioBroadcaster = mBroadcasterMap.remove(name);
AudioBroadcaster audioBroadcaster = mBroadcasterMap.remove(id);
if(audioBroadcaster != null)
{
Iterator<ConfiguredBroadcast> it = mConfiguredBroadcasts.iterator();
while(it.hasNext())
{
ConfiguredBroadcast configuredBroadcast = it.next();
if(configuredBroadcast.getBroadcastConfiguration().getId() == id)
{
configuredBroadcast.setAudioBroadcaster(null);
break;
}
}
audioBroadcaster.stop();
audioBroadcaster.removeListener();
audioBroadcaster.dispose();
int index = mBroadcastConfigurations.indexOf(audioBroadcaster.getBroadcastConfiguration());
@@ -358,7 +428,15 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
*/
public BroadcastConfiguration getBroadcastConfiguration(String streamName)
{
return mBroadcastConfigurationMap.get(streamName);
for(BroadcastConfiguration config: mBroadcastConfigurations)
{
if(config.getName() != null && config.getName().contentEquals(streamName))
{
return config;
}
}
return null;
}
/**
@@ -399,24 +477,23 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
break;
case CONFIGURATION_CHANGE:
BroadcastConfiguration broadcastConfiguration = broadcastEvent.getBroadcastConfiguration();
int index = mBroadcastConfigurations.indexOf(broadcastConfiguration);
//Delete the existing broadcaster for any broadcast configuration changes
String previousChannelName = cleanupMapAssociations(broadcastConfiguration);
deleteBroadcaster(previousChannelName);
//Delete the broadcaster if it exists
deleteBroadcaster(broadcastConfiguration.getId());
//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 = mBroadcastConfigurations.indexOf(broadcastConfiguration);
fireTableRowsUpdated(index, index);
break;
case CONFIGURATION_DELETE:
deleteBroadcaster(broadcastEvent.getBroadcastConfiguration().getName());
deleteBroadcaster(broadcastEvent.getBroadcastConfiguration().getId());
break;
}
}
@@ -457,37 +534,6 @@ 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()
{
@@ -649,37 +695,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);
}
}
});
}
/**
@@ -743,4 +803,29 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
}
}
}
/**
* List change listener to detect broadcast configuration changes and broadcast a change event
*/
public class ConfigurationChangeListener implements ListChangeListener<BroadcastConfiguration>
{
@Override
public void onChanged(Change<? extends BroadcastConfiguration> change)
{
while(change.next())
{
if(change.wasUpdated())
{
for(int x = change.getFrom(); x < change.getTo(); x++)
{
BroadcastConfiguration config = change.getList().get(x);
BroadcastEvent event = new BroadcastEvent(config, BroadcastEvent.Event.CONFIGURATION_CHANGE);
process(event);
broadcast(event);
}
}
}
}
}
}

View File

@@ -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);
}
}
});
}
}
}

View File

@@ -0,0 +1,142 @@
/*
*
* * ******************************************************************************
* * 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 javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.util.Callback;
/**
* Composite observable object that joins a broadcast configuration and (optional) constructed audio broadcaster
*/
public class ConfiguredBroadcast
{
private BroadcastConfiguration mBroadcastConfiguration;
private AudioBroadcaster mAudioBroadcaster;
private ObjectProperty<BroadcastState> mBroadcastState = new SimpleObjectProperty<>();
/**
* Constructs an instance
* @param broadcastConfiguration for the instance
*/
public ConfiguredBroadcast(BroadcastConfiguration broadcastConfiguration)
{
mBroadcastConfiguration = broadcastConfiguration;
mBroadcastConfiguration.validProperty().addListener((observable, oldValue, newValue) -> updateBroadcastState());
updateBroadcastState();
}
/**
* Configuration for this broadcast
*/
public BroadcastConfiguration getBroadcastConfiguration()
{
return mBroadcastConfiguration;
}
/**
* Enabled state of the configuration
*/
public BooleanProperty enabledProperty()
{
return mBroadcastConfiguration.enabledProperty();
}
/**
* Name of the broadcast configuration
*/
public StringProperty nameProperty()
{
return mBroadcastConfiguration.nameProperty();
}
/**
* Server type for the broadcast configuration
*/
public BroadcastServerType getBroadcastServerType()
{
return mBroadcastConfiguration.getBroadcastServerType();
}
/**
* Broadcast state of the configured audio broadcaster (optional)
*/
public ObjectProperty<BroadcastState> broadcastStateProperty()
{
return mBroadcastState;
}
/**
* Sets the audio broadcaster
* @param audioBroadcaster to use for this configuration
*/
public void setAudioBroadcaster(AudioBroadcaster audioBroadcaster)
{
mBroadcastState.unbind();
mAudioBroadcaster = audioBroadcaster;
if(audioBroadcaster != null)
{
mBroadcastState.bind(mAudioBroadcaster.broadcastStateProperty());
}
else
{
updateBroadcastState();
}
}
private void updateBroadcastState()
{
if(!mBroadcastState.isBound())
{
if(mBroadcastConfiguration.isValid())
{
mBroadcastState.setValue(BroadcastState.READY);
}
else
{
mBroadcastState.setValue(BroadcastState.CONFIGURATION_ERROR);
}
}
}
/**
* Optional audio broadcaster created from the configuration
*/
public AudioBroadcaster getAudioBroadcaster()
{
return mAudioBroadcaster;
}
/**
* Creates an observable property extractor for use with observable lists to detect changes internal to this object.
*/
public static Callback<ConfiguredBroadcast, Observable[]> extractor()
{
return (ConfiguredBroadcast b) -> new Observable[] {b.nameProperty(), b.enabledProperty(),
b.broadcastStateProperty(), b.getBroadcastConfiguration().validProperty()};
}
}

View File

@@ -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.audio.broadcast.broadcastify;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
@@ -23,9 +26,14 @@ import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPConfiguration;
import io.github.dsheirer.rrapi.type.UserFeedBroadcast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BroadcastifyConfiguration extends IcecastTCPConfiguration
{
private final static Logger mLog = LoggerFactory.getLogger(BroadcastifyConfiguration.class);
private int mFeedID;
public BroadcastifyConfiguration()
@@ -48,6 +56,27 @@ public class BroadcastifyConfiguration extends IcecastTCPConfiguration
setSampleRate(8000);
}
public static BroadcastifyConfiguration from(UserFeedBroadcast userFeedBroadcast)
{
BroadcastifyConfiguration config = new BroadcastifyConfiguration(BroadcastFormat.MP3);
config.setName(userFeedBroadcast.getDescription());
config.setHost(userFeedBroadcast.getHostname());
config.setMountPoint(userFeedBroadcast.getMount());
config.setFeedID(userFeedBroadcast.getFeedId());
config.setPassword(userFeedBroadcast.getPassword());
try
{
config.setPort(Integer.parseInt(userFeedBroadcast.getPort()));
}
catch(Exception e)
{
mLog.error("Error creating broadcastify configuration from radio reference user feed instance");
}
return config;
}
@Override
public BroadcastConfiguration copyOf()
{

View File

@@ -1,408 +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.broadcastify;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastConfigurationEditor;
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
import io.github.dsheirer.audio.broadcast.BroadcastModel;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.rrapi.type.UserFeedBroadcast;
import io.github.dsheirer.sample.Listener;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class BroadcastifyConfigurationEditor extends BroadcastConfigurationEditor implements Listener<UserFeedBroadcast>
{
private final static Logger mLog = LoggerFactory.getLogger(BroadcastifyConfigurationEditor.class);
private static final int ONE_MINUTE_MS = 60000;
private JTextField mName;
private JTextField mServer;
private JTextField mPort;
private JTextField mMountPoint;
private JTextField mPassword;
private JTextField mFeedID;
private JSlider mDelay;
private JLabel mDelayValue;
private JSlider mMaximumRecordingAge;
private JLabel mMaximumRecordingAgeValue;
private JCheckBox mEnabled;
private JButton mSaveButton;
private JButton mResetButton;
private UserPreferences mUserPreferences;
public BroadcastifyConfigurationEditor(UserPreferences userPreferences, BroadcastModel broadcastModel,
AliasModel aliasModel, IconManager iconManager)
{
super(broadcastModel, aliasModel, iconManager);
mUserPreferences = userPreferences;
init();
}
private void init()
{
setLayout(new MigLayout("fill,wrap 2", "[align right][grow,fill]",
"[][][][][][][][][][][][][][][grow,fill]"));
setPreferredSize(new Dimension(150, 400));
JLabel channelLabel = new JLabel("Broadcastify Stream");
ImageIcon broadcastifyIcon = mIconManager.getScaledIcon(BroadcastServerType.BROADCASTIFY.getIconPath(), 25);
channelLabel.setIcon(broadcastifyIcon);
add(channelLabel, "span, align center");
JButton lookupButton = new JButton("Lookup ...");
lookupButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
final UserFeedSelectionDialog dialog = new UserFeedSelectionDialog(mUserPreferences,BroadcastifyConfigurationEditor.this);
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
dialog.setLocationRelativeTo(BroadcastifyConfigurationEditor.this);
dialog.setVisible(true);
}
});
}
});
add(new JLabel(""));
add(lookupButton, "grow,wrap");
add(new JLabel("Name:"));
mName = new JTextField();
mName.getDocument().addDocumentListener(this);
mName.setToolTipText("Unique name for this stream");
add(mName);
add(new JLabel("Server:"));
mServer = new JTextField();
mServer.getDocument().addDocumentListener(this);
mServer.setToolTipText("Server address (e.g. audio3.broadcastify.com). See MyBCFY feed details, Technicals tab");
add(mServer);
add(new JLabel("Port:"));
mPort = new JTextField();
mPort.getDocument().addDocumentListener(this);
mPort.setToolTipText("Server port number (e.g. 80). See MyBCFY feed details, Technicals tab");
add(mPort);
add(new JLabel("Mount:"));
mMountPoint = new JTextField();
mMountPoint.getDocument().addDocumentListener(this);
mMountPoint.setToolTipText("Mount point (e.g. \\awlkejrlkjsd). See MyBCFY feed details, Technicals tab");
add(mMountPoint);
add(new JLabel("Password:"));
mPassword = new JTextField();
mPassword.getDocument().addDocumentListener(this);
mPassword.setToolTipText("Password. See MyBCFY feed details, Technicals tab");
add(mPassword);
add(new JLabel("Feed ID:"));
mFeedID = new JTextField();
mFeedID.getDocument().addDocumentListener(this);
mFeedID.setToolTipText("Feed ID (optional). See MyBCFY feed details");
add(mFeedID);
add(new JLabel());
mDelayValue = new JLabel("0 Minutes");
add(mDelayValue);
add(new JLabel("Delay:"));
mDelay = new JSlider(0, 60, 0);
mDelay.setMajorTickSpacing(10);
mDelay.setMinorTickSpacing(5);
mDelay.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent e)
{
mDelayValue.setText(mDelay.getValue() + " Minutes");
setModified(true);
}
});
mDelay.setToolTipText("Audio broadcast delay in minutes");
add(mDelay);
add(new JLabel());
mMaximumRecordingAgeValue = new JLabel("1 Minute");
add(mMaximumRecordingAgeValue);
add(new JLabel("Age Limit:"));
mMaximumRecordingAge = new JSlider(1,60,5);
mMaximumRecordingAge.setMajorTickSpacing(10);
mMaximumRecordingAge.setMinorTickSpacing(5);
mMaximumRecordingAge.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent e)
{
int value = mMaximumRecordingAge.getValue();
mMaximumRecordingAgeValue.setText(value + " Minute" + (value > 1 ? "s" : ""));
setModified(true);
}
});
mMaximumRecordingAge.setToolTipText("Maximum recording age (in addition to delay) to stream");
add(mMaximumRecordingAge);
mEnabled = new JCheckBox("Enabled");
mEnabled.setToolTipText("Enable (checked) or disable this stream");
mEnabled.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(new JLabel());
add(mEnabled);
mSaveButton = new JButton("Save");
mSaveButton.setEnabled(false);
mSaveButton.setToolTipText("Click to save configuration information");
mSaveButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
save();
}
});
add(mSaveButton);
mResetButton = new JButton("Reset");
mResetButton.setEnabled(false);
mResetButton.setToolTipText("Click to reset any changes you've made\n since the last save");
mResetButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(false);
setItem(getItem());
}
});
add(mResetButton);
}
@Override
public void setModified(boolean modified)
{
super.setModified(modified);
mSaveButton.setEnabled(modified);
mResetButton.setEnabled(modified);
}
@Override
public void setItem(BroadcastConfiguration item)
{
super.setItem(item);
if(hasItem())
{
BroadcastifyConfiguration config = (BroadcastifyConfiguration) getItem();
mName.setText(config.getName());
mServer.setText(config.getHost());
mPort.setText(config.getPort() != 0 ? String.valueOf(config.getPort()) : null);
mMountPoint.setText(config.getMountPoint());
mPassword.setText(config.getPassword());
mFeedID.setText(String.valueOf(config.getFeedID()));
mDelay.setValue((int) (config.getDelay() / ONE_MINUTE_MS));
mMaximumRecordingAge.setValue((int)config.getMaximumRecordingAge() / ONE_MINUTE_MS);
mEnabled.setSelected(config.isEnabled());
}
else
{
mName.setText(null);
mServer.setText(null);
mPort.setText(null);
mMountPoint.setText(null);
mPassword.setText(null);
mFeedID.setText(null);
mDelay.setValue(0);
mMaximumRecordingAge.setValue(1);
mEnabled.setSelected(false);
}
setModified(false);
}
@Override
public void save()
{
BroadcastifyConfiguration config = (BroadcastifyConfiguration) getItem();
if(validateConfiguration())
{
updateConfigurationName(config, mName.getText());
config.setHost(mServer.getText());
config.setPort(getPort());
config.setMountPoint(mMountPoint.getText());
config.setPassword(mPassword.getText());
config.setFeedID(getFeedID());
config.setDelay(mDelay.getValue() * ONE_MINUTE_MS);
config.setMaximumRecordingAge(mMaximumRecordingAge.getValue() * ONE_MINUTE_MS);
config.setEnabled(mEnabled.isSelected());
setModified(false);
mBroadcastModel.process(new BroadcastEvent(getItem(), BroadcastEvent.Event.CONFIGURATION_CHANGE));
}
}
/**
* Validates the user specified values in the controls prior to saving them to the configuration
*/
private boolean validateConfiguration()
{
String name = mName.getText();
//A unique stream name is required
if(!mBroadcastModel.isUniqueName(name, getItem()))
{
String message = (name == null || name.isEmpty()) ?
"Please specify a stream name." : "Stream name " + name +
"is already in use.\nPlease choose another name.";
JOptionPane.showMessageDialog(BroadcastifyConfigurationEditor.this,
message, "Invalid Stream Name", JOptionPane.ERROR_MESSAGE);
mName.requestFocus();
return false;
}
//Server address is optional, but required
if(!validateTextField(mServer, "Invalid Server Address", "Please specify a server address."))
{
return true;
}
//Port is optional, but required
if(!validateIntegerTextField(mPort, "Invalid Port Number", "Please specify a port number (1 <> 65535)", 1, 65535))
{
return true;
}
//Mount Point is optional, but required
if(!validateTextField(mMountPoint, "Invalid Mount Point", "Please specify a mount point."))
{
return true;
}
//Password is optional, but required
if(!validateTextField(mPassword, "Invalid Password", "Please specify a password."))
{
return true;
}
return true;
}
/**
* Parses an Integer from the port text control or returns 0 if the control doesn't contain an integer value.
*/
private int getPort()
{
String port = mPort.getText();
if(port != null && !port.isEmpty())
{
try
{
return Integer.parseInt(port);
}
catch(Exception e)
{
//Do nothing, we couldn't parse the number value
}
}
return 0;
}
/**
* Parses an Integer from the port text control or returns 0 if the control doesn't contain an integer value.
*/
private int getFeedID()
{
String feedID = mFeedID.getText();
if(feedID != null && !feedID.isEmpty())
{
try
{
return Integer.parseInt(feedID);
}
catch(Exception e)
{
//Do nothing, we couldn't parse the number value
}
}
return 0;
}
@Override
public void receive(UserFeedBroadcast userFeedBroadcast)
{
mName.setText(userFeedBroadcast.getDescription());
mServer.setText(userFeedBroadcast.getHostname());
mPort.setText(userFeedBroadcast.getPort());
mMountPoint.setText(userFeedBroadcast.getMount());
mPassword.setText(userFeedBroadcast.getPassword());
mFeedID.setText(String.valueOf(userFeedBroadcast.getFeedId()));
}
}

View File

@@ -1,343 +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.broadcastify;
import com.google.common.eventbus.Subscribe;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.rrapi.RadioReferenceException;
import io.github.dsheirer.rrapi.RadioReferenceService;
import io.github.dsheirer.rrapi.type.AuthorizationInformation;
import io.github.dsheirer.rrapi.type.UserFeedBroadcast;
import io.github.dsheirer.rrapi.type.UserInfo;
import io.github.dsheirer.sample.Listener;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListCellRenderer;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
public class UserFeedSelectionDialog extends JDialog implements DocumentListener
{
private final static Logger mLog = LoggerFactory.getLogger( UserFeedSelectionDialog.class );
public static final String SDRTRUNK_APP_KEY = "88969092";
private Listener<UserFeedBroadcast> mListener;
private UserPreferences mUserPreferences;
private JTextField mUserNameText;
private JTextField mPasswordText;
private JCheckBox mSaveCredentialsCheckBox;
private JButton mClearStoredCredentialsButton;
private JButton mLookupButton;
private JList mUserFeedBroadcastList;
private JButton mOKButton;
public UserFeedSelectionDialog(UserPreferences userPreferences, Listener<UserFeedBroadcast> listener)
{
mUserPreferences = userPreferences;
mListener = listener;
//Register to receive radio reference preferences updates
MyEventBus.getEventBus().register(this);
init();
}
private void init()
{
JPanel panel = new JPanel();
panel.setLayout(new MigLayout("","[align right,grow,fill][align left,grow,fill][]","[][][][][][grow][]"));
panel.add(new JLabel("Please enter your credentials"), "align left,span");
panel.add(new JLabel("User Name:"));
mUserNameText = new JTextField();
String storedUserName = mUserPreferences.getRadioReferencePreference().getUserName();
if(storedUserName != null)
{
mUserNameText.setText(storedUserName);
}
mUserNameText.getDocument().addDocumentListener(this);
panel.add(mUserNameText, "wrap");
panel.add(new JLabel("Password:"));
mPasswordText = new JTextField();
String storedPassword = mUserPreferences.getRadioReferencePreference().getPassword();
if(storedPassword != null)
{
mPasswordText.setText(storedPassword);
}
mPasswordText.getDocument().addDocumentListener(this);
panel.add(mPasswordText, "wrap");
mSaveCredentialsCheckBox = new JCheckBox("Store Credentials");
mSaveCredentialsCheckBox.setSelected(mUserPreferences.getRadioReferencePreference().isStoreCredentials());
mSaveCredentialsCheckBox.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
//If user unchecks box and there are stored credentials, prompt to clear them
if(!mSaveCredentialsCheckBox.isSelected() && mUserPreferences.getRadioReferencePreference().hasStoredCredentials())
{
int choice = JOptionPane.showOptionDialog(UserFeedSelectionDialog.this,
"Would you like to remove previously stored credentials from this computer?",
"Clear Stored Credentials?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE,
null, null, 0);
if(choice == JOptionPane.YES_OPTION)
{
mUserPreferences.getRadioReferencePreference().removeStoreCredentials();
}
}
}
});
panel.add(mSaveCredentialsCheckBox);
mClearStoredCredentialsButton = new JButton("Remove");
mClearStoredCredentialsButton.setToolTipText("Clear stored credentials");
mClearStoredCredentialsButton.setEnabled(mUserPreferences.getRadioReferencePreference().hasStoredCredentials());
mClearStoredCredentialsButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
mUserPreferences.getRadioReferencePreference().removeStoreCredentials();
}
});
panel.add(mClearStoredCredentialsButton, "wrap");
mLookupButton = new JButton("Get Feeds");
mLookupButton.setEnabled(storedPassword != null && storedPassword != null);
mLookupButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
mLookupButton.setEnabled(false);
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
try
{
AuthorizationInformation authorizationInformation = new AuthorizationInformation(SDRTRUNK_APP_KEY,
mUserNameText.getText(), mPasswordText.getText());
mLog.debug("User:" + authorizationInformation.getUserName() + " Pass:" + authorizationInformation.getPassword() + " Key:" + authorizationInformation.getApplicationKey());
RadioReferenceService service = new RadioReferenceService(authorizationInformation);
UserInfo userInfo = service.getUserInfo();
mLog.info("RR User:" + userInfo.getUserName() + " Expiration Date:" + userInfo.getExpirationDate());
List<UserFeedBroadcast> feeds = service.getUserFeeds();
mUserFeedBroadcastList.setListData(feeds.toArray(new UserFeedBroadcast[0]));
}
catch(RadioReferenceException rre)
{
JOptionPane.showMessageDialog(UserFeedSelectionDialog.this,
"Unable to create radio reference web service client. See log file.",
"Error", JOptionPane.ERROR_MESSAGE);
mLog.error("Error creating the radio reference web service client", rre);
}
mLookupButton.setEnabled(true);
}
});
}
});
panel.add(mLookupButton,"grow,span");
mUserFeedBroadcastList = new JList();
mUserFeedBroadcastList.setVisibleRowCount(3);
mUserFeedBroadcastList.setLayoutOrientation(JList.VERTICAL);
mUserFeedBroadcastList.setCellRenderer(new CellRenderer());
mUserFeedBroadcastList.addListSelectionListener(new ListSelectionListener()
{
@Override
public void valueChanged(ListSelectionEvent e)
{
mOKButton.setEnabled(mUserFeedBroadcastList.getSelectedValue() != null);
}
});
JScrollPane listScrollPane = new JScrollPane(mUserFeedBroadcastList);
panel.add(listScrollPane, "grow,span");
mOKButton = new JButton("Ok");
mOKButton.setEnabled(false);
mOKButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
if(mListener != null)
{
storeCredentials();
mListener.receive((UserFeedBroadcast)mUserFeedBroadcastList.getSelectedValue());
}
dispose();
}
});
panel.add(mOKButton,"grow");
JButton cancelButton = new JButton("Cancel");
cancelButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
dispose();
}
});
panel.add(cancelButton, "wrap");
setTitle("Broadcastify Feed Lookup");
setLayout(new BorderLayout());
add(panel, BorderLayout.CENTER);
setSize(400,450);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setModalityType(ModalityType.APPLICATION_MODAL);
}
/**
* Stores login credentials in the user preferences
*/
private void storeCredentials()
{
boolean store = mSaveCredentialsCheckBox.isSelected();
mUserPreferences.getRadioReferencePreference().setStoreCredentials(store);
if(store)
{
String userName = mUserNameText.getText();
if(userName != null)
{
mUserPreferences.getRadioReferencePreference().setUserName(userName);
}
String password = mPasswordText.getText();
if(password != null)
{
mUserPreferences.getRadioReferencePreference().setPassword(password);
}
}
}
private void updateButtons()
{
String username = mUserNameText.getText();
String password = mPasswordText.getText();
mLookupButton.setEnabled(username != null && !username.isEmpty() && password != null && !password.isEmpty());
}
@Override
public void insertUpdate(DocumentEvent e)
{
updateButtons();
}
@Override
public void removeUpdate(DocumentEvent e)
{
updateButtons();
}
@Override
public void changedUpdate(DocumentEvent e)
{
updateButtons();
}
private class CellRenderer extends JLabel implements ListCellRenderer<UserFeedBroadcast>
{
public CellRenderer()
{
setOpaque(true);
setHorizontalAlignment(LEFT);
setVerticalAlignment(CENTER);
}
@Override
public Component getListCellRendererComponent(JList<? extends UserFeedBroadcast> list,
UserFeedBroadcast value,
int index,
boolean isSelected,
boolean cellHasFocus)
{
if (isSelected)
{
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
}
else
{
setBackground(list.getBackground());
setForeground(list.getForeground());
}
setText(value.getDescription());
return this;
}
}
@Subscribe
public void preferenceUpdated(PreferenceType preferenceType)
{
if(preferenceType == PreferenceType.RADIO_REFERENCE)
{
//Update the store-credentials checkbox only, so we don't change the username or password currently displayed
mSaveCredentialsCheckBox.setSelected(mUserPreferences.getRadioReferencePreference().isStoreCredentials());
mClearStoredCredentialsButton.setEnabled(mUserPreferences.getRadioReferencePreference().hasStoredCredentials());
}
}
}

View File

@@ -1,383 +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.icecast;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastConfigurationEditor;
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
import io.github.dsheirer.audio.broadcast.BroadcastModel;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.icon.IconManager;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class IcecastHTTPConfigurationEditor extends BroadcastConfigurationEditor
{
private final static Logger mLog = LoggerFactory.getLogger( IcecastHTTPConfigurationEditor.class );
private static final int ONE_MINUTE_MS = 60000;
private JTextField mName;
private JTextField mServer;
private JTextField mPort;
private JTextField mMountPoint;
private JTextField mUsername;
private JTextField mPassword;
private JTextField mDescription;
private JTextField mGenre;
private JCheckBox mPublic;
private JSlider mDelay;
private JLabel mDelayValue;
private JSlider mMaximumRecordingAge;
private JLabel mMaximumRecordingAgeValue;
private JCheckBox mEnabled;
private JButton mSaveButton;
private JButton mResetButton;
public IcecastHTTPConfigurationEditor(BroadcastModel broadcastModel, AliasModel aliasModel, IconManager iconManager)
{
super(broadcastModel, aliasModel, iconManager);
init();
}
private void init()
{
setLayout( new MigLayout( "fill,wrap 2", "[align right][grow,fill]",
"[][][][][][][][][][][][][][][][][grow,fill]" ) );
setPreferredSize(new Dimension(150,400));
JLabel channelLabel = new JLabel("Icecast 2 (v2.4+) Stream");
ImageIcon icon = mIconManager.getScaledIcon(BroadcastServerType.ICECAST_HTTP.getIconPath(), 25);
channelLabel.setIcon(icon);
add(channelLabel, "span, align center");
add(new JLabel("Name:"));
mName = new JTextField();
mName.getDocument().addDocumentListener(this);
mName.setToolTipText("Unique name for this stream");
add(mName);
add(new JLabel("Server:"));
mServer = new JTextField();
mServer.getDocument().addDocumentListener(this);
mServer.setToolTipText("Server address (e.g. audio1.icecast.com or localhost)");
add(mServer);
add(new JLabel("Port:"));
mPort = new JTextField();
mPort.getDocument().addDocumentListener(this);
mPort.setToolTipText("Server port number (e.g. 80)");
add(mPort);
add(new JLabel("Mount:"));
mMountPoint = new JTextField();
mMountPoint.getDocument().addDocumentListener(this);
mMountPoint.setToolTipText("Mount point (e.g. /12345)");
add(mMountPoint);
add(new JLabel("User Name:"));
mUsername = new JTextField();
mUsername.getDocument().addDocumentListener(this);
mUsername.setToolTipText("User name for the stream. Default: source");
add(mUsername);
add(new JLabel("Password:"));
mPassword = new JTextField();
mPassword.getDocument().addDocumentListener(this);
mPassword.setToolTipText("Password for the stream");
add(mPassword);
add(new JLabel("Description:"));
mDescription = new JTextField();
mDescription.getDocument().addDocumentListener(this);
mDescription.setToolTipText("Description of the stream contents");
add(mDescription);
add(new JLabel("Genre:"));
mGenre = new JTextField();
mGenre.getDocument().addDocumentListener(this);
mGenre.setToolTipText("Genre (e.g. public safety)");
add(mGenre);
add(new JLabel("Public:"));
mPublic = new JCheckBox();
mPublic.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
mPublic.setToolTipText("Designate stream as public (checked) or private");
add(mPublic);
add(new JLabel());
mDelayValue = new JLabel("0 Minutes");
add(mDelayValue);
add(new JLabel("Delay:"));
mDelay = new JSlider(0,60,0);
mDelay.setMajorTickSpacing(10);
mDelay.setMinorTickSpacing(5);
mDelay.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent e)
{
mDelayValue.setText(mDelay.getValue() + " Minutes");
setModified(true);
}
});
mDelay.setToolTipText("Audio broadcast delay in minutes");
add(mDelay);
add(new JLabel());
mMaximumRecordingAgeValue = new JLabel("1 Minute");
add(mMaximumRecordingAgeValue);
add(new JLabel("Age Limit:"));
mMaximumRecordingAge = new JSlider(1,60,5);
mMaximumRecordingAge.setMajorTickSpacing(10);
mMaximumRecordingAge.setMinorTickSpacing(5);
mMaximumRecordingAge.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent e)
{
int value = mMaximumRecordingAge.getValue();
mMaximumRecordingAgeValue.setText(value + " Minute" + (value > 1 ? "s" : ""));
setModified(true);
}
});
mMaximumRecordingAge.setToolTipText("Maximum recording age (in addition to delay) to stream");
add(mMaximumRecordingAge);
add(new JLabel("Enabled:"));
mEnabled = new JCheckBox();
mEnabled.setToolTipText("Enable (checked) or disable this stream");
mEnabled.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(mEnabled);
mSaveButton = new JButton("Save");
mSaveButton.setEnabled(false);
mSaveButton.setToolTipText("Click to save configuration information");
mSaveButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
save();
}
});
add(mSaveButton);
mResetButton = new JButton("Reset");
mResetButton.setEnabled(false);
mResetButton.setToolTipText("Click to reset any changes you've made since the last save");
mResetButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(false);
setItem(getItem());
}
});
add(mResetButton);
}
@Override
public void setModified(boolean modified)
{
super.setModified(modified);
mSaveButton.setEnabled(modified);
mResetButton.setEnabled(modified);
}
@Override
public void setItem(BroadcastConfiguration item)
{
super.setItem(item);
if(hasItem())
{
IcecastHTTPConfiguration config = (IcecastHTTPConfiguration)getItem();
mName.setText(config.getName());
mServer.setText(config.getHost());
mPort.setText(config.getPort() != 0 ? String.valueOf(config.getPort()) : null);
mMountPoint.setText(config.getMountPoint());
mUsername.setText(config.getUserName());
mPassword.setText(config.getPassword());
mDescription.setText(config.getDescription());
mGenre.setText(config.getGenre());
mPublic.setSelected(config.isPublic());
mDelay.setValue((int)(config.getDelay() / ONE_MINUTE_MS));
mMaximumRecordingAge.setValue((int)config.getMaximumRecordingAge() / ONE_MINUTE_MS);
mEnabled.setSelected(config.isEnabled());
}
else
{
mName.setText(null);
mServer.setText(null);
mPort.setText(null);
mMountPoint.setText(null);
mUsername.setText(null);
mPassword.setText(null);
mDescription.setText(null);
mGenre.setText(null);
mPublic.setSelected(true);
mDelay.setValue(0);
mMaximumRecordingAge.setValue(1);
mEnabled.setSelected(false);
}
setModified(false);
}
@Override
public void save()
{
IcecastHTTPConfiguration config = (IcecastHTTPConfiguration)getItem();
if(validateConfiguration())
{
updateConfigurationName(config, mName.getText());
config.setHost(mServer.getText());
config.setPort(getPort());
config.setMountPoint(mMountPoint.getText());
config.setUserName(mUsername.getText());
config.setPassword(mPassword.getText());
config.setDescription(mDescription.getText());
config.setGenre(mGenre.getText());
config.setPublic(mPublic.isSelected());
config.setDelay(mDelay.getValue() * ONE_MINUTE_MS);
config.setMaximumRecordingAge(mMaximumRecordingAge.getValue() * ONE_MINUTE_MS);
config.setEnabled(mEnabled.isSelected());
setModified(false);
mBroadcastModel.process(new BroadcastEvent(getItem(), BroadcastEvent.Event.CONFIGURATION_CHANGE));
}
}
/**
* Validates the user specified values in the controls prior to saving them to the configuration
*/
private boolean validateConfiguration()
{
String name = mName.getText();
//A unique stream name is required
if(!mBroadcastModel.isUniqueName(name, getItem()))
{
String message = (name == null || name.isEmpty()) ?
"Please specify a stream name." : "Stream name " + name +
"is already in use.\nPlease choose another name.";
JOptionPane.showMessageDialog(IcecastHTTPConfigurationEditor.this,
message, "Invalid Stream Name", JOptionPane.ERROR_MESSAGE);
mName.requestFocus();
return false;
}
//Server address is optional, but required
if(!validateTextField(mServer, "Invalid Server Address", "Please specify a server address."))
{
return true;
}
//Port is optional, but required
if(!validateIntegerTextField(mPort, "Invalid Port Number", "Please specify a port number (1 <> 65535)", 1, 65535))
{
return true;
}
//Mount Point is required
if(!validateTextField(mMountPoint, "Invalid Mount Point", "Please specify a mount point."))
{
return false;
}
//User name is required
if(!validateTextField(mUsername, "Invalid User Name", "Please specify a user name."))
{
return false;
}
//Password is required
if(!validateTextField(mPassword, "Invalid Password", "Please specify a password."))
{
return false;
}
return true;
}
/**
* Parses an Integer from the port text control or returns 0 if the control doesn't contain an integer value.
*/
private int getPort()
{
String port = mPort.getText();
if(port != null && !port.isEmpty())
{
try
{
return Integer.parseInt(port);
}
catch(Exception e)
{
//Do nothing, we couldn't parse the number value
}
}
return 0;
}
}

View File

@@ -1,367 +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.icecast;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastConfigurationEditor;
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
import io.github.dsheirer.audio.broadcast.BroadcastModel;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.icon.IconManager;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class IcecastTCPConfigurationEditor extends BroadcastConfigurationEditor
{
private final static Logger mLog = LoggerFactory.getLogger( IcecastTCPConfigurationEditor.class );
private static final int ONE_MINUTE_MS = 60000;
private JTextField mName;
private JTextField mServer;
private JTextField mPort;
private JTextField mMountPoint;
private JTextField mPassword;
private JTextField mDescription;
private JTextField mGenre;
private JCheckBox mPublic;
private JSlider mDelay;
private JLabel mDelayValue;
private JSlider mMaximumRecordingAge;
private JLabel mMaximumRecordingAgeValue;
private JCheckBox mEnabled;
private JButton mSaveButton;
private JButton mResetButton;
public IcecastTCPConfigurationEditor(BroadcastModel broadcastModel, AliasModel aliasModel, IconManager iconManager)
{
super(broadcastModel, aliasModel, iconManager);
init();
}
private void init()
{
setLayout( new MigLayout( "fill,wrap 2", "[align right][grow,fill]",
"[][][][][][][][][][][][][][][][grow,fill]" ) );
setPreferredSize(new Dimension(150,400));
JLabel channelLabel = new JLabel("Icecast (v2.3) Stream");
ImageIcon icon = mIconManager.getScaledIcon(BroadcastServerType.ICECAST_TCP.getIconPath(), 25);
channelLabel.setIcon(icon);
add(channelLabel, "span, align center");
add(new JLabel("Name:"));
mName = new JTextField();
mName.getDocument().addDocumentListener(this);
mName.setToolTipText("Unique name for this stream");
add(mName);
add(new JLabel("Server:"));
mServer = new JTextField();
mServer.getDocument().addDocumentListener(this);
mServer.setToolTipText("Server address (e.g. audio1.icecast.com or localhost)");
add(mServer);
add(new JLabel("Port:"));
mPort = new JTextField();
mPort.getDocument().addDocumentListener(this);
mPort.setToolTipText("Server port number (e.g. 80)");
add(mPort);
add(new JLabel("Mount:"));
mMountPoint = new JTextField();
mMountPoint.getDocument().addDocumentListener(this);
mMountPoint.setToolTipText("Mount point (e.g. /12345)");
add(mMountPoint);
add(new JLabel("Password:"));
mPassword = new JTextField();
mPassword.getDocument().addDocumentListener(this);
mPassword.setToolTipText("Password for the stream");
add(mPassword);
add(new JLabel("Description:"));
mDescription = new JTextField();
mDescription.getDocument().addDocumentListener(this);
mDescription.setToolTipText("Description of the stream contents");
add(mDescription);
add(new JLabel("Genre:"));
mGenre = new JTextField();
mGenre.getDocument().addDocumentListener(this);
mGenre.setToolTipText("Genre (e.g. public safety)");
add(mGenre);
add(new JLabel("Public:"));
mPublic = new JCheckBox();
mPublic.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
mPublic.setToolTipText("Designate stream as public (checked) or private");
add(mPublic);
add(new JLabel());
mDelayValue = new JLabel("0 Minutes");
add(mDelayValue);
add(new JLabel("Delay:"));
mDelay = new JSlider(0,60,0);
mDelay.setMajorTickSpacing(10);
mDelay.setMinorTickSpacing(5);
mDelay.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent e)
{
mDelayValue.setText(mDelay.getValue() + " Minutes");
setModified(true);
}
});
mDelay.setToolTipText("Audio broadcast delay in minutes");
add(mDelay);
add(new JLabel());
mMaximumRecordingAgeValue = new JLabel("1 Minute");
add(mMaximumRecordingAgeValue);
add(new JLabel("Age Limit:"));
mMaximumRecordingAge = new JSlider(1,60,5);
mMaximumRecordingAge.setMajorTickSpacing(10);
mMaximumRecordingAge.setMinorTickSpacing(5);
mMaximumRecordingAge.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent e)
{
int value = mMaximumRecordingAge.getValue();
mMaximumRecordingAgeValue.setText(value + " Minute" + (value > 1 ? "s" : ""));
setModified(true);
}
});
mMaximumRecordingAge.setToolTipText("Maximum recording age (in addition to delay) to stream");
add(mMaximumRecordingAge);
add(new JLabel("Enabled:")); //Empty
mEnabled = new JCheckBox();
mEnabled.setToolTipText("Enable (checked) or disable this stream");
mEnabled.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(mEnabled);
mSaveButton = new JButton("Save");
mSaveButton.setEnabled(false);
mSaveButton.setToolTipText("Click to save configuration information");
mSaveButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
save();
}
});
add(mSaveButton);
mResetButton = new JButton("Reset");
mResetButton.setEnabled(false);
mResetButton.setToolTipText("Click to reset any changes you've made since the last save");
mResetButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(false);
setItem(getItem());
}
});
add(mResetButton);
}
@Override
public void setModified(boolean modified)
{
super.setModified(modified);
mSaveButton.setEnabled(modified);
mResetButton.setEnabled(modified);
}
@Override
public void setItem(BroadcastConfiguration item)
{
super.setItem(item);
if(hasItem())
{
IcecastTCPConfiguration config = (IcecastTCPConfiguration)getItem();
mName.setText(config.getName());
mServer.setText(config.getHost());
mPort.setText(config.getPort() != 0 ? String.valueOf(config.getPort()) : null);
mMountPoint.setText(config.getMountPoint());
mPassword.setText(config.getPassword());
mDescription.setText(config.getDescription());
mGenre.setText(config.getGenre());
mPublic.setSelected(config.isPublic());
mDelay.setValue((int)(config.getDelay() / ONE_MINUTE_MS));
mMaximumRecordingAge.setValue((int)config.getMaximumRecordingAge() / ONE_MINUTE_MS);
mEnabled.setSelected(config.isEnabled());
}
else
{
mName.setText(null);
mServer.setText(null);
mPort.setText(null);
mMountPoint.setText(null);
mPassword.setText(null);
mDescription.setText(null);
mGenre.setText(null);
mPublic.setSelected(true);
mDelay.setValue(0);
mMaximumRecordingAge.setValue(1);
mEnabled.setSelected(false);
}
setModified(false);
}
@Override
public void save()
{
IcecastTCPConfiguration config = (IcecastTCPConfiguration)getItem();
if(validateConfiguration())
{
updateConfigurationName(config, mName.getText());
config.setHost(mServer.getText());
config.setPort(getPort());
config.setMountPoint(mMountPoint.getText());
config.setPassword(mPassword.getText());
config.setDescription(mDescription.getText());
config.setGenre(mGenre.getText());
config.setPublic(mPublic.isSelected());
config.setDelay(mDelay.getValue() * ONE_MINUTE_MS);
config.setMaximumRecordingAge(mMaximumRecordingAge.getValue() * ONE_MINUTE_MS);
config.setEnabled(mEnabled.isSelected());
setModified(false);
mBroadcastModel.process(new BroadcastEvent(getItem(), BroadcastEvent.Event.CONFIGURATION_CHANGE));
}
}
/**
* Validates the user specified values in the controls prior to saving them to the configuration
*/
private boolean validateConfiguration()
{
String name = mName.getText();
//A unique stream name is required
if(!mBroadcastModel.isUniqueName(name, getItem()))
{
String message = (name == null || name.isEmpty()) ?
"Please specify a stream name." : "Stream name " + name +
"is already in use.\nPlease choose another name.";
JOptionPane.showMessageDialog(IcecastTCPConfigurationEditor.this,
message, "Invalid Stream Name", JOptionPane.ERROR_MESSAGE);
mName.requestFocus();
return false;
}
//Server address is optional, but required
if(!validateTextField(mServer, "Invalid Server Address", "Please specify a server address."))
{
return true;
}
//Port is optional, but required
if(!validateIntegerTextField(mPort, "Invalid Port Number", "Please specify a port number (1 <> 65535)", 1, 65535))
{
return true;
}
//Mount Point is optional, but required
if(!validateTextField(mMountPoint, "Invalid Mount Point", "Please specify a mount point."))
{
return true;
}
//Password is optional, but required
if(!validateTextField(mPassword, "Invalid Password", "Please specify a password."))
{
return true;
}
return true;
}
/**
* Parses an Integer from the port text control or returns 0 if the control doesn't contain an integer value.
*/
private int getPort()
{
String port = mPort.getText();
if(port != null && !port.isEmpty())
{
try
{
return Integer.parseInt(port);
}
catch(Exception e)
{
//Do nothing, we couldn't parse the number value
}
}
return 0;
}
}

View File

@@ -1,351 +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.shoutcast.v1;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastConfigurationEditor;
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
import io.github.dsheirer.audio.broadcast.BroadcastModel;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.icon.IconManager;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ShoutcastV1ConfigurationEditor extends BroadcastConfigurationEditor
{
private final static Logger mLog = LoggerFactory.getLogger(ShoutcastV1ConfigurationEditor.class);
private static final int ONE_MINUTE_MS = 60000;
private JTextField mName;
private JTextField mServer;
private JTextField mPort;
private JTextField mPassword;
private JTextField mGenre;
private JTextField mDescription;
private JCheckBox mPublic;
private JSlider mDelay;
private JLabel mDelayValue;
private JSlider mMaximumRecordingAge;
private JLabel mMaximumRecordingAgeValue;
private JCheckBox mEnabled;
private JButton mSaveButton;
private JButton mResetButton;
public ShoutcastV1ConfigurationEditor(BroadcastModel broadcastModel, AliasModel aliasModel, IconManager iconManager)
{
super(broadcastModel, aliasModel, iconManager);
init();
}
private void init()
{
setLayout(new MigLayout("fill,wrap 2", "[align right][grow,fill]",
"[][][][][][][][][][][][][][][][grow,fill]"));
setPreferredSize(new Dimension(150, 400));
JLabel channelLabel = new JLabel("Shoutcast (v1.x) Stream");
ImageIcon icon = mIconManager.getScaledIcon(BroadcastServerType.SHOUTCAST_V1.getIconPath(), 25);
channelLabel.setIcon(icon);
add(channelLabel, "span, align center");
add(new JLabel("Name:"));
mName = new JTextField();
mName.getDocument().addDocumentListener(this);
mName.setToolTipText("Unique name for this stream");
add(mName);
add(new JLabel("Server:"));
mServer = new JTextField();
mServer.getDocument().addDocumentListener(this);
mServer.setToolTipText("Server address (e.g. audio1.shoutcast.com or localhost)");
add(mServer);
add(new JLabel("Port:"));
mPort = new JTextField();
mPort.getDocument().addDocumentListener(this);
mPort.setToolTipText("Server port number (e.g. 80)");
add(mPort);
add(new JLabel("Password:"));
mPassword = new JTextField();
mPassword.getDocument().addDocumentListener(this);
mPassword.setToolTipText("Password for the stream");
add(mPassword);
add(new JLabel("Genre:"));
mGenre = new JTextField();
mGenre.getDocument().addDocumentListener(this);
mGenre.setToolTipText("Genre (e.g. public safety)");
add(mGenre);
add(new JLabel("Description:"));
mDescription = new JTextField();
mDescription.getDocument().addDocumentListener(this);
mDescription.setToolTipText("Description of the stream");
add(mDescription);
add(new JLabel("Public:"));
mPublic = new JCheckBox();
mPublic.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
mPublic.setToolTipText("Designate stream as public (checked) or private");
add(mPublic);
add(new JLabel());
mDelayValue = new JLabel("0 Minutes");
add(mDelayValue);
add(new JLabel("Delay:"));
mDelay = new JSlider(0, 60, 0);
mDelay.setMajorTickSpacing(10);
mDelay.setMinorTickSpacing(5);
mDelay.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent e)
{
mDelayValue.setText(mDelay.getValue() + " Minutes");
setModified(true);
}
});
mDelay.setToolTipText("Audio broadcast delay in minutes");
add(mDelay);
add(new JLabel());
mMaximumRecordingAgeValue = new JLabel("1 Minute");
add(mMaximumRecordingAgeValue);
add(new JLabel("Age Limit:"));
mMaximumRecordingAge = new JSlider(1,60,5);
mMaximumRecordingAge.setMajorTickSpacing(10);
mMaximumRecordingAge.setMinorTickSpacing(5);
mMaximumRecordingAge.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent e)
{
int value = mMaximumRecordingAge.getValue();
mMaximumRecordingAgeValue.setText(value + " Minute" + (value > 1 ? "s" : ""));
setModified(true);
}
});
mMaximumRecordingAge.setToolTipText("Maximum recording age (in addition to delay) to stream");
add(mMaximumRecordingAge);
add(new JLabel("Enabled:")); //Empty
mEnabled = new JCheckBox();
mEnabled.setToolTipText("Enable (checked) or disable this stream");
mEnabled.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(mEnabled);
mSaveButton = new JButton("Save");
mSaveButton.setEnabled(false);
mSaveButton.setToolTipText("Click to save configuration information");
mSaveButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
save();
}
});
add(mSaveButton);
mResetButton = new JButton("Reset");
mResetButton.setEnabled(false);
mResetButton.setToolTipText("Click to reset any changes you've made since the last save");
mResetButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(false);
setItem(getItem());
}
});
add(mResetButton);
}
@Override
public void setModified(boolean modified)
{
super.setModified(modified);
mSaveButton.setEnabled(modified);
mResetButton.setEnabled(modified);
}
@Override
public void setItem(BroadcastConfiguration item)
{
super.setItem(item);
if(hasItem())
{
ShoutcastV1Configuration config = (ShoutcastV1Configuration) getItem();
mName.setText(config.getName());
mServer.setText(config.getHost());
mPort.setText(config.getPort() != 0 ? String.valueOf(config.getPort()) : null);
mPassword.setText(config.getPassword());
mGenre.setText(config.getGenre());
mDescription.setText(config.getDescription());
mPublic.setSelected(config.isPublic());
mDelay.setValue((int) (config.getDelay() / ONE_MINUTE_MS));
mMaximumRecordingAge.setValue((int)config.getMaximumRecordingAge() / ONE_MINUTE_MS);
mEnabled.setSelected(config.isEnabled());
}
else
{
mName.setText(null);
mServer.setText(null);
mPort.setText(null);
mPassword.setText(null);
mGenre.setText(null);
mDescription.setText(null);
mPublic.setSelected(true);
mDelay.setValue(0);
mMaximumRecordingAge.setValue(1);
mEnabled.setSelected(false);
}
setModified(false);
}
@Override
public void save()
{
ShoutcastV1Configuration config = (ShoutcastV1Configuration) getItem();
if(validateConfiguration())
{
updateConfigurationName(config, mName.getText());
config.setHost(mServer.getText());
config.setPort(getPort());
config.setPassword(mPassword.getText());
config.setDescription(mDescription.getText());
config.setGenre(mGenre.getText());
config.setPublic(mPublic.isSelected());
config.setDelay(mDelay.getValue() * ONE_MINUTE_MS);
config.setMaximumRecordingAge(mMaximumRecordingAge.getValue() * ONE_MINUTE_MS);
config.setEnabled(mEnabled.isSelected());
setModified(false);
mBroadcastModel.process(new BroadcastEvent(getItem(), BroadcastEvent.Event.CONFIGURATION_CHANGE));
}
}
/**
* Validates the user specified values in the controls prior to saving them to the configuration
*/
private boolean validateConfiguration()
{
String name = mName.getText();
//A unique stream name is required
if(!mBroadcastModel.isUniqueName(name, getItem()))
{
String message = (name == null || name.isEmpty()) ?
"Please specify a stream name." : "Stream name " + name +
"is already in use.\nPlease choose another name.";
JOptionPane.showMessageDialog(ShoutcastV1ConfigurationEditor.this,
message, "Invalid Stream Name", JOptionPane.ERROR_MESSAGE);
mName.requestFocus();
return false;
}
//Server address is optional, but required
if(!validateTextField(mServer, "Invalid Server Address", "Please specify a server address."))
{
return true;
}
//Port is optional, but required
if(!validateIntegerTextField(mPort, "Invalid Port Number", "Please specify a port number (1 <> 65535)", 1, 65535))
{
return true;
}
//Password is optional, but required
if(!validateTextField(mPassword, "Invalid Password", "Please specify a password."))
{
return true;
}
return true;
}
/**
* Parses an Integer from the port text control or returns 0 if the control doesn't contain an integer value.
*/
private int getPort()
{
String port = mPort.getText();
if(port != null && !port.isEmpty())
{
try
{
return Integer.parseInt(port);
}
catch(Exception e)
{
//Do nothing, we couldn't parse the number value
}
}
return 0;
}
}

View File

@@ -1,28 +1,30 @@
/*******************************************************************************
* 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.audio.broadcast.shoutcast.v2;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import org.apache.commons.lang3.Validate;
public class ShoutcastV2Configuration extends BroadcastConfiguration
{
@@ -90,7 +92,6 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
*/
public void setStreamID(int streamID)
{
Validate.isTrue(1 <= streamID && streamID <= Integer.MAX_VALUE);
mStreamID = streamID;
}

View File

@@ -1,395 +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.audio.broadcast.shoutcast.v2;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastConfigurationEditor;
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
import io.github.dsheirer.audio.broadcast.BroadcastModel;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.icon.IconManager;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ShoutcastV2ConfigurationEditor extends BroadcastConfigurationEditor
{
private final static Logger mLog = LoggerFactory.getLogger(ShoutcastV2ConfigurationEditor.class);
private static final int ONE_MINUTE_MS = 60000;
private JTextField mName;
private JTextField mServer;
private JTextField mPort;
private JTextField mStreamID;
private JTextField mUserID;
private JTextField mPassword;
private JTextField mGenre;
private JCheckBox mPublic;
private JSlider mDelay;
private JLabel mDelayValue;
private JSlider mMaximumRecordingAge;
private JLabel mMaximumRecordingAgeValue;
private JCheckBox mEnabled;
private JButton mSaveButton;
private JButton mResetButton;
public ShoutcastV2ConfigurationEditor(BroadcastModel broadcastModel, AliasModel aliasModel, IconManager iconManager)
{
super(broadcastModel, aliasModel, iconManager);
init();
}
private void init()
{
setLayout(new MigLayout("fill,wrap 2", "[align right][grow,fill]",
"[][][][][][][][][][][][][][][][grow,fill]"));
setPreferredSize(new Dimension(150, 400));
JLabel channelLabel = new JLabel("Shoutcast (v2) Stream");
ImageIcon icon = mIconManager.getScaledIcon(BroadcastServerType.SHOUTCAST_V2.getIconPath(), 25);
channelLabel.setIcon(icon);
add(channelLabel, "span, align center");
add(new JLabel("Name:"));
mName = new JTextField();
mName.getDocument().addDocumentListener(this);
mName.setToolTipText("Unique name for this stream");
add(mName);
add(new JLabel("Server:"));
mServer = new JTextField();
mServer.getDocument().addDocumentListener(this);
mServer.setToolTipText("Server address (e.g. audio1.shoutcast.com or localhost)");
add(mServer);
add(new JLabel("Port:"));
mPort = new JTextField();
mPort.getDocument().addDocumentListener(this);
mPort.setToolTipText("Server port number (e.g. 80)");
add(mPort);
add(new JLabel("Stream ID:"));
mStreamID = new JTextField();
mStreamID.getDocument().addDocumentListener(this);
mStreamID.setToolTipText("Stream Identifier)");
add(mStreamID);
add(new JLabel("User ID:"));
mUserID = new JTextField();
mUserID.getDocument().addDocumentListener(this);
mUserID.setToolTipText("User ID/Name");
add(mUserID);
add(new JLabel("Password:"));
mPassword = new JTextField();
mPassword.getDocument().addDocumentListener(this);
mPassword.setToolTipText("Password for the stream");
add(mPassword);
add(new JLabel("Genre:"));
mGenre = new JTextField();
mGenre.getDocument().addDocumentListener(this);
mGenre.setToolTipText("Genre (e.g. public safety)");
add(mGenre);
add(new JLabel("Public:"));
mPublic = new JCheckBox();
mPublic.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
mPublic.setToolTipText("Designate stream as public (checked) or private");
add(mPublic);
add(new JLabel());
mDelayValue = new JLabel("0 Minutes");
add(mDelayValue);
add(new JLabel("Delay:"));
mDelay = new JSlider(0, 60, 0);
mDelay.setMajorTickSpacing(10);
mDelay.setMinorTickSpacing(5);
mDelay.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent e)
{
mDelayValue.setText(mDelay.getValue() + " Minutes");
setModified(true);
}
});
mDelay.setToolTipText("Audio broadcast delay in minutes");
add(mDelay);
add(new JLabel());
mMaximumRecordingAgeValue = new JLabel("1 Minute");
add(mMaximumRecordingAgeValue);
add(new JLabel("Age Limit:"));
mMaximumRecordingAge = new JSlider(1,60,5);
mMaximumRecordingAge.setMajorTickSpacing(10);
mMaximumRecordingAge.setMinorTickSpacing(5);
mMaximumRecordingAge.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent e)
{
int value = mMaximumRecordingAge.getValue();
mMaximumRecordingAgeValue.setText(value + " Minute" + (value > 1 ? "s" : ""));
setModified(true);
}
});
mMaximumRecordingAge.setToolTipText("Maximum recording age (in addition to delay) to stream");
add(mMaximumRecordingAge);
add(new JLabel("Enabled:")); //Empty
mEnabled = new JCheckBox();
mEnabled.setToolTipText("Enable (checked) or disable this stream");
mEnabled.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(mEnabled);
mSaveButton = new JButton("Save");
mSaveButton.setEnabled(false);
mSaveButton.setToolTipText("Click to save configuration information");
mSaveButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
save();
}
});
add(mSaveButton);
mResetButton = new JButton("Reset");
mResetButton.setEnabled(false);
mResetButton.setToolTipText("Click to reset any changes you've made since the last save");
mResetButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(false);
setItem(getItem());
}
});
add(mResetButton);
}
@Override
public void setModified(boolean modified)
{
super.setModified(modified);
mSaveButton.setEnabled(modified);
mResetButton.setEnabled(modified);
}
@Override
public void setItem(BroadcastConfiguration item)
{
super.setItem(item);
if(hasItem())
{
ShoutcastV2Configuration config = (ShoutcastV2Configuration) getItem();
mName.setText(config.getName());
mServer.setText(config.getHost());
mPort.setText(config.getPort() != 0 ? String.valueOf(config.getPort()) : null);
mStreamID.setText(String.valueOf(config.getStreamID()));
mUserID.setText(config.getUserID());
mPassword.setText(config.getPassword());
mGenre.setText(config.getGenre());
mPublic.setSelected(config.isPublic());
mDelay.setValue((int) (config.getDelay() / ONE_MINUTE_MS));
mMaximumRecordingAge.setValue((int)config.getMaximumRecordingAge() / ONE_MINUTE_MS);
mEnabled.setSelected(config.isEnabled());
}
else
{
mName.setText(null);
mServer.setText(null);
mPort.setText(null);
mStreamID.setText(null);
mUserID.setText(null);
mPassword.setText(null);
mGenre.setText(null);
mPublic.setSelected(true);
mDelay.setValue(0);
mMaximumRecordingAge.setValue(1);
mEnabled.setSelected(false);
}
setModified(false);
}
@Override
public void save()
{
ShoutcastV2Configuration config = (ShoutcastV2Configuration) getItem();
if(validateConfiguration())
{
updateConfigurationName(config, mName.getText());
config.setHost(mServer.getText());
config.setPort(getPort());
config.setUserID(mUserID.getText());
config.setPassword(mPassword.getText());
config.setStreamID(getStreamID());
config.setGenre(mGenre.getText());
config.setPublic(mPublic.isSelected());
config.setDelay(mDelay.getValue() * ONE_MINUTE_MS);
config.setMaximumRecordingAge(mMaximumRecordingAge.getValue() * ONE_MINUTE_MS);
config.setEnabled(mEnabled.isSelected());
setModified(false);
mBroadcastModel.process(new BroadcastEvent(getItem(), BroadcastEvent.Event.CONFIGURATION_CHANGE));
}
}
/**
* Validates the user specified values in the controls prior to saving them to the configuration
*/
private boolean validateConfiguration()
{
String name = mName.getText();
//A unique stream name is required
if(!mBroadcastModel.isUniqueName(name, getItem()))
{
String message = (name == null || name.isEmpty()) ?
"Please specify a stream name." : "Stream name " + name +
"is already in use.\nPlease choose another name.";
JOptionPane.showMessageDialog(ShoutcastV2ConfigurationEditor.this,
message, "Invalid Stream Name", JOptionPane.ERROR_MESSAGE);
mName.requestFocus();
return false;
}
//Server address is required
if(!validateTextField(mServer, "Invalid Server Address", "Please specify a server address."))
{
return true;
}
//Port is required
if(!validateIntegerTextField(mPort, "Invalid Port Number", "Please specify a port number (1 <> 65535)", 1, 65535))
{
return true;
}
//Stream ID is required
if(!validateIntegerTextField(mStreamID, "Invalid Stream ID", "Please specify a stream ID of 1 or higher", 1, Integer.MAX_VALUE))
{
return true;
}
//User ID is required
if(!validateTextField(mUserID, "Invalid User ID", "Please specify a user ID/name."))
{
return true;
}
//Password is required
if(!validateTextField(mPassword, "Invalid Password", "Please specify a password."))
{
return true;
}
return true;
}
/**
* Parses an Integer from the port text control or returns 0 if the control doesn't contain an integer value.
*/
private int getPort()
{
String port = mPort.getText();
if(port != null && !port.isEmpty())
{
try
{
return Integer.parseInt(port);
}
catch(Exception e)
{
//Do nothing, we couldn't parse the number value
}
}
return 0;
}
/**
* Parses an Integer from the port text control or returns 0 if the control doesn't contain an integer value.
*/
private int getStreamID()
{
String streamID = mStreamID.getText();
if(streamID != null && !streamID.isEmpty())
{
try
{
return Integer.parseInt(streamID);
}
catch(Exception e)
{
//Do nothing, we couldn't parse the number value
}
}
return 0;
}
}

View File

@@ -120,7 +120,7 @@ public class AudioChannelPanel extends JPanel implements Listener<AudioEvent>, S
@Subscribe
public void preferenceUpdated(PreferenceType preferenceType)
{
if(preferenceType == PreferenceType.IDENTIFIER)
if(preferenceType == PreferenceType.TALKGROUP_FORMAT)
{
updateLabels();
}

View File

@@ -25,14 +25,13 @@ import io.github.dsheirer.audio.AudioException;
import io.github.dsheirer.audio.IAudioController;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.preference.PreferenceEditorType;
import io.github.dsheirer.gui.preference.PreferenceEditorViewRequest;
import io.github.dsheirer.gui.preference.UserPreferenceEditorViewRequest;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.SourceManager;
import io.github.dsheirer.source.mixer.MixerChannelConfiguration;
import io.github.dsheirer.source.mixer.MixerManager;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.swing.IconFontSwing;
import net.miginfocom.swing.MigLayout;
@@ -58,7 +57,6 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.List;
public class AudioPanel extends JPanel implements Listener<AudioEvent>
{
@@ -185,7 +183,7 @@ public class AudioPanel extends JPanel implements Listener<AudioEvent>
@Override
public void actionPerformed(ActionEvent e)
{
MyEventBus.getEventBus().post(new PreferenceEditorViewRequest(PreferenceEditorType.AUDIO_PLAYBACK));
MyEventBus.getEventBus().post(new UserPreferenceEditorViewRequest(PreferenceEditorType.AUDIO_OUTPUT));
}
});
popup.add(outputMenu);

View File

@@ -70,7 +70,7 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel
@Subscribe
public void preferenceUpdated(PreferenceType preferenceType)
{
if(preferenceType == PreferenceType.IDENTIFIER)
if(preferenceType == PreferenceType.TALKGROUP_FORMAT)
{
for(int row = 0; row < mChannelMetadata.size(); row++)
{

View File

@@ -33,6 +33,7 @@ import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier;
import io.github.dsheirer.module.ProcessingChain;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference;
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
@@ -76,11 +77,10 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
/**
* Table view for currently decoding channel metadata
*/
public ChannelMetadataPanel(ChannelModel channelModel, ChannelProcessingManager channelProcessingManager,
IconManager iconManager, UserPreferences userPreferences)
public ChannelMetadataPanel(PlaylistManager playlistManager, IconManager iconManager, UserPreferences userPreferences)
{
mChannelModel = channelModel;
mChannelProcessingManager = channelProcessingManager;
mChannelModel = playlistManager.getChannelModel();
mChannelProcessingManager = playlistManager.getChannelProcessingManager();
mIconManager = iconManager;
mUserPreferences = userPreferences;
init();

View File

@@ -21,13 +21,11 @@ package io.github.dsheirer.channel.metadata;
import com.jidesoft.swing.JideSplitPane;
import com.jidesoft.swing.JideTabbedPane;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.channel.details.ChannelDetailPanel;
import io.github.dsheirer.controller.channel.ChannelModel;
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.module.decode.event.DecodeEventPanel;
import io.github.dsheirer.module.decode.event.MessageActivityPanel;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import net.miginfocom.swing.MigLayout;
@@ -45,13 +43,12 @@ public class NowPlayingPanel extends JPanel
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,
* messages, events, and spectral view.
*/
public NowPlayingPanel(ChannelModel channelModel, ChannelProcessingManager channelProcessingManager,
IconManager iconManager, AliasModel aliasModel, UserPreferences userPreferences)
public NowPlayingPanel(PlaylistManager playlistManager, IconManager iconManager, UserPreferences userPreferences)
{
mChannelDetailPanel = new ChannelDetailPanel(channelProcessingManager);
mDecodeEventPanel = new DecodeEventPanel(iconManager, userPreferences, aliasModel);
mChannelDetailPanel = new ChannelDetailPanel(playlistManager.getChannelProcessingManager());
mDecodeEventPanel = new DecodeEventPanel(iconManager, userPreferences, playlistManager.getAliasModel());
mMessageActivityPanel = new MessageActivityPanel(userPreferences);
mChannelMetadataPanel = new ChannelMetadataPanel(channelModel, channelProcessingManager, iconManager, userPreferences);
mChannelMetadataPanel = new ChannelMetadataPanel(playlistManager, iconManager, userPreferences);
init();
}

View File

@@ -19,71 +19,53 @@
package io.github.dsheirer.controller;
import com.jidesoft.swing.JideTabbedPane;
import io.github.dsheirer.alias.AliasController;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.BroadcastModel;
import io.github.dsheirer.audio.broadcast.BroadcastPanel;
import io.github.dsheirer.audio.playback.AudioPanel;
import io.github.dsheirer.audio.playback.AudioPlaybackManager;
import io.github.dsheirer.channel.metadata.NowPlayingPanel;
import io.github.dsheirer.controller.channel.ChannelController;
import io.github.dsheirer.controller.channel.ChannelModel;
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
import io.github.dsheirer.controller.channel.map.ChannelMapModel;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.playlist.ViewPlaylistRequest;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.map.MapPanel;
import io.github.dsheirer.map.MapService;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.SourceManager;
import io.github.dsheirer.source.tuner.TunerModel;
import io.github.dsheirer.source.tuner.TunerViewPanel;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.swing.IconFontSwing;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Dimension;
public class ControllerPanel extends JPanel
{
private final static Logger mLog = LoggerFactory.getLogger(ControllerPanel.class);
private static final long serialVersionUID = 1L;
private int mSettingsTabIndex = -1;
private AliasController mAliasController;
private AudioPanel mAudioPanel;
private BroadcastPanel mBroadcastPanel;
private ChannelController mChannelController;
private NowPlayingPanel mNowPlayingPanel;
private ChannelModel mChannelModel;
private MapPanel mMapPanel;
private TunerViewPanel mTunerManagerPanel;
private BroadcastModel mBroadcastModel;
private JideTabbedPane mTabbedPane;
public ControllerPanel(AudioPlaybackManager audioPlaybackManager, AliasModel aliasModel, BroadcastModel broadcastModel,
ChannelModel channelModel, ChannelMapModel channelMapModel, ChannelProcessingManager channelProcessingManager,
public ControllerPanel(PlaylistManager playlistManager, AudioPlaybackManager audioPlaybackManager,
IconManager iconManager, MapService mapService, SettingsManager settingsManager,
SourceManager sourceManager, TunerModel tunerModel, UserPreferences userPreferences)
SourceManager sourceManager, UserPreferences userPreferences)
{
mBroadcastModel = broadcastModel;
mChannelModel = channelModel;
mAudioPanel = new AudioPanel(iconManager, userPreferences, settingsManager, sourceManager, audioPlaybackManager,
aliasModel);
mNowPlayingPanel = new NowPlayingPanel(channelModel, channelProcessingManager, iconManager,
aliasModel, userPreferences);
mMapPanel = new MapPanel(mapService, aliasModel, iconManager, settingsManager);
mBroadcastPanel = new BroadcastPanel(broadcastModel, aliasModel, iconManager, userPreferences);
mChannelController = new ChannelController(channelModel, channelProcessingManager, channelMapModel,
sourceManager, aliasModel, userPreferences);
mAliasController = new AliasController(aliasModel, broadcastModel, iconManager, userPreferences);
mTunerManagerPanel = new TunerViewPanel(tunerModel, userPreferences);
playlistManager.getAliasModel());
mNowPlayingPanel = new NowPlayingPanel(playlistManager, iconManager, userPreferences);
mMapPanel = new MapPanel(mapService, playlistManager.getAliasModel(), iconManager, settingsManager);
mTunerManagerPanel = new TunerViewPanel(sourceManager.getTunerModel(), userPreferences);
init();
}
@@ -94,20 +76,34 @@ public class ControllerPanel extends JPanel
add(mAudioPanel, "wrap");
//Tabbed View - configuration, calls, messages, map
mTabbedPane = new JideTabbedPane();
mTabbedPane = new JideTabbedPane()
{
@Override
public void setSelectedIndex(int index)
{
if(index == mSettingsTabIndex)
{
MyEventBus.getEventBus().post(new ViewPlaylistRequest());
}
else
{
super.setSelectedIndex(index);
}
}
};
mTabbedPane.setFont(this.getFont());
mTabbedPane.setForeground(Color.BLACK);
mTabbedPane.addTab("Now Playing", mNowPlayingPanel);
mTabbedPane.addTab("Aliases", mAliasController);
mTabbedPane.addTab("Channels", mChannelController);
mTabbedPane.addTab("Map", mMapPanel);
mTabbedPane.addTab("Streaming", mBroadcastPanel);
mTabbedPane.addTab("Tuners", mTunerManagerPanel);
Icon gearIcon = IconFontSwing.buildIcon(FontAwesome.COG, 20, Color.DARK_GRAY);
mTabbedPane.addTab("Playlist Editor", gearIcon, new JLabel("Show Playlist Manager"));
mSettingsTabIndex = mTabbedPane.getTabCount() - 1;
//Set preferred size to influence the split between these panels
mTabbedPane.setPreferredSize(new Dimension(880, 500));
add(mTabbedPane,"wrap");
add(mTabbedPane, "wrap");
}
}

View File

@@ -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.controller.channel;
@@ -39,6 +41,14 @@ import io.github.dsheirer.source.config.SourceConfigRecording;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfiguration;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import javafx.beans.Observable;
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.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -64,14 +74,14 @@ public class Channel extends Configuration implements Listener<SourceEvent>
private EventLogConfiguration mEventLogConfiguration = new EventLogConfiguration();
private RecordConfiguration mRecordConfiguration = new RecordConfiguration();
private String mAliasListName;
private String mSystem = "System";
private String mSite = "Site";
private String mName = "Channel";
private StringProperty mAliasListName = new SimpleStringProperty();
private StringProperty mSystem = new SimpleStringProperty();
private StringProperty mSite = new SimpleStringProperty();
private StringProperty mName = new SimpleStringProperty();
private boolean mProcessing;
private boolean mAutoStart;
private Integer mAutoStartOrder;
private BooleanProperty mProcessing = new SimpleBooleanProperty();
private BooleanProperty mAutoStart = new SimpleBooleanProperty();
private IntegerProperty mAutoStartOrder = new SimpleIntegerProperty();
private boolean mSelected;
private TunerChannel mTunerChannel = null;
@@ -100,7 +110,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
public Channel(String channelName)
{
this();
mName = channelName;
mName.set(channelName);
}
/**
@@ -111,52 +121,119 @@ public class Channel extends Configuration implements Listener<SourceEvent>
mChannelID = UNIQUE_ID++;
}
/**
* Creates a (new) deep copy of this channel
*/
public Channel copyOf()
{
Channel channel = new Channel(mName);
channel.setSystem(mSystem);
channel.setSite(mSite);
channel.setAliasListName(mAliasListName);
channel.setAutoStart(mAutoStart);
channel.setAutoStartOrder(mAutoStartOrder);
Channel channel = new Channel(mName.get());
channel.setSystem(mSystem.get());
channel.setSite(mSite.get());
channel.setAliasListName(mAliasListName.get());
channel.setAutoStart(mAutoStart.get());
channel.setAutoStartOrder(mAutoStartOrder.get());
AuxDecodeConfiguration aux = new AuxDecodeConfiguration();
AuxDecodeConfiguration auxCopy = new AuxDecodeConfiguration();
for(DecoderType auxType : aux.getAuxDecoders())
if(getAuxDecodeConfiguration() != null)
{
aux.addAuxDecoder(auxType);
for(DecoderType auxType : getAuxDecodeConfiguration().getAuxDecoders())
{
auxCopy.addAuxDecoder(auxType);
}
}
channel.setAuxDecodeConfiguration(aux);
channel.setAuxDecodeConfiguration(auxCopy);
channel.setDecodeConfiguration(DecoderFactory.copy(mDecodeConfiguration));
EventLogConfiguration log = new EventLogConfiguration();
EventLogConfiguration logCopy = new EventLogConfiguration();
for(EventLogType logType : mEventLogConfiguration.getLoggers())
if(mEventLogConfiguration != null)
{
log.addLogger(logType);
for(EventLogType logType : mEventLogConfiguration.getLoggers())
{
logCopy.addLogger(logType);
}
}
channel.setEventLogConfiguration(log);
channel.setEventLogConfiguration(logCopy);
RecordConfiguration record = new RecordConfiguration();
RecordConfiguration recordCopy = new RecordConfiguration();
for(RecorderType recordType : mRecordConfiguration.getRecorders())
if(mRecordConfiguration != null)
{
record.addRecorder(recordType);
for(RecorderType recordType : mRecordConfiguration.getRecorders())
{
recordCopy.addRecorder(recordType);
}
}
channel.setRecordConfiguration(record);
channel.setRecordConfiguration(recordCopy);
channel.setSourceConfiguration(SourceConfigFactory.copy(mSourceConfiguration));
return channel;
}
/**
* Alias list name property
*/
public StringProperty aliasListNameProperty()
{
return mAliasListName;
}
/**
* System property
*/
public StringProperty systemProperty()
{
return mSystem;
}
/**
* Site property
*/
public StringProperty siteProperty()
{
return mSite;
}
/**
* Channel name property
*/
public StringProperty nameProperty()
{
return mName;
}
/**
* Processing property. Indicates if this channel configuration is currently processing.
*/
public BooleanProperty processingProperty()
{
return mProcessing;
}
/**
* Auto-Start property. Indicates if this channel is setup for auto-start
*/
public BooleanProperty autoStartProperty()
{
return mAutoStart;
}
/**
* Auto-start order property. Indicates the order for starting channels that are flagged for auto-start.
*/
public IntegerProperty autoStartOrderProperty()
{
return mAutoStartOrder;
}
/**
* Unique identifier for this channel. Value is transient (not persisted).
*/
@@ -214,17 +291,17 @@ public class Channel extends Configuration implements Listener<SourceEvent>
@JacksonXmlProperty(isAttribute = true, localName = "system")
public String getSystem()
{
return mSystem;
return mSystem.get();
}
public void setSystem(String system)
{
mSystem = system;
mSystem.set(system);
}
public boolean hasSystem()
{
return mSystem != null;
return mSystem != null && mSystem.get() != null;
}
/**
@@ -233,17 +310,17 @@ public class Channel extends Configuration implements Listener<SourceEvent>
@JacksonXmlProperty(isAttribute = true, localName = "site")
public String getSite()
{
return mSite;
return mSite.get();
}
public void setSite(String site)
{
mSite = site;
mSite.set(site);
}
public boolean hasSite()
{
return mSite != null;
return mSite != null && mSite.get() != null;
}
/**
@@ -252,7 +329,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
@JacksonXmlProperty(isAttribute = true, localName = "name")
public String getName()
{
return mName;
return mName.get();
}
/**
@@ -260,7 +337,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
*/
public void setName(String name)
{
mName = name;
mName.set(name);
}
/**
@@ -284,7 +361,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
@JsonIgnore
public boolean isProcessing()
{
return mProcessing;
return mProcessing.get();
}
/**
@@ -296,7 +373,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
*/
void setProcessing(boolean processing)
{
mProcessing = processing;
mProcessing.set(processing);
}
/**
@@ -310,7 +387,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
@JacksonXmlProperty(isAttribute = true, localName = "enabled")
public boolean getAutoStart()
{
return mAutoStart;
return mAutoStart.get();
}
/**
@@ -319,7 +396,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
@JsonIgnore
public boolean isAutoStart()
{
return mAutoStart;
return mAutoStart.get();
}
/**
@@ -329,7 +406,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
*/
public void setAutoStart(boolean autoStart)
{
mAutoStart = autoStart;
mAutoStart.set(autoStart);
}
/**
@@ -341,7 +418,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
@JacksonXmlProperty(isAttribute = true, localName = "order")
public Integer getAutoStartOrder()
{
return mAutoStartOrder;
return mAutoStartOrder.get();
}
/**
@@ -351,7 +428,14 @@ public class Channel extends Configuration implements Listener<SourceEvent>
*/
public void setAutoStartOrder(Integer order)
{
mAutoStartOrder = order;
if(order != null)
{
mAutoStartOrder.set(order);
}
else
{
mAutoStartOrder.setValue(null);
}
}
/**
@@ -362,7 +446,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
@JsonIgnore
public boolean hasAutoStartOrder()
{
return mAutoStartOrder != null;
return mAutoStartOrder != null && mAutoStartOrder.getValue() != null;
}
/**
@@ -372,7 +456,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
@JacksonXmlProperty(isAttribute = false, localName = "alias_list_name")
public String getAliasListName()
{
return mAliasListName;
return mAliasListName.get();
}
/**
@@ -381,15 +465,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
*/
public void setAliasListName(String name)
{
mAliasListName = name;
}
/**
* Indicates if this channel has a non-null, non-empty alias list name specified.
*/
public boolean hasAliasList()
{
return mAliasListName != null && !mAliasListName.isEmpty();
mAliasListName.set(name);
}
/**
@@ -558,8 +634,6 @@ public class Channel extends Configuration implements Listener<SourceEvent>
/**
* Current channel frequency correction value (when this channel is processing)
*
* @return
*/
@JsonIgnore
public int getChannelFrequencyCorrection()
@@ -595,4 +669,13 @@ public class Channel extends Configuration implements Listener<SourceEvent>
{
return Objects.hash(getChannelID());
}
/**
* Creates an observable property extractor for use with observable lists to detect changes internal to this object.
*/
public static Callback<Channel,Observable[]> extractor()
{
return (Channel c) -> new Observable[] {c.processingProperty(), c.nameProperty(), c.aliasListNameProperty(),
c.autoStartOrderProperty(), c.autoStartProperty(), c.siteProperty(), c.systemProperty()};
}
}

View File

@@ -1,306 +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.controller.channel;
import com.jidesoft.swing.JideSplitPane;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.controller.channel.map.ChannelMapModel;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
import io.github.dsheirer.source.SourceManager;
import net.coderazzi.filters.gui.AutoChoices;
import net.coderazzi.filters.gui.TableFilterHeader;
import net.miginfocom.swing.MigLayout;
import javax.swing.JButton;
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;
public class ChannelController extends JPanel implements ActionListener, ListSelectionListener
{
private static final long serialVersionUID = 1L;
private static final String TABLE_PREFERENCE_KEY = "channel.controller";
private ChannelModel mChannelModel;
private ChannelProcessingManager mChannelProcessingManager;
private JTable mChannelTable;
private JTableColumnWidthMonitor mColumnWidthMonitor;
private TableFilterHeader mTableFilterHeader;
private ChannelEditor mEditor;
private UserPreferences mUserPreferences;
private static final String NEW_CHANNEL = "New";
private static final String COPY_CHANNEL = "Copy";
private static final String DELETE_CHANNEL = "Delete";
private JButton mNewChannelButton = new JButton(NEW_CHANNEL);
private JButton mCopyChannelButton = new JButton(COPY_CHANNEL);
private JButton mDeleteChannelButton = new JButton(DELETE_CHANNEL);
public ChannelController(ChannelModel channelModel, ChannelProcessingManager channelProcessingManager,
ChannelMapModel channelMapModel, SourceManager sourceManager, AliasModel aliasModel,
UserPreferences userPreferences)
{
mChannelModel = channelModel;
mChannelProcessingManager = channelProcessingManager;
mEditor = new ChannelEditor(channelModel, channelProcessingManager, channelMapModel, sourceManager, aliasModel);
mChannelModel.addListener(mEditor);
mChannelProcessingManager.addChannelEventListener(mEditor);
mUserPreferences = userPreferences;
init();
}
private void init()
{
setLayout(new MigLayout("", "[grow,fill]", "[grow,fill]"));
//System Configuration View and Editor
mChannelTable = new JTable(mChannelModel);
mChannelTable.setDefaultRenderer(String.class, new ChannelTableCellRenderer());
mChannelTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mChannelTable.getSelectionModel().addListSelectionListener(this);
mChannelTable.setAutoCreateRowSorter(true);
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mChannelTable, TABLE_PREFERENCE_KEY);
mTableFilterHeader = new TableFilterHeader(mChannelTable, AutoChoices.ENABLED);
mTableFilterHeader.setFilterOnUpdates(true);
JScrollPane channelScroller = new JScrollPane(mChannelTable);
JPanel buttonsPanel = new JPanel();
buttonsPanel.setLayout(
new MigLayout("insets 0 0 0 0", "[grow,fill][grow,fill][grow,fill]", "[]"));
mNewChannelButton.addActionListener(this);
mNewChannelButton.setToolTipText("Create a new channel configuration");
buttonsPanel.add(mNewChannelButton);
mCopyChannelButton.addActionListener(this);
mCopyChannelButton.setEnabled(false);
mCopyChannelButton.setToolTipText("Create a copy of the currently selected channel configuration");
buttonsPanel.add(mCopyChannelButton);
mDeleteChannelButton.addActionListener(this);
mDeleteChannelButton.setEnabled(false);
mDeleteChannelButton.setToolTipText("Delete the currently selected channel configuration");
buttonsPanel.add(mDeleteChannelButton);
JPanel listAndButtonsPanel = new JPanel();
listAndButtonsPanel.setLayout(
new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill][]"));
listAndButtonsPanel.add(channelScroller, "wrap");
listAndButtonsPanel.add(buttonsPanel);
JideSplitPane splitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT);
splitPane.setDividerSize(5);
splitPane.setShowGripper(true);
//Shrink this guy so that he'll eventually be expanded to the scroller preferred size
listAndButtonsPanel.setPreferredSize(new Dimension(10, 10));
//Attempt to get a 60:40 vertical sizing preference
JScrollPane listScroller = new JScrollPane(listAndButtonsPanel);
listScroller.setPreferredSize(new Dimension(800, 58));
JScrollPane editorScroller = new JScrollPane(mEditor);
editorScroller.setPreferredSize(new Dimension(800, 42));
splitPane.add(listScroller);
splitPane.add(editorScroller);
add(splitPane);
}
@Override
public void valueChanged(ListSelectionEvent event)
{
//This limits event firing to only when selection is complete
if(!event.getValueIsAdjusting())
{
int selectedRow = mChannelTable.getSelectedRow();
if(selectedRow != -1)
{
int index = mChannelTable.convertRowIndexToModel(mChannelTable.getSelectedRow());
Channel channel = mChannelModel.getChannelAtIndex(index);
if(channel != null)
{
mEditor.setItem(channel);
mCopyChannelButton.setEnabled(true);
mDeleteChannelButton.setEnabled(true);
}
}
else
{
mEditor.setItem(null);
mCopyChannelButton.setEnabled(false);
mDeleteChannelButton.setEnabled(false);
}
}
}
/**
* Adds the channel to the channel model/table and scrolls to view it
*/
private void addChannel(Channel channel)
{
//HACK: when inserting a row to the model, the JTable gets
//notified and attempts to tell the coderazzi table filter
//adaptive choices filter to refresh before the table filter is
//notified of the row additions, causing an index out of bounds
//exception. We turn off adaptive choices temporarily, add the
//channel, and turn on adaptive choices again.
mTableFilterHeader.setAdaptiveChoices(false);
int index = mChannelModel.addChannel(channel);
mTableFilterHeader.setAdaptiveChoices(true);
if(index >= 0)
{
int translatedIndex = mChannelTable.convertRowIndexToView(index);
if(translatedIndex >= 0)
{
mChannelTable.setRowSelectionInterval(translatedIndex, translatedIndex);
mChannelTable.scrollRectToVisible(
new Rectangle(mChannelTable.getCellRect(translatedIndex, 0, true)));
}
}
}
private Channel getSelectedChannel()
{
int index = mChannelTable.getSelectedRow();
if(index >= 0)
{
return mChannelModel.getChannelAtIndex(
mChannelTable.convertRowIndexToModel(index));
}
return null;
}
/**
* Responds to New, Copy and Delete Channel button invocations
*/
@Override
public void actionPerformed(ActionEvent event)
{
switch(event.getActionCommand())
{
case NEW_CHANNEL:
addChannel(new Channel("New Channel"));
break;
case COPY_CHANNEL:
Channel selected = getSelectedChannel();
if(selected != null)
{
addChannel(selected.copyOf());
}
break;
case DELETE_CHANNEL:
Channel toDelete = getSelectedChannel();
if(toDelete != null)
{
int choice = JOptionPane.showConfirmDialog(ChannelController.this,
"Do you want to delete this channel?", "Delete Channel?",
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
if(choice == JOptionPane.YES_OPTION)
{
mChannelModel.removeChannel(toDelete);
}
}
break;
default:
break;
}
}
public class ChannelTableCellRenderer extends DefaultTableCellRenderer
{
private static final long serialVersionUID = 1L;
@Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column)
{
Component component = super.getTableCellRendererComponent(
table, value, isSelected, hasFocus, row, column);
ChannelModel model = (ChannelModel)table.getModel();
int index = table.convertRowIndexToModel(row);
Channel channel = model.getChannelAtIndex(index);
boolean enabled = channel.isProcessing();
if(isSelected)
{
if(enabled)
{
component.setBackground(Color.YELLOW);
}
else
{
component.setBackground(table.getSelectionBackground());
}
}
else
{
if(enabled)
{
component.setBackground(Color.GREEN);
}
else
{
component.setBackground(table.getBackground());
}
}
return component;
}
}
}

View File

@@ -1,262 +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.controller.channel;
import com.jidesoft.swing.JideTabbedPane;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.controller.channel.map.ChannelMapModel;
import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.gui.editor.EditorValidationException;
import io.github.dsheirer.module.decode.AuxDecodeConfigurationEditor;
import io.github.dsheirer.module.decode.DecodeConfigurationEditor;
import io.github.dsheirer.module.log.EventLogConfigurationEditor;
import io.github.dsheirer.record.RecordConfigurationEditor;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.SourceConfigurationEditor;
import io.github.dsheirer.source.SourceManager;
import net.miginfocom.swing.MigLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ChannelEditor extends Editor<Channel> implements ActionListener, Listener<ChannelEvent>
{
private static final long serialVersionUID = 1L;
private static final String ACTION_START = "Start";
private static final String ACTION_STOP = "Stop";
private static final String ACTION_SAVE = "Save";
private static final String ACTION_RESET = "Reset";
private NameConfigurationEditor mNameConfigurationEditor;
private SourceConfigurationEditor mSourceConfigurationEditor;
private DecodeConfigurationEditor mDecodeConfigurationEditor;
private AuxDecodeConfigurationEditor mAuxDecodeConfigurationEditor;
private EventLogConfigurationEditor mEventLogConfigurationEditor;
private RecordConfigurationEditor mRecordConfigurationEditor;
private JButton mProcessingStateButton = new JButton(ACTION_START);
private JLabel mChannelName = new JLabel("Channel:");
private ChannelModel mChannelModel;
private ChannelProcessingManager mChannelProcessingManager;
private ChannelMapModel mChannelMapModel;
private SourceManager mSourceManager;
private boolean mChannelStartRequested = false;
public ChannelEditor(ChannelModel channelModel,
ChannelProcessingManager channelProcessingManager,
ChannelMapModel channelMapModel,
SourceManager sourceManager,
AliasModel aliasModel)
{
mChannelModel = channelModel;
mChannelProcessingManager = channelProcessingManager;
mChannelMapModel = channelMapModel;
mSourceManager = sourceManager;
mNameConfigurationEditor = new NameConfigurationEditor(aliasModel, mChannelModel);
init();
}
private void init()
{
setLayout(new MigLayout("fill,wrap 3", "[grow,fill][grow,fill][grow,fill]", "[grow,fill][]"));
JideTabbedPane tabs = new JideTabbedPane();
tabs.setFont(this.getFont());
tabs.setForeground(Color.BLACK);
tabs.setTabTrailingComponent(mChannelName);
mNameConfigurationEditor.setSaveRequestListener(this);
tabs.addTab("Name/Alias", mNameConfigurationEditor);
mSourceConfigurationEditor = new SourceConfigurationEditor(mSourceManager);
mSourceConfigurationEditor.setSaveRequestListener(this);
tabs.addTab("Source", mSourceConfigurationEditor);
mDecodeConfigurationEditor = new DecodeConfigurationEditor(mChannelMapModel);
mDecodeConfigurationEditor.setSaveRequestListener(this);
tabs.addTab("Decoder", mDecodeConfigurationEditor);
mAuxDecodeConfigurationEditor = new AuxDecodeConfigurationEditor();
mAuxDecodeConfigurationEditor.setSaveRequestListener(this);
tabs.addTab("Aux Decoders", mAuxDecodeConfigurationEditor);
mEventLogConfigurationEditor = new EventLogConfigurationEditor();
mEventLogConfigurationEditor.setSaveRequestListener(this);
tabs.addTab("Logging", mEventLogConfigurationEditor);
mRecordConfigurationEditor = new RecordConfigurationEditor();
mRecordConfigurationEditor.setSaveRequestListener(this);
tabs.addTab("Recording", mRecordConfigurationEditor);
add(tabs, "span");
mProcessingStateButton.addActionListener(this);
mProcessingStateButton.setEnabled(false);
mProcessingStateButton.setToolTipText("Start/Stop the currently selected channel");
add(mProcessingStateButton);
JButton btnSave = new JButton(ACTION_SAVE);
btnSave.setToolTipText("Save changes to the currently selected channel");
btnSave.addActionListener(ChannelEditor.this);
add(btnSave);
JButton btnReset = new JButton(ACTION_RESET);
btnReset.setToolTipText("Reload the currently selected channel");
btnReset.addActionListener(ChannelEditor.this);
add(btnReset);
}
@Override
public void receive(ChannelEvent event)
{
if(hasItem() && getItem() == event.getChannel())
{
switch(event.getEvent())
{
case NOTIFICATION_CONFIGURATION_CHANGE:
case NOTIFICATION_PROCESSING_START:
case NOTIFICATION_PROCESSING_STOP:
setItem(getItem());
mChannelStartRequested = false;
break;
case NOTIFICATION_DELETE:
setItem(null);
break;
case NOTIFICATION_PROCESSING_START_REJECTED:
if(mChannelStartRequested)
{
JOptionPane.showMessageDialog(ChannelEditor.this, "Channel could not be "
+ "started. This is likely because there are no tuner channels "
+ "available", "Couldn't start channel", JOptionPane.INFORMATION_MESSAGE);
mChannelStartRequested = false;
}
break;
default:
break;
}
}
}
@Override
public void actionPerformed(ActionEvent e)
{
String command = e.getActionCommand();
if(command.contentEquals(ACTION_START))
{
if(hasItem())
{
save();
mChannelStartRequested = true;
mChannelProcessingManager.receive(new ChannelEvent(getItem(), ChannelEvent.Event.REQUEST_ENABLE));
}
}
else if(command.contentEquals(ACTION_STOP))
{
if(hasItem())
{
mChannelProcessingManager.receive(new ChannelEvent(getItem(), ChannelEvent.Event.REQUEST_DISABLE));
}
}
else if(command.contentEquals(ACTION_SAVE))
{
save();
}
else if(command.contentEquals(ACTION_RESET))
{
setItem(getItem());
}
}
/**
* Sets the channel configuration in each of the channel editor components
* Note: this method must be invoked on the swing event dispatch thread
*/
public void setItem(final Channel channel)
{
super.setItem(channel);
mNameConfigurationEditor.setItem(channel);
mSourceConfigurationEditor.setItem(channel);
mDecodeConfigurationEditor.setItem(channel);
mAuxDecodeConfigurationEditor.setItem(channel);
mEventLogConfigurationEditor.setItem(channel);
mRecordConfigurationEditor.setItem(channel);
if(channel != null)
{
mChannelName.setText("Channel: " + channel.getName());
mProcessingStateButton.setText(channel.isProcessing() ? ACTION_STOP : ACTION_START);
mProcessingStateButton.setEnabled(true);
mProcessingStateButton.setBackground(channel.isProcessing() ? Color.GREEN : getBackground());
}
else
{
mChannelName.setText("Channel: ");
mProcessingStateButton.setText(ACTION_START);
mProcessingStateButton.setEnabled(false);
mProcessingStateButton.setBackground(getBackground());
}
}
public void save()
{
if(hasItem())
{
mNameConfigurationEditor.save();
mSourceConfigurationEditor.save();
mDecodeConfigurationEditor.save();
mAuxDecodeConfigurationEditor.save();
mEventLogConfigurationEditor.save();
mRecordConfigurationEditor.save();
try
{
mDecodeConfigurationEditor.validate(mSourceConfigurationEditor);
mDecodeConfigurationEditor.validate(mAuxDecodeConfigurationEditor);
}
catch(EditorValidationException e)
{
e.getEditor().requestFocus();
JOptionPane.showMessageDialog(e.getEditor(), e.getReason(),
"Configuration Error", JOptionPane.ERROR_MESSAGE);
return;
}
ChannelEvent update = new ChannelEvent(getItem(), ChannelEvent.Event.NOTIFICATION_CONFIGURATION_CHANGE);
mChannelModel.receive(update);
}
setModified(false);
}
}

View File

@@ -80,6 +80,22 @@ public class ChannelEvent
(mDescription != null ? " Description:" + mDescription : "");
}
/**
* Creates a request enable channel event
*/
public static ChannelEvent requestEnable(Channel channel)
{
return new ChannelEvent(channel, Event.REQUEST_ENABLE);
}
/**
* Creates a new request disable channel event
*/
public static ChannelEvent requestDisable(Channel channel)
{
return new ChannelEvent(channel, Event.REQUEST_DISABLE);
}
/**
* Channel events to describe the specific event
*/

View File

@@ -0,0 +1,36 @@
/*******************************************************************************
* 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.controller.channel;
/**
* Exception for channel start/stop errors
*/
public class ChannelException extends Exception
{
private static final long serialVersionUID = 1L;
public ChannelException(String message )
{
this( message, null );
}
public ChannelException(String message, Throwable cause )
{
super( message, cause );
}
}

View File

@@ -1,62 +1,94 @@
/*******************************************************************************
* 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.
* * ******************************************************************************
* * 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.controller.channel;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.controller.channel.Channel.ChannelType;
import io.github.dsheirer.controller.channel.ChannelEvent.Event;
import io.github.dsheirer.module.decode.DecoderType;
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 javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Channel Model
*/
public class ChannelModel extends AbstractTableModel implements Listener<ChannelEvent>
public class ChannelModel implements Listener<ChannelEvent>
{
private static final long serialVersionUID = 1L;
public static final int COLUMN_ENABLED = 0;
public static final int COLUMN_SYSTEM = 1;
public static final int COLUMN_SITE = 2;
public static final int COLUMN_NAME = 3;
public static final int COLUMN_ALIAS_LIST = 4;
public static final int COLUMN_SOURCE = 5;
public static final int COLUMN_DECODER = 6;
public static final int COLUMN_AUTO_START = 7;
private static final String[] COLUMN_NAMES = new String[] {"Playing", "System", "Site", "Name", "Alias List",
"Source", "Decoder", "Auto-Start"};
private static final String VALUE_YES = "Yes";
private List<Channel> mChannels = new CopyOnWriteArrayList<>();
private List<Channel> mTrafficChannels = new CopyOnWriteArrayList<>();
private ObservableList<Channel> mChannels = FXCollections.observableArrayList(Channel.extractor());
private ObservableList<Channel> mTrafficChannels = FXCollections.observableArrayList(Channel.extractor());
private Broadcaster<ChannelEvent> mChannelEventBroadcaster = new Broadcaster();
private AliasModel mAliasModel;
public ChannelModel()
public ChannelModel(AliasModel aliasModel)
{
mAliasModel = aliasModel;
//Register a listener to detect channel changes and broadcast change events to cause playlist save requests
ChannelListChangeListener changeListener = new ChannelListChangeListener();
mChannels.addListener(changeListener);
mTrafficChannels.addListener(changeListener);
}
/**
* Observable list of channel configurations managed by this model
*/
public ObservableList<Channel> channelList()
{
return mChannels;
}
/**
* Observable list of traffic channel configurations managed by this model
*/
public ObservableList<Channel> trafficChannelList()
{
return mTrafficChannels;
}
/**
* Removes all channels and traffic channels and fires remove event for each.
*/
public void clear()
{
List<Channel> trafficChannels = new ArrayList<>(mTrafficChannels);
for(Channel trafficChannel: trafficChannels)
{
removeChannel(trafficChannel);
}
List<Channel> channels = new ArrayList<>(mChannels);
for(Channel channel: channels)
{
removeChannel(channel);
}
}
/**
@@ -80,7 +112,7 @@ public class ChannelModel extends AbstractTableModel implements Listener<Channel
/**
* Returns a list of unique system values from across the channel set
*/
public List<String> getSystems()
public List<String> getSystemNames()
{
List<String> systems = new ArrayList<>();
@@ -97,33 +129,6 @@ public class ChannelModel extends AbstractTableModel implements Listener<Channel
return systems;
}
/**
* Returns a list of unique site values for all channels that have a matching
* system value
*/
public List<String> getSites(String system)
{
List<String> sites = new ArrayList<>();
if(system != null)
{
for(Channel channel : mChannels)
{
if(channel.hasSystem() &&
system.equals(channel.getSystem()) &&
channel.hasSite() &&
!sites.contains(channel.getSite()))
{
sites.add(channel.getSite());
}
}
}
Collections.sort(sites);
return sites;
}
/**
* Broadcasts the channel event to all registered listeners
*/
@@ -136,22 +141,9 @@ public class ChannelModel extends AbstractTableModel implements Listener<Channel
{
case NOTIFICATION_CONFIGURATION_CHANGE:
case NOTIFICATION_SELECTION_CHANGE:
index = mChannels.indexOf(event.getChannel());
if(index > 0)
{
fireTableRowsUpdated(index, index);
}
//rebroadcast this event to any listeners
mChannelEventBroadcaster.broadcast(event);
break;
case NOTIFICATION_PROCESSING_START:
case NOTIFICATION_PROCESSING_STOP:
index = mChannels.indexOf(event.getChannel());
if(index > 0)
{
fireTableRowsUpdated(index, index);
}
break;
default:
break;
}
@@ -178,30 +170,21 @@ public class ChannelModel extends AbstractTableModel implements Listener<Channel
/**
* Adds the channel to the model and broadcasts a channel add event
*/
public int addChannel(Channel channel)
public void addChannel(Channel channel)
{
int index = -1;
switch(channel.getChannelType())
{
case STANDARD:
mChannels.add(channel);
index = mChannels.size() - 1;
fireTableRowsInserted(index, index);
//Make sure the alias model has the alias list referred to by the channel
mAliasModel.addAliasList(channel.getAliasListName());
break;
case TRAFFIC:
mTrafficChannels.add(channel);
index = mChannels.size() - 1;
break;
default:
break;
}
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_ADD));
return index;
}
/**
@@ -214,14 +197,7 @@ public class ChannelModel extends AbstractTableModel implements Listener<Channel
switch(channel.getChannelType())
{
case STANDARD:
int index = mChannels.indexOf(channel);
mChannels.remove(channel);
if(index >= 0)
{
fireTableRowsDeleted(index, index);
}
break;
case TRAFFIC:
mTrafficChannels.remove(channel);
@@ -229,8 +205,6 @@ public class ChannelModel extends AbstractTableModel implements Listener<Channel
default:
break;
}
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_DELETE));
}
}
@@ -312,89 +286,59 @@ public class ChannelModel extends AbstractTableModel implements Listener<Channel
mChannelEventBroadcaster.removeListener(listener);
}
//Table Model Interface Methods - standard channels only
@Override
public int getRowCount()
/**
* List of site names from all channels in the model
*/
public List<String> getSiteNames()
{
return mChannels.size();
}
List<String> sites = new ArrayList<>();
@Override
public int getColumnCount()
{
return COLUMN_NAMES.length;
}
@Override
public String getColumnName(int columnIndex)
{
if(columnIndex < COLUMN_NAMES.length)
for(Channel channel: mChannels)
{
return COLUMN_NAMES[columnIndex];
String site = channel.getSite();
if(site != null && !site.isEmpty() && !sites.contains(site))
{
sites.add(site);
}
}
return null;
return sites;
}
@Override
public Class<?> getColumnClass(int columnIndex)
/**
* Observable list change listener for both channels and traffic channels lists
*/
public class ChannelListChangeListener implements ListChangeListener<Channel>
{
return String.class;
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex)
{
return false;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex)
{
Channel channel = mChannels.get(rowIndex);
switch(columnIndex)
@Override
public void onChanged(Change<? extends Channel> change)
{
case COLUMN_ENABLED:
return channel.isProcessing() ? VALUE_YES : null;
case COLUMN_SYSTEM:
return channel.getSystem();
case COLUMN_SITE:
return channel.getSite();
case COLUMN_NAME:
return channel.getName();
case COLUMN_ALIAS_LIST:
return channel.getAliasListName();
case COLUMN_SOURCE:
return channel.getSourceConfiguration().getDescription();
case COLUMN_DECODER:
return channel.getDecodeConfiguration().getDecoderType().getShortDisplayString();
case COLUMN_AUTO_START:
if(channel.isAutoStart())
while(change.next())
{
if(change.wasAdded())
{
if(channel.hasAutoStartOrder())
for(Channel channel: change.getAddedSubList())
{
return channel.getAutoStartOrder();
}
else
{
return VALUE_YES;
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_ADD));
}
}
break;
else if(change.wasRemoved())
{
for(Channel channel: change.getRemoved())
{
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_DELETE));
}
}
else if(change.wasUpdated())
{
for(int x = change.getFrom(); x < change.getTo(); x++)
{
mChannelEventBroadcaster.broadcast(new ChannelEvent(change.getList().get(x),
Event.NOTIFICATION_CONFIGURATION_CHANGE));
}
}
}
}
return null;
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex)
{
throw new IllegalArgumentException("Not yet implemented");
}
public void createChannel(DecoderType decoderType, long frequency)
{
throw new UnsupportedOperationException("Not yet implemented");
}
}

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