Compare commits

...

89 Commits

Author SHA1 Message Date
Denny Sheirer
eaa3433034 #895 Cleanup and Testing of FCD Pro and Pro Plus tuners. (#896)
Co-authored-by: Denny <denny@denny-desktop>
2020-07-25 16:27:04 -04:00
Denny Sheirer
838016c89f #893 Updates UDP packet to construct a Cellocator packet payload when the source UDP port is 231 and any time the packet contains the correct start packet identifier. Updates the decode event type label. (#894)
Co-authored-by: Denny <denny@denny-desktop>
2020-07-25 09:01:44 -04:00
Denny
030a88f5a3 Updates build to 0.5.0 alpha 3 2020-07-25 08:28:27 -04:00
Denny Sheirer
de3ba5529a #890 Resolves issue where radio reference channel import only imports 1 of multiple listed control channels and adds improved detection for P25P2 simulcast sites to set the decoder type to LSM. (#892)
Co-authored-by: Denny <denny@denny-desktop>
2020-07-25 08:26:19 -04:00
Denny Sheirer
7962758e05 #866 Cellocator Packet Support (#875)
Resolves #866 Implements cellocator packet support.
2020-07-25 07:55:12 -04:00
Denny Sheirer
0f9d1c0792 #886 Resolves issue with audio recording when duplicate call detection is enabled. (#891)
Co-authored-by: Denny <denny@denny-desktop>
2020-07-25 07:24:12 -04:00
Noah Lynch
48be38a2d8 Wrap setAliases() in Platform.runLater() (#885)
Fixes issue #884 (see issue for more info).

AliasItemEditor.save() modifies objects that are observed by the
TableView's underlying SortedList.  Thus, the TableView's selection can
change by calling save(), which means the ListChangeListener can be
fired.

This is a problem for two reasons.
1. The alert dialogue that prompts the user to save a modified alias
will show when the user presses the "save" button, because saving
modifies the selected alias and causes the list to update.

2. setAliases() calls Alert.showAndWait().  From the JavaDoc:
"It must not be called during animation or layout processing."
This statement is likely there because it can cause issues like the one
here: TableView's layout processing (and the list updating) is paused
and the list is further modified (by calling save()).  Thus, when the
processing is resumed the list is in a different and unexpected state.

Calling setAliases() from Platform.runLater() will allow the SortedList,
TableView, and any other involved objects to finish processing changes
before more modifications are made.  Additionally, it allows save(),
when called by pressing the save button, to complete and set the
modifiedProperty to false, so setAliases() does not display an
additional save prompt.
2020-07-25 07:08:39 -04:00
Noah Lynch
207893e5a8 Move BinaryMessage isFull() check into try block to prevent NPE. (#881)
From issue 879:
When a BitSetFullException is caught and reset(0) is called, the
BinaryMessage is set to null.

This should resolve issue 879.
2020-07-25 06:59:13 -04:00
CurtisHx
e07d6fa854 Log build information on startup (#878) 2020-07-25 06:51:24 -04:00
Denny Sheirer
e26698d0d8 #839 Updates logging for tuner unavailable to allocate traffic channel frequencies. Logs the frequency once and then suppresses further logging. (#870)
Co-authored-by: Denny <denny@denny-desktop>
2020-06-16 05:17:05 -04:00
Denny
a8837c8e2a Updates version to 0.5.0-alpha2 2020-06-13 09:35:11 -04:00
Denny Sheirer
7395f02fb8 #864 Updates radio reference service library to resolve issue where the library wasn't parsing the system information object correctly and was assuming a singular bandplan object versus an array of bandplans. (#865)
Updating the service library prompted some minor changes with the way that county info objects are handled for each site.

Co-authored-by: Denny <denny@denny-desktop>
2020-06-13 08:16:37 -04:00
Denny Sheirer
706ada2940 #862 Updates JavaFX libraries to use version 13.0.2 (#863)
Co-authored-by: Denny <denny@denny-desktop>
2020-06-12 05:39:01 -04:00
Denny Sheirer
d957846ab0 #837 Sorted list ArrayIndexOutOfBoundsException during alias save. (#858)
Co-authored-by: Denny <denny@denny-desktop>
2020-06-11 04:59:15 -04:00
Denny Sheirer
bbe90ca74b #856 Adds support for resetting alias color selection in the alias bulk editor. (#857)
Co-authored-by: Denny <denny@denny-desktop>
2020-06-11 04:26:40 -04:00
Denny Sheirer
96611fa2c2 #854 Resolves issue with radio reference system view where site list is getting squeezed by height requirements in the site editor components. (#855)
Co-authored-by: Denny <denny@denny-desktop>
2020-06-10 16:11:17 -04:00
Denny Sheirer
c94edf05ef #851 Resolves issue with residual alias list names after playlist change. (#852)
Co-authored-by: Denny <denny@denny-desktop>
2020-06-10 05:48:45 -04:00
Denny Sheirer
74adb6f30c #849 Resolves issue with frequency table column not updating after saving new channel configuration. (#850)
Co-authored-by: Denny <denny@denny-desktop>
2020-06-10 05:34:21 -04:00
Denny Sheirer
421abe5768 #724 Resolves issue with usb transfer buffer(s) memory leak. (#847)
Co-authored-by: Denny <denny@denny-desktop>
2020-06-09 04:33:33 -04:00
Denny Sheirer
1d003c561e #845 File Not Found when setting initial directory on JMBE library select button. (#846)
Co-authored-by: Denny <denny@denny-desktop>
2020-06-08 04:47:19 -04:00
Denny Sheirer
b22177ee17 #829 Integrates JMBE Creator with new user preferences editor for auto-downloading and compiling the JMBE library from within sdrtrunk. (#844)
Co-authored-by: Denny <denny@denny-desktop>
2020-06-07 08:04:57 -04:00
Denny Sheirer
a1e8c761ef #438 Adds feature for duplicate call audio detection and suppression for playback, recording and streaming. (#835)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-27 06:08:53 -04:00
Denny Sheirer
ad194d1bab #818 Resolves P25 duplicate traffic channel issue where duplicate traffic channels were being allocated for the same frequency. (#834)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-25 06:29:56 -04:00
Denny Sheirer
2bb5082943 #831 Resolves issue with playlist editor and channel map editor. (#832)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-24 08:11:39 -04:00
Denny Sheirer
2c3648f8c2 #817 Resolves issue with jittery audio playback and recording for MPT1327 channels. (#830)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-24 07:58:59 -04:00
Andrzej Pańkowski
3d816c3570 MPT1327: Fix decoding of calls to past talkgroups (#824)
* MPT1327: Fix decoding of calls to past talkgroups

* Fixed updating event in case of teardown. Other improvements.
2020-05-21 05:22:12 -04:00
Andrzej Pańkowski
e1194d26bd MPT1327: Fix for call timeout setting not being honored (#823)
* MPT1327: Fix for call timeout setting not being honored
2020-05-21 05:14:50 -04:00
Denny Sheirer
8167e9e9f2 #812 Alias Bulk Editor feature (#821)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-18 05:11:48 -04:00
Denny Sheirer
3b357c4d40 Update README.md 2020-05-15 04:39:14 -04:00
Denny Sheirer
89cb54676d Update README.md 2020-05-09 12:06:38 -04:00
Denny Sheirer
73c251ff4d #804 Tracks main application gui maximized state on shutdown and restores to maximized on restart. (#811)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-09 11:36:01 -04:00
Denny Sheirer
0d51b39604 #808 Improves broadcastify calls socket reset exception handling. (#810)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-09 11:20:33 -04:00
Denny
af0dbf1566 Removes Debug Logging. 2020-05-09 10:08:34 -04:00
Denny Sheirer
01146a5624 #797 and #801 Code Review and Cleanup. (#807)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-09 09:47:39 -04:00
Denny Sheirer
316cdb4068 #800 Resolves issue with rendering of icons in playlist alias editor and slow loading of icons in dropdown combobox. Adds error checking to the icon manager to notify user when there is an issue with loading a custom icon in the ADD method. (#803)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-07 05:51:21 -04:00
Denny Sheirer
91cae70fa1 #799 resolves npe in alias hasBroadcastChannels() method (#802)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-07 04:30:07 -04:00
Denny Sheirer
2723926f83 #778 Updates Icon Manager to JavaFX. Resolves issue with displaying custom icons in alias editor. (#796)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-06 04:08:05 -04:00
Denny Sheirer
2aff9ee817 #788 Adds monitoring for maximized state of FX windows. (#794)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-04 14:57:26 -04:00
Denny Sheirer
c87e6553dc #789 Resolves screen capture menu item takes up whole menubar. (#793)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-04 14:48:24 -04:00
Denny Sheirer
5d580544e7 #766 Resolves issue with playlist alias editor clone alias feature causing multiple copies of alias. Changed clone feature to only use a shallow copy (no ids or actions). Removed alias event framework from code since all change notifications can now be detected by the alias model's aliasList property. (#791)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-04 06:20:49 -04:00
Denny Sheirer
08ea0e0f18 #786 Updates Broadcastify Calls api for new test endpoint and editor changes. Updates Icecast and Shoutcast configurations to correctly show configuration validation status. (#787)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-03 07:23:21 -04:00
Denny Sheirer
c8b441dbd4 #782 Resolves NPE error in AudioOutput (#785)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-03 05:30:29 -04:00
Denny Sheirer
1aa22789e5 #783 Resolves issue with P25 Phase 2 channel grants (#784)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-03 05:25:23 -04:00
Denny Sheirer
cff5494db4 #775 Playlist Editor Cleanup (#779)
* #775 Adds frequency column to channel editor

* #775 Resolves issue with changing channel processing flag on FX event thread

* #775 Removes auto-detect channel settings for P25 Phase 2 configurations.

* #775 Playlist editor cleanup actions.  Changes style of segmented buttons.  Fix issue with channel set processing not on FX thread.  Removes P25P2 auto-detect settings feature.  Adds frequency column to channel editor.

Co-authored-by: Denny <denny@denny-desktop>
2020-05-02 09:53:44 -04:00
Denny Sheirer
9bd6507342 #770 Resolves issue with correct display of P25 uplink channel frequency values. (#774)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-02 07:27:45 -04:00
Denny Sheirer
379461e649 #757 Implements Broadcastify Calls API audio broadcaster and resolves audio streaming error introduced with #5 (#773)
Co-authored-by: Denny <denny@denny-desktop>
2020-05-02 06:29:45 -04:00
Denny Sheirer
cc52ae5bd7 #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>
2020-04-26 07:53:43 -04:00
robekl
39f6d6c831 Performance improvements (#746)
* Misc minor performance improvements

* Use more concise loop syntax

* Use more concise Collection sort syntax

* Use StringUtils pad methods

* Optimize some loops and double-map-lookups

* Optimize array instantiation

There are two styles to convert a collection to an array: either
using a pre-sized array (like c.toArray(new String[c.size()])) or
using an empty array (like c.toArray(new String[0]).

In older Java versions using pre-sized array was recommended,
as the reflection call which is necessary to create an array
of proper size was quite slow. However since late updates of
OpenJDK 6 this call was intrinsified, making the performance
of the empty array version the same and sometimes even better,
compared to the pre-sized version. Also passing pre-sized array
is dangerous for a concurrent or synchronized collection as
a data race is possible between the size and toArray call
which may result in extra nulls at the end of the array,
if the collection was concurrently shrunk during the operation.

* Cleanup string concatenation

* Use EnumMap where possible

* Use FastMath instead of Math

* Initialize complex things only once

* Reduce method size to aloow more JIT inlining

* remove map of short->float

* Move field to local variable

* Cleanup Map lookup

Co-authored-by: Rob Ekl <xwq876@zebra.com>
2020-04-25 07:57:56 -04:00
robekl
649258bd2b Fix some minor bugs (#745)
* Include other member variables in copy constructor

* Add break to switch cases

* Implement equals() in classes extending Comparable

If equals() is not overridden, the equals() implementation is not consistent
with the compareTo() implementation. If an object of such a class is added
to a collection such as java.util.SortedSet, this collection will violate
the contract of java.util.Set, which is defined in terms of equals().

Co-authored-by: Rob Ekl <xwq876@zebra.com>
2020-04-25 05:52:14 -04:00
Denny Sheirer
de9431f649 Resolves #751 adds quoted, comma-separated aliases to TRACK_TITLE metadata tag. (#759)
Co-authored-by: Denny <denny@denny-desktop>
2020-04-25 05:46:31 -04:00
Denny Sheirer
18f9e38090 Updates HackRF Sample Rates (#744)
* Hackrf Changes

* Updates HackRF tuner controller sample rates to deprecate previous sample rates to ensure playlist settings don't get corrupted.

Co-authored-by: Doug <dswelch51964@yahoo.com>
Co-authored-by: Denny <denny@denny-desktop>
2020-04-19 10:57:05 -04:00
Denny Sheirer
2e37f1e687 Updates gradle wrapper to 6.3. Updates embeded java to JDK 14 for building platform releases. (#729)
Co-authored-by: Denny <denny@denny-desktop>
2020-04-04 09:22:13 -04:00
Denny Sheirer
5e8f2d0523 Updates build to 0.4.0 final and updates CHANGELOG revision history. (#727)
Co-authored-by: Denny <denny@denny-desktop>
2020-04-04 08:19:29 -04:00
crsawyer
9fb6c33319 Fix HackRF-JawBreaker and HackRF sample conversion (#725) 2020-04-04 08:02:17 -04:00
Denny Sheirer
fdecac4230 Resovles #721 Suppress RID values of 0. (#723)
Co-authored-by: Denny <denny@denny-desktop>
2020-04-01 05:05:28 -04:00
Denny Sheirer
85c27a6cca Resolves #719 P25 Phase 2 display channel number (#722)
Co-authored-by: Denny <denny@denny-desktop>
2020-04-01 03:28:26 -04:00
Denny Sheirer
57f56d31a2 #713 Update Version 0.4.0 Beta 8 (#714)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 12:06:23 -04:00
Denny Sheirer
df25675d3f Resolves #707 separate DTMF tones of the same value get merged together (#712)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 12:03:47 -04:00
Denny Sheirer
277a84702a Resolves #580 channel map selection and editing (#711)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 11:52:39 -04:00
Denny Sheirer
c651296378 Resolves #709 MPT1327 no audio on traffic channels. (#710)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 11:33:15 -04:00
Denny Sheirer
866d25e86a #705 Updates release version 0.4.0 beta 7 (#706)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 06:17:06 -04:00
Denny Sheirer
5e62f1b889 Resolves #677 Streaming Audio Metadata UNKNOWN/UNKNOWN (#704)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 05:58:21 -04:00
Denny Sheirer
e9d524ad14 Resolves #699 Channel Multi-Frequency rotation delay user preference does not persist. (#703)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 05:53:58 -04:00
Denny Sheirer
158c8b4b8a #701 P25 Phase 2 Tone Metadata and Channel State (#702)
* Resolves #701 Updates P25P2 Tone metadata handling and P25P2 channel state management.
2020-03-28 05:51:11 -04:00
Denny Sheirer
41df207bb9 Resolves #694. Address null pointer when no system audio devices are available. (#695)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-23 04:45:06 -04:00
Denny Sheirer
692ffabe82 Resolves #692. Updates Java requirement to version 13 and JavaFX libraries to version 13. (#693)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-22 09:30:43 -04:00
Denny
944ad5a571 Updates version to 0.4.0 beta 6 2020-03-22 06:39:53 -04:00
Denny
5ca70d3940 Removes logging from invalid channel type values. 2020-03-22 06:07:36 -04:00
Denny Sheirer
904752e3dd Resolves #688. Updates HackRF Sample Rate options and sets usable bandwidth for the tuner to 75% of the sample rate. (#689)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-22 05:31:56 -04:00
Denny Sheirer
cf61798466 Resolves #685. Resolves several issues with P25 Phase 2 channel state and call handling. (#687)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-21 14:42:04 -04:00
Denny Sheirer
fd72f459f9 Resolves #685. Resolves several issues with P25 Phase 2 channel state and call handling. (#686)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-21 14:31:26 -04:00
Denny Sheirer
1bc462b300 Resolves #676 Do Not Monitor feature not working. (#684)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-21 05:32:12 -04:00
Denny Sheirer
1c6f91f101 Resolves #682 NPE when logging channel events with no alias list specified. (#683)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-20 11:15:53 -04:00
Denny
a7c28f066a Resolved NPE with tone updates. 2020-03-20 09:14:10 -04:00
Denny Sheirer
604c8ab6e5 Resolves #680 Audio Do Not Monitor is not working. Adds audio segment drop tone to indicate that an audio playback is dropped because the do not monitor setting was applied after the audio segment started. (#681)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-20 08:31:35 -04:00
Denny Sheirer
23a3ee39cc Resolves #678 AudioChannelPanel threading issue. (#679)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-20 05:21:49 -04:00
Denny Sheirer
fa16db129b #660 Updates audio processing subsystem to use audio segments. Updates audio playback, recording and streaming to use audio segments. Adds support to UserPreferences for sound card selection and audio tone insertion during playback. Adds record preference for WAVE or MP3. Resolves issues with metadata in recordings. (#675) 2020-03-19 06:35:05 -04:00
Denny
de249c448b Removes debug logging. 2020-02-29 11:02:32 +00:00
Denny
e1bc56c500 Resolves issue with build script where OS-specific JavaFX libraries were not included in build images. 2020-02-23 08:35:49 -05:00
Denny Sheirer
d1d115d11f Updates build to version 0.4.0 beta 5 (#668) 2020-02-23 09:49:00 +00:00
Allegiance Zone pusher
aca41b4f6e alias in event log.diff (#659) 2020-02-22 20:38:37 +00:00
whelgeson
1e3ae996ac Implementing some build script cleanup work. (#664)
- Adding jitpack to import libs directly from the git repos
 - Removing binary imports from repo
 - Fixing Gradle 7 upgrade warnings
2020-02-22 20:31:29 +00:00
Denny Sheirer
b3b2e74688 Resolves #663. Updates P25 phase 2 Decoder to detect PLL lock errors for +/-90 and 180 out-of-phase. Adds support for broadcasting phase 2 channel frequency error calculations to support tuner auto-correction for frequency errors. Removes adaptive PLL bandwidth support from Phase 1 and uses a fixed PLL bandwidth for both phase 1 and phase 2 decoders. Updates the Phase 2 symbol timing gain with results from extensive testing of PLL bandwidth and symbol timing combinations. (#666) 2020-02-22 20:09:24 +00:00
Denny Sheirer
44237bec5c Resolves issue where user added icons were not displaying on windows OS. (#658) 2020-02-16 12:22:27 +00:00
Denny Sheirer
53b44f44e3 Updates gradle build to use version-specific root folder names for OS-specific image products. (#657) 2020-02-16 11:50:20 +00:00
Denny
9af7a184ad Utility for calculating PLL phase lock sync pattern derivatives 2020-02-14 06:11:15 -05:00
Denny Sheirer
9c8d87ec64 #651 Updates audio broadcaster to decrement pending replay count so that pending recordings don't cause a memory leak when the streaming server is not available. (#656) 2020-02-09 10:54:46 -05:00
Denny Sheirer
4c34968d28 #654 Warn users when USB bus speeds are exceeded. (#655)
Updates logging to track max usb speeds per bus and warns user when any USB bus combined tuner data rates exceeds 70% of the max bus rate.
2020-02-09 10:51:55 -05:00
Denny Sheirer
2d71f5173a #652 USB Tuner Error Handling Enhancements (#653)
-Separates USB timeout event processing to a dedicated thread
-Updates USB transfer buffer processor to better detect errors and auto-restart on error (max 3 times)
-Updates Tuner to add an error state and SourceManager to not select tuners with an error state
-Updates ChannelProcessingManager to auto-shutdown channels sourced from tuners set with error state
-Improves logging of USB related errors.
2020-02-09 05:40:04 -05:00
825 changed files with 51388 additions and 26623 deletions

11
.idea/compiler.xml generated
View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel>
<module name="sdr-trunk_main" target="11" />
<module name="sdr-trunk_test" target="11" />
<module name="sdrtrunk-sdr-trunk_main" target="11" />
<module name="sdrtrunk-sdr-trunk_test" target="11" />
</bytecodeTargetLevel>
</component>
</project>

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>

View File

@@ -1,12 +0,0 @@
<component name="libraryTable">
<library name="imports">
<CLASSES>
<root url="jar://$PROJECT_DIR$/imports/java-lame-3.98.4.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/commons-math3-3.6.1.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/libresample4j-0.1.3.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/jmbe-api-1.0.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

3
.idea/misc.xml generated
View File

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

2
.idea/modules.xml generated
View File

@@ -5,8 +5,6 @@
<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" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdrtrunk-sdr-trunk_main.iml" filepath="$PROJECT_DIR$/.idea/modules/sdrtrunk-sdr-trunk_main.iml" group="sdr-trunk" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdrtrunk-sdr-trunk_test.iml" filepath="$PROJECT_DIR$/.idea/modules/sdrtrunk-sdr-trunk_test.iml" group="sdr-trunk" />
</modules>
</component>
</project>

12
.idea/vcs.xml generated
View File

@@ -1,16 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="IssueNavigationConfiguration">
<option name="links">
<list>
<IssueNavigationLink>
<option name="issueRegexp" value="#(\d+)" />
<option name="linkRegexp" value="https://github.com/DSheirer/sdrtrunk/issues/$1" />
</IssueNavigationLink>
</list>
</option>
</component>
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -1,3 +1,18 @@
0.4.0 20200404
- Release includes embedded Java 14 & JavaFX 13 runtime
- Adds Polyphase Channelizer with 2x channel oversampling and redesigned source management
- Adds P25 Phase 2 decoder
- Adds P25 radio identifier aliasing
- JMBE library 1.0.4 with support for AMBE tone metadata
- New JavaFX User Preferences dialog
- HackRF tuner enhanced (sample rates and saturation issues)
- Adds support for Airspy mini tuner
- Redesigned audio sub-system that uses AudioSegments
- USB tuner bus saturation management/monitoring
- Bitstream (.bits) recording
- MP3 Audio Recording
- Numerous bug fixes
0.3.4 20180418
- New P25 C4FM decoder that uses Differential QPSK decoder instead of FM demodulation with 4FSK.
- Adds channel auto-start and ordering capability with startup dialog allowing user to invoke or bypass channel

View File

@@ -3,6 +3,7 @@ A cross-platform java application for decoding, monitoring, recording and stream
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted_V0.3.0)
* [User's Manual Version 0.3.0 and 0.4.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual_V0.3.0)
* [Playlist Editor User Manual Version 0.5.0](https://github.com/DSheirer/sdrtrunk/wiki/Playlist-Editor)
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
* [Support Group](https://groups.google.com/forum/#!forum/sdrtrunk)
* [Discord Support Channel](https://discord.gg/HJQaKYE)
@@ -36,7 +37,7 @@ Once unzipped, open a command prompt to where you unzipped the release. Change
If you're using sdrtrunk with a P25 trunked radio system, the [JMBE](https://github.com/DSheirer/sdrtrunk/wiki/JMBE) wiki page contains instructions for downloading the JMBE audio library source code and compiling the JMBE library. Once you have compiled the library, launch the sdrtrunk application. From the menu bar, choose **View >> Preferences**. In the **JMBE Audio Codec** section, update the path to where your compiled JMBE library is located. Any channels that are started after you set the path will be able to produce P25 audio.
## Minimum System Requirements
* **Operating System:** Windows (32 or 64-bit), Linux (32 or 64-bit) or Mac/Linux (64-bit, 10.14 or higher)
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac/Linux (64-bit, 10.14 or higher)
* **CPU:** 4-core
* **RAM:** 8GB or more (preferred). Depending on usage, 4GB may be sufficient.
@@ -62,5 +63,5 @@ The **/build/distributions** folder will contain the zip file of the compiled pr
All dependencies/versions are controlled from build.gradle.
To change the new release version tag of artifact - change property:
```
version = '0.4.0'
version = '0.5.0'
```

View File

@@ -23,17 +23,18 @@ plugins {
id 'application'
id 'java'
id 'idea'
id 'org.openjfx.javafxplugin' version '0.0.6'
id 'org.beryx.runtime' version '1.8.0'
id 'org.openjfx.javafxplugin' version '0.0.8'
id 'org.beryx.runtime' version '1.8.1'
}
repositories {
jcenter()
mavenCentral()
maven { url "https://jitpack.io" }
}
version = '0.4.0-beta.4'
sourceCompatibility = '11'
version = '0.5.0-alpha3'
sourceCompatibility = '13'
sourceSets {
main.java.srcDirs 'src/main'
@@ -41,45 +42,60 @@ sourceSets {
}
dependencies {
testCompile 'junit:junit:4.12'
testCompile 'org.assertj:assertj-core:3.8.0'
compile fileTree(dir: 'imports', include: '*.jar')
compile 'ch.qos.logback:logback-core:1.2.3'
compile 'ch.qos.logback:logback-classic:1.2.3'
compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.8'
compile 'com.fazecast:jSerialComm:2.5.0'
compile 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
compile 'com.github.jiconfont:jiconfont-javafx:1.0.0'
compile 'com.github.jiconfont:jiconfont-swing:1.0.1'
compile 'com.github.wendykierp:JTransforms:3.1'
compile 'com.google.guava:guava:27.0.1-jre'
compile 'com.jidesoft:jide-oss:3.6.18'
compile 'com.miglayout:miglayout-swing:5.2'
compile 'eu.hansolo:charts:1.0.5'
compile 'io.github.dsheirer:radio-reference-api:15.1.2'
compile 'javax.usb:usb-api:1.0.2'
compile 'net.coderazzi:tablefilter-swing:5.4.0'
compile 'org.apache.commons:commons-lang3:3.8.1'
compile 'org.apache.commons:commons-io:1.3.2'
compile 'org.apache.mina:mina-core:2.0.19'
compile 'org.apache.mina:mina-http:2.0.19'
compile "org.openjfx:javafx-base:11:win"
compile "org.openjfx:javafx-base:11:linux"
compile "org.openjfx:javafx-base:11:mac"
compile "org.openjfx:javafx-controls:11.0.2:win"
compile "org.openjfx:javafx-controls:11.0.2:linux"
compile "org.openjfx:javafx-controls:11.0.2:mac"
compile "org.openjfx:javafx-graphics:11.0.2:win"
compile "org.openjfx:javafx-graphics:11.0.2:linux"
compile "org.openjfx:javafx-graphics:11.0.2:mac"
compile "org.openjfx:javafx-swing:11.0.2:win"
compile "org.openjfx:javafx-swing:11.0.2:linux"
compile "org.openjfx:javafx-swing:11.0.2:mac"
compile 'org.slf4j:slf4j-api:1.7.25'
compile 'org.usb4java:libusb4java:1.3.0'
compile 'org.usb4java:usb4java:1.3.0'
compile 'org.usb4java:usb4java-javax:1.3.0'
compile 'pl.edu.icm:JLargeArrays:1.6'
testImplementation 'junit:junit:4.12'
testImplementation 'org.assertj:assertj-core:3.8.0'
//Jitpack imports
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
implementation 'com.github.dnault:libresample4j:master'
implementation 'com.github.DSheirer.jmbe:jmbe-api:1.0.0'
//mavenCentral/jcenter imports
implementation 'ch.qos.logback:logback-core:1.2.3'
implementation 'ch.qos.logback:logback-classic:1.2.3'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.8'
implementation 'com.fazecast:jSerialComm:2.5.0'
implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
implementation 'com.github.jiconfont:jiconfont-javafx:1.0.0'
implementation 'com.github.jiconfont:jiconfont-swing:1.0.1'
implementation 'com.github.wendykierp:JTransforms:3.1'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.google.guava:guava:27.0.1-jre'
implementation 'com.jidesoft:jide-oss:3.6.18'
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.6'
implementation 'javax.usb:usb-api:1.0.2'
implementation 'net.coderazzi:tablefilter-swing:5.4.0'
implementation 'org.apache.commons:commons-compress:1.20'
implementation 'commons-io:commons-io:2.7'
implementation 'org.apache.commons:commons-lang3:3.8.1'
implementation 'org.apache.commons:commons-math3:3.6.1'
implementation 'org.apache.mina:mina-core:2.1.3'
implementation 'org.apache.mina:mina-http:2.1.3'
implementation 'org.controlsfx:controlsfx:11.0.1'
implementation 'org.rauschig:jarchivelib:1.0.0'
implementation 'org.slf4j:slf4j-api:1.7.25'
implementation 'org.usb4java:libusb4java:1.3.0'
implementation 'org.usb4java:usb4java:1.3.0'
implementation 'org.usb4java:usb4java-javax:1.3.0'
implementation 'pl.edu.icm:JLargeArrays:1.6'
//These must remain as compile options to ensure they are included in the build to produce multi-OS images
compile "org.openjfx:javafx-base:13.0.2:win"
compile "org.openjfx:javafx-base:13.0.2:linux"
compile "org.openjfx:javafx-base:13.0.2:mac"
compile "org.openjfx:javafx-controls:13.0.2:win"
compile "org.openjfx:javafx-controls:13.0.2:linux"
compile "org.openjfx:javafx-controls:13.0.2:mac"
compile "org.openjfx:javafx-graphics:13.0.2:win"
compile "org.openjfx:javafx-graphics:13.0.2:linux"
compile "org.openjfx:javafx-graphics:13.0.2:mac"
compile "org.openjfx:javafx-swing:13.0.2:win"
compile "org.openjfx:javafx-swing:13.0.2:linux"
compile "org.openjfx:javafx-swing:13.0.2:mac"
}
application {
@@ -100,64 +116,71 @@ javafx {
jar {
manifest {
attributes (
'Implementation-Title' : 'sdrtrunk project',
'Implementation-Version' : archiveVersion,
'Class-Path' : 'jmbe-1.0.1.jar',
'Created-By' : "Gradle ${gradle.gradleVersion}",
'Build-Timestamp' : new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
'Build-OS' : "${System.properties['os.name']} (${System.properties['os.arch']} ${System.properties['os.version']}"
'Implementation-Title' : 'sdrtrunk project',
'Implementation-Version' : archiveVersion,
'Class-Path' : 'jmbe-1.0.4.jar',
'Created-By' : "Gradle ${gradle.gradleVersion}",
'Build-Timestamp' : new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
'Build-OS' : "${System.properties['os.name']} (${System.properties['os.arch']} ${System.properties['os.version']}"
)
}
}
/**
* 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
* 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.
*/
def jdk_linux_x86_64 = '/media/denny/WD250GB/java/bellsoft/linux-x64/jdk-13.0.1'
def jdk_linux_x86_32 = '/media/denny/WD250GB/java/bellsoft/linux-x86/jdk-13.0.1'
def jdk_osx_x86_64 = '/media/denny/WD250GB/java/bellsoft/osx-x64/jdk-13.0.1.jdk'
def jdk_windows_x86_64 = '/media/denny/WD250GB/java/bellsoft/windows-x64/jdk-13.0.1'
def jdk_windows_x86_32 = '/media/denny/WD250GB/java/bellsoft/windows-x86/jdk-13.0.1'
def jdk_linux_x86_64 = '/media/denny/WD250GB/java/bellsoft/linux-x64/jdk-14'
def jdk_linux_x86_32 = '/media/denny/WD250GB/java/bellsoft/linux-x86/jdk-14'
def jdk_osx_x86_64 = '/media/denny/WD250GB/java/bellsoft/osx-x64/jdk-14.jdk'
def jdk_windows_x86_64 = '/media/denny/WD250GB/java/bellsoft/windows-x64/jdk-14'
def jdk_windows_x86_32 = '/media/denny/WD250GB/java/bellsoft/windows-x86/jdk-14'
def directory_linux_x86_64 = new File(jdk_linux_x86_64)
def directory_linux_x86_32 = new File(jdk_linux_x86_32)
def directory_osx_x86_64 = new File(jdk_osx_x86_64)
def directory_windows_x86_64 = new File(jdk_windows_x86_64)
def directory_windows_x86_32 = new File(jdk_windows_x86_32)
def file_jdk_linux_x86_64 = new File(jdk_linux_x86_64)
def file_jdk_linux_x86_32 = new File(jdk_linux_x86_32)
def file_jdk_osx_x86_64 = new File(jdk_osx_x86_64)
def file_jdk_windows_x86_64 = new File(jdk_windows_x86_64)
def file_jdk_windows_x86_32 = new File(jdk_windows_x86_32)
def hasTargetJdk = file_jdk_linux_x86_32.exists() ||
file_jdk_linux_x86_64.exists() ||
file_jdk_osx_x86_64.exists() ||
file_jdk_windows_x86_32.exists() ||
file_jdk_windows_x86_64.exists()
runtime {
if(directory_linux_x86_64.exists())
if(file_jdk_linux_x86_64.exists())
{
targetPlatform('linux-x86_64', jdk_linux_x86_64)
targetPlatform('linux-x86_64-v' + version, jdk_linux_x86_64)
}
if(directory_linux_x86_32.exists())
if(file_jdk_linux_x86_32.exists())
{
targetPlatform('linux-x86_32', jdk_linux_x86_32)
targetPlatform('linux-x86_32-v' + version, jdk_linux_x86_32)
}
if(directory_osx_x86_64.exists())
if(file_jdk_osx_x86_64.exists())
{
targetPlatform('osx-x86_64', jdk_osx_x86_64)
targetPlatform('osx-x86_64-v' + version, jdk_osx_x86_64)
}
if(directory_windows_x86_64.exists())
if(file_jdk_windows_x86_64.exists())
{
targetPlatform('windows-x86_64', jdk_windows_x86_64)
targetPlatform('windows-x86_64-v' + version, jdk_windows_x86_64)
}
if(directory_windows_x86_32.exists())
if(file_jdk_windows_x86_32.exists())
{
targetPlatform('windows-x86_32', jdk_windows_x86_32)
targetPlatform('windows-x86_32-v' + version, jdk_windows_x86_32)
}
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
modules = ['java.desktop', 'java.naming', 'jdk.unsupported', 'jdk.unsupported.desktop', 'java.net.http']
imageZip = file("$buildDir/image/sdr-trunk-" + version + ".zip")
modules = ['java.desktop', 'java.naming', 'jdk.unsupported', 'jdk.unsupported.desktop', 'java.net.http',
'java.sql', 'jdk.crypto.ec']
imageZip = hasTargetJdk ? file("$buildDir/image/sdr-trunk.zip") : file("$buildDir/image/sdr-trunk-" + version + ".zip")
}
/**
@@ -165,40 +188,46 @@ runtime {
*/
tasks.runtime.doFirst {
if(!directory_linux_x86_64.exists())
if(!file_jdk_linux_x86_64.exists())
{
println("Skipping OS Image - Linux x86 64-bit JDK was not found at " + jdk_linux_x86_64)
}
if(!directory_linux_x86_32.exists())
if(!file_jdk_linux_x86_32.exists())
{
println("Skipping OS Image - Linux x86 32-bit JDK was not found at " + jdk_linux_x86_32)
}
if(!directory_osx_x86_64.exists())
if(!file_jdk_osx_x86_64.exists())
{
println("Skipping OS Image - OSX x86 64-bit JDK was not found at " + jdk_osx_x86_64);
}
if(!directory_windows_x86_64.exists())
if(!file_jdk_windows_x86_64.exists())
{
println("Skipping OS Image - Windows x86 64-bit JDK was not found at " + jdk_windows_x86_64)
}
if(!directory_windows_x86_32.exists())
if(!file_jdk_windows_x86_32.exists())
{
println("Skipping OS Image - Windows x86 32-bit JDK was not found at " + jdk_windows_x86_32)
}
}
/**
* Since we have to list each of the platform-specific JavaFX libraries as dependencies,
* remove them from each build image
* Remove unused JavaFX OS-specific libraries from each platform target directory
*/
tasks.runtime.doLast {
delete(fileTree('build/image/sdr-trunk-linux-x86_64/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
delete(fileTree('build/image/sdr-trunk-linux-x86_32/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
delete(fileTree('build/image/sdr-trunk-osx-x86_64/lib').include { it.name ==~ /javafx.*-(win|linux)\.jar/ })
delete(fileTree('build/image/sdr-trunk-windows-x86_64/lib').include { it.name ==~ /javafx.*-(linux|mac)\.jar/ })
delete(fileTree('build/image/sdr-trunk-windows-x86_32/lib').include { it.name ==~ /javafx.*-(linux|mac)\.jar/ })
delete(fileTree('build/image/sdr-trunk-linux-x86_64-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
delete(fileTree('build/image/sdr-trunk-linux-x86_32-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
delete(fileTree('build/image/sdr-trunk-osx-x86_64-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|linux)\.jar/ })
delete(fileTree('build/image/sdr-trunk-windows-x86_64-v' + version + '/lib').include { it.name ==~ /javafx.*-(linux|mac)\.jar/ })
delete(fileTree('build/image/sdr-trunk-windows-x86_32-v' + version + '/lib').include { it.name ==~ /javafx.*-(linux|mac)\.jar/ })
//JavaFX web and media modules are transitive dependencies of ControlsFX but are not required for this application
delete(fileTree('build/image/sdr-trunk-linux-x86_64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
delete(fileTree('build/image/sdr-trunk-linux-x86_32-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
delete(fileTree('build/image/sdr-trunk-osx-x86_64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
delete(fileTree('build/image/sdr-trunk-windows-x86_64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
delete(fileTree('build/image/sdr-trunk-windows-x86_32-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
}

16
copyright.template Normal file
View File

@@ -0,0 +1,16 @@
******************************************************************************
Copyright (C) 2014-${today.year} 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/>
*****************************************************************************

Binary file not shown.

View File

@@ -1,27 +1,5 @@
#
# /*
# * ******************************************************************************
# * 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/>
# * *****************************************************************************
# */
#
#
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

51
gradlew vendored
View File

@@ -1,5 +1,21 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m"'
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@@ -109,8 +125,8 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
@@ -138,19 +154,19 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
i=`expr $i + 1`
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@@ -159,14 +175,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

18
gradlew.bat vendored
View File

@@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m"
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -30,9 +30,12 @@
<component name="DependencyValidationManager">
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
</component>
<component name="GradleMigrationSettings" migrationVersion="1" />
<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" />
@@ -60,21 +63,7 @@
<version value="1.0" />
</component>
<component name="JavadocGenerationManager">
<option name="OUTPUT_DIRECTORY" />
<option name="OPTION_SCOPE" value="protected" />
<option name="OPTION_HIERARCHY" value="true" />
<option name="OPTION_NAVIGATOR" value="true" />
<option name="OPTION_INDEX" value="true" />
<option name="OPTION_SEPARATE_INDEX" value="true" />
<option name="OPTION_DOCUMENT_TAG_USE" value="false" />
<option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false" />
<option name="OPTION_DOCUMENT_TAG_VERSION" value="false" />
<option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true" />
<option name="OPTION_DEPRECATED_LIST" value="true" />
<option name="OTHER_OPTIONS" value="" />
<option name="HEAP_SIZE" />
<option name="LOCALE" />
<option name="OPEN_IN_BROWSER" value="true" />
</component>
<component name="Palette2">
<group name="Swing">
@@ -212,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>
@@ -322,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!/" />
@@ -366,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!/" />
@@ -430,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">
@@ -494,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">
@@ -549,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">
@@ -626,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!/" />
@@ -681,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!/" />
@@ -758,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!/" />
@@ -791,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,15 +616,100 @@ public class Alias
return false;
}
for(AliasID id : getId())
for(AliasID id : getAliasIdentifiers())
{
if(id.getType() == AliasIDType.BROADCAST_CHANNEL &&
((BroadcastChannel)id).getChannelName().contentEquals(channel))
if(id instanceof BroadcastChannel)
{
return true;
BroadcastChannel broadcastChannel = (BroadcastChannel)id;
if(broadcastChannel.getChannelName() != null && broadcastChannel.getChannelName().contentEquals(channel))
{
return true;
}
}
}
return false;
}
/**
* Removes any broadcast channel(s) that match the argument
*/
public void removeBroadcastChannel(String channel)
{
if(channel == null || channel.isEmpty())
{
return;
}
List<AliasID> toRemove = new ArrayList<>();
for(AliasID aliasID: getAliasIdentifiers())
{
if(aliasID instanceof BroadcastChannel &&
((BroadcastChannel)aliasID).getChannelName().contentEquals(channel))
{
toRemove.add(aliasID);
}
}
for(AliasID aliasID: toRemove)
{
removeAliasID(aliasID);
}
}
/**
* Removes (clears) all broadcast channels from this alias
*/
public void removeAllBroadcastChannels()
{
List<AliasID> toRemove = new ArrayList<>();
for(AliasID aliasID: mAliasIDs)
{
if(aliasID instanceof BroadcastChannel)
{
toRemove.add(aliasID);
}
}
for(AliasID aliasID: toRemove)
{
removeAliasID(aliasID);
}
}
/**
* Removes all non audio identifiers to prepare for replacing them from an editor.
*/
public void removeNonAudioIdentifiers()
{
Iterator<AliasID> it = mAliasIDs.iterator();
while(it.hasNext())
{
if(!it.next().isAudioIdentifier())
{
it.remove();
}
}
}
/**
* Removes all alias actions from this alias
*/
public void removeAllActions()
{
mAliasActions.clear();
}
/**
* Creates an observable property extractor for use with observable lists to detect changes internal to this object.
*/
public static Callback<Alias,Observable[]> extractor()
{
return (Alias a) -> new Observable[] {a.recordableProperty(), a.streamableProperty(), a.colorProperty(),
a.aliasListNameProperty(), a.groupProperty(), a.iconNameProperty(), a.nameProperty(), a.aliasIds(),
a.aliasActions(), a.nonAudioIdentifierCountProperty(), a.overlapProperty()};
}
}

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,55 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias;
public class AliasEvent
{
private Alias mAlias;
private Event mEvent;
/**
* AliasEvent - event describing any changes to an alias
* @param alias - alias that changed
* @param event - change event
*/
public AliasEvent( Alias alias, Event event )
{
mAlias = alias;
mEvent = event;
}
public Alias getAlias()
{
return mAlias;
}
public Event getEvent()
{
return mEvent;
}
/**
* Channel events to describe the specific event
*/
public enum Event
{
ADD,
CHANGE,
DELETE;
}
}

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,40 @@ package io.github.dsheirer.alias;
import io.github.dsheirer.alias.action.AliasAction;
import io.github.dsheirer.alias.action.AliasActionType;
import io.github.dsheirer.alias.action.beep.BeepAction;
import io.github.dsheirer.alias.action.beep.BeepActionEditor;
import io.github.dsheirer.alias.action.clip.ClipAction;
import io.github.dsheirer.alias.action.clip.ClipActionEditor;
import io.github.dsheirer.alias.action.script.ScriptAction;
import io.github.dsheirer.alias.action.script.ScriptActionEditor;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannelEditor;
import io.github.dsheirer.alias.id.esn.ESNEditor;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncIDEditor;
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200IDEditor;
import io.github.dsheirer.alias.id.legacy.mobileID.MINEditor;
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327ID;
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327IDEditor;
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordable;
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordableEditor;
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
import io.github.dsheirer.alias.id.legacy.siteID.SiteIDEditor;
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupIDEditor;
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueIDEditor;
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
import io.github.dsheirer.alias.id.lojack.LoJackIDEditor;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.priority.PriorityEditor;
import io.github.dsheirer.alias.id.radio.Radio;
import io.github.dsheirer.alias.id.radio.RadioEditor;
import io.github.dsheirer.alias.id.radio.RadioRange;
import io.github.dsheirer.alias.id.radio.RadioRangeEditor;
import io.github.dsheirer.alias.id.record.Record;
import io.github.dsheirer.alias.id.record.RecordEditor;
import io.github.dsheirer.alias.id.status.StatusID;
import io.github.dsheirer.alias.id.status.StatusIDEditor;
import io.github.dsheirer.alias.id.status.UnitStatusID;
import io.github.dsheirer.alias.id.status.UserStatusID;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupEditor;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRangeEditor;
import io.github.dsheirer.audio.broadcast.BroadcastModel;
import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.gui.editor.EmptyEditor;
import io.github.dsheirer.alias.id.tone.TonesID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class AliasFactory
{
private static final Logger mLog = LoggerFactory.getLogger(AliasFactory.class);
public static AliasID copyOf(AliasID id)
{
switch(id.getType())
@@ -87,11 +72,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 +91,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 +107,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 +155,11 @@ public class AliasFactory
{
if(action instanceof BeepAction)
{
return new BeepAction();
BeepAction original = (BeepAction)action;
BeepAction copyBeep = new BeepAction();
copyBeep.setInterval(original.getInterval());
copyBeep.setPeriod(original.getPeriod());
return copyBeep;
}
else if(action instanceof ClipAction)
{
@@ -179,106 +183,59 @@ public class AliasFactory
return null;
}
public static Alias copyOf(Alias original)
public static Alias shallowCopyOf(Alias original)
{
Alias copy = new Alias(original.getName());
copy.setList(original.getList());
copy.setAliasListName(original.getAliasListName());
copy.setGroup(original.getGroup());
copy.setColor(original.getColor());
copy.setIconName(original.getIconName());
for(AliasID id : original.getId())
{
AliasID copyID = copyOf(id);
if(copyID != null)
{
copy.addAliasID(copyID);
}
}
for(AliasAction action : original.getAction())
{
AliasAction copyAction = copyOf(action);
if(copyAction != null)
{
copy.addAliasAction(copyAction);
}
}
return copy;
}
public static Editor<AliasID> getEditor(AliasID aliasID, BroadcastModel broadcastModel)
public static List<AliasID> copyAliasIDs(Alias original)
{
if(aliasID != null)
{
switch(aliasID.getType())
{
case BROADCAST_CHANNEL:
return new BroadcastChannelEditor(aliasID, broadcastModel);
case ESN:
return new ESNEditor(aliasID);
case LTR_NET_UID:
return new UniqueIDEditor(aliasID);
case LOJACK:
return new LoJackIDEditor(aliasID);
case MIN:
return new MINEditor(aliasID);
case PRIORITY:
return new PriorityEditor(aliasID);
case RADIO_ID:
return new RadioEditor(aliasID);
case RADIO_ID_RANGE:
return new RadioRangeEditor(aliasID);
case RECORD:
return new RecordEditor(aliasID);
case SITE:
return new SiteIDEditor(aliasID);
case STATUS:
return new StatusIDEditor(aliasID);
case TALKGROUP:
return new TalkgroupEditor(aliasID);
case TALKGROUP_RANGE:
return new TalkgroupRangeEditor(aliasID);
List<AliasID> aliasIDS = new ArrayList<>();
case FLEETSYNC:
return new FleetsyncIDEditor(aliasID);
case LEGACY_TALKGROUP:
return new LegacyTalkgroupIDEditor(aliasID);
case MDC1200:
return new MDC1200IDEditor(aliasID);
case MPT1327:
return new MPT1327IDEditor(aliasID);
case NON_RECORDABLE:
return new NonRecordableEditor(aliasID);
default:
break;
}
for(AliasID id : original.getAliasIdentifiers())
{
aliasIDS.add(copyOf(id));
}
return new EmptyEditor<AliasID>();
return aliasIDS;
}
public static Editor<AliasAction> getEditor(AliasAction aliasAction)
public static List<AliasAction> copyAliasActions(Alias original)
{
if(aliasAction != null)
List<AliasAction> actions = new ArrayList<>();
for(AliasAction aliasAction: original.getAliasActions())
{
switch(aliasAction.getType())
{
case BEEP:
return new BeepActionEditor(aliasAction);
case CLIP:
return new ClipActionEditor(aliasAction);
case SCRIPT:
return new ScriptActionEditor(aliasAction);
default:
break;
}
actions.add(copyOf(aliasAction));
}
return new EmptyEditor<AliasAction>();
return actions;
}
public static Alias copyOf(Alias original)
{
Alias copy = shallowCopyOf(original);
List<AliasID> aliasIDS = copyAliasIDs(original);
for(AliasID aliasID: aliasIDS)
{
copy.addAliasID(aliasID);
}
List<AliasAction> actions = copyAliasActions(original);
for(AliasAction action: actions)
{
copy.addAliasAction(action);
}
return copy;
}
public static AliasID getAliasID(AliasIDType type)
@@ -318,7 +275,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,32 +42,41 @@ import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.status.UnitStatusIdentifier;
import io.github.dsheirer.identifier.status.UserStatusIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.identifier.tone.ToneIdentifier;
import io.github.dsheirer.identifier.tone.ToneSequence;
import io.github.dsheirer.protocol.Protocol;
import io.github.dsheirer.sample.Listener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* List of aliases that share the same alias list name and provides convenient methods for looking up alias
* objects that match an identifier.
*/
public class AliasList implements Listener<AliasEvent>
public class AliasList
{
private final static Logger mLog = LoggerFactory.getLogger(AliasList.class);
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new HashMap<>();
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new HashMap<>();
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new EnumMap<>(Protocol.class);
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new EnumMap<>(Protocol.class);
private Map<String,Alias> mESNMap = new HashMap<>();
private Map<Integer,Alias> mStatusMap = new HashMap<>();
private Map<Integer,Alias> mUnitStatusMap = new HashMap<>();
private Map<Integer,Alias> mUserStatusMap = new HashMap<>();
private Map<ToneSequence,Alias> mToneSequenceMap = new HashMap<>();
private boolean mHasAliasActions = false;
private String mName;
private ObservableList<Alias> mAliases = FXCollections.observableArrayList(Alias.extractor());
/**
* List of aliases where all aliases share the same list name. Contains
@@ -78,6 +89,14 @@ public class AliasList implements Listener<AliasEvent>
mName = name;
}
/**
* Observable list of aliases contained in this alias list
*/
public ObservableList<Alias> aliases()
{
return mAliases;
}
/**
* Adds the alias to this list
*/
@@ -85,16 +104,18 @@ public class AliasList implements Listener<AliasEvent>
{
if(alias != null)
{
for(AliasID aliasID : alias.getId())
{
addAliasID(aliasID, alias);
}
alias.getAliasIdentifiers().stream().forEach(aliasID -> addAliasID(aliasID, alias));
}
if(alias.hasActions())
{
mHasAliasActions = true;
}
if(!mAliases.contains(alias))
{
mAliases.add(alias);
}
}
/**
@@ -161,7 +182,7 @@ public class AliasList implements Listener<AliasEvent>
radioRangeAliasList.add(radioRange, alias);
break;
case ESN:
String esn = ((Esn) id).getEsn();
String esn = ((Esn)id).getEsn();
if(esn != null && !esn.isEmpty())
{
@@ -169,7 +190,67 @@ public class AliasList implements Listener<AliasEvent>
}
break;
case STATUS:
mStatusMap.put(((StatusID) id).getStatus(), alias);
int userStatus = ((UserStatusID)id).getStatus();
if(mUserStatusMap.containsKey(userStatus) && !mUserStatusMap.get(userStatus).equals(alias))
{
id.setOverlap(true);
Alias existing = mUserStatusMap.get(userStatus);
for(AliasID aliasID: existing.getAliasIdentifiers())
{
if(aliasID instanceof UserStatusID && ((UserStatusID)aliasID).getStatus() == userStatus)
{
aliasID.setOverlap(true);
}
}
}
mUserStatusMap.put(userStatus, alias);
break;
case UNIT_STATUS:
int unitStatus = ((UnitStatusID)id).getStatus();
if(mUnitStatusMap.containsKey(unitStatus) && !mUnitStatusMap.get(unitStatus).equals(alias))
{
id.setOverlap(true);
Alias existing = mUnitStatusMap.get(unitStatus);
for(AliasID aliasID: existing.getAliasIdentifiers())
{
if(aliasID instanceof UnitStatusID && ((UnitStatusID)aliasID).getStatus() == unitStatus)
{
aliasID.setOverlap(true);
}
}
}
mUnitStatusMap.put(unitStatus, alias);
break;
case TONES:
ToneSequence toneSequence = ((TonesID)id).getToneSequence();
if(toneSequence != null)
{
if(mToneSequenceMap.containsKey(toneSequence) && !mToneSequenceMap.get(toneSequence).equals(alias))
{
id.setOverlap(true);
Alias existing = mToneSequenceMap.get(toneSequence);
for(AliasID aliasID: existing.getAliasIdentifiers())
{
if(aliasID instanceof TonesID && aliasID.equals(id))
{
aliasID.setOverlap(true);
}
}
}
else
{
mToneSequenceMap.put(toneSequence, alias);
}
}
break;
}
}
@@ -185,13 +266,45 @@ public class AliasList implements Listener<AliasEvent>
*/
public void removeAlias(Alias alias)
{
for(TalkgroupAliasList talkgroupAliasList: mTalkgroupProtocolMap.values())
//Note: because the alias' identifiers could have changed from when we initially added the alias, we have to
//inspect every collection and map to remove the alias completely.
mAliases.remove(alias);
mTalkgroupProtocolMap.values().stream().forEach(talkgroupAliasList -> talkgroupAliasList.remove(alias));
mRadioProtocolMap.values().stream().forEach(radioAliasList -> radioAliasList.remove(alias));
Collection<Alias> collection = Collections.singleton(alias);
mESNMap.values().removeAll(collection);
mUnitStatusMap.values().removeAll(collection);
mUserStatusMap.values().removeAll(collection);
mToneSequenceMap.values().removeAll(collection);
validate();
}
/**
* Identifies all aliases with an alias identifier that has the overlap flag set, resets the flag, and then readds
* each alias back to this alias list so that overlap can be detected again.
*/
public void validate()
{
Set<Alias> overlapAliases = new HashSet<>();
List<Alias> aliases = new ArrayList<>(mAliases);
for(Alias alias: aliases)
{
talkgroupAliasList.remove(alias);
for(AliasID aliasID: alias.getAliasIdentifiers())
{
if(aliasID.overlapProperty().get())
{
aliasID.setOverlap(false);
overlapAliases.add(alias);
}
}
}
remove(alias, mStatusMap);
remove(alias, mESNMap);
overlapAliases.stream().forEach(alias -> addAlias(alias));
}
/**
@@ -235,39 +348,15 @@ public class AliasList implements Listener<AliasEvent>
}
/**
* Receive alias change event notifications and modify this list accordingly
* Updates the alias by removing it from this list and then adding it back to this list when the list name matches.
*/
@Override
public void receive(AliasEvent event)
public void updateAlias(Alias alias)
{
if(hasName())
{
Alias alias = event.getAlias();
removeAlias(alias);
switch(event.getEvent())
{
case ADD:
if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList()))
{
addAlias(alias);
}
break;
case CHANGE:
if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList()))
{
removeAlias(alias);
addAlias(alias);
}
break;
case DELETE:
if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList()))
{
removeAlias(alias);
}
break;
default:
break;
}
if(hasName() && getName().equals(alias.getAliasListName()))
{
addAlias(alias);
}
}
@@ -342,14 +431,31 @@ public class AliasList implements Listener<AliasEvent>
if(identifier instanceof UnitStatusIdentifier)
{
int status = ((UnitStatusIdentifier)identifier).getValue();
return toList(mStatusMap.get(status));
return toList(mUserStatusMap.get(status));
}
break;
case USER_STATUS:
if(identifier instanceof UserStatusIdentifier)
{
int status = ((UserStatusIdentifier)identifier).getValue();
return toList(mStatusMap.get(status));
return toList(mUserStatusMap.get(status));
}
break;
case TONE:
if(identifier instanceof ToneIdentifier)
{
ToneSequence toneSequence = ((ToneIdentifier)identifier).getValue();
if(toneSequence != null && toneSequence.hasTones())
{
for(Map.Entry<ToneSequence,Alias> entry: mToneSequenceMap.entrySet())
{
if(entry.getKey().isContainedIn(toneSequence))
{
return toList(entry.getValue());
}
}
}
}
break;
}
@@ -482,23 +588,6 @@ public class AliasList implements Listener<AliasEvent>
return channels;
}
/**
* Removes the alias (as a value) from the specified map
*/
public static void remove(Alias alias, Map map)
{
Iterator<Map.Entry> it = map.entrySet().iterator();
while(it.hasNext())
{
if(it.next().getValue().equals(alias))
{
it.remove();
}
}
}
/**
* Listing of talkgroups and ranges for a specific protocol
*/
@@ -515,16 +604,17 @@ public class AliasList implements Listener<AliasEvent>
{
int value = identifier.getValue();
if(mTalkgroupAliasMap.containsKey(value))
Alias mapValue = mTalkgroupAliasMap.get(value);
if (mapValue != null)
{
return mTalkgroupAliasMap.get(value);
return mapValue;
}
for(TalkgroupRange talkgroupRange: mTalkgroupRangeAliasMap.keySet())
for(Map.Entry<TalkgroupRange, Alias> entry : mTalkgroupRangeAliasMap.entrySet())
{
if(talkgroupRange.contains(value))
if(entry.getKey().contains(value))
{
return mTalkgroupRangeAliasMap.get(talkgroupRange);
return entry.getValue();
}
}
@@ -533,14 +623,23 @@ 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());
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() + "]");
if(!existing.equals(alias))
{
talkgroup.setOverlap(true);
for(AliasID aliasID: existing.getAliasIdentifiers())
{
if(aliasID instanceof Talkgroup && ((Talkgroup)aliasID).getValue() == talkgroup.getValue())
{
aliasID.setOverlap(true);
}
}
}
}
mTalkgroupAliasMap.put(talkgroup.getValue(), alias);
@@ -551,34 +650,23 @@ public class AliasList implements Listener<AliasEvent>
//Log warning if the new talkgroup range overlaps with any existing ranges
for(Map.Entry<TalkgroupRange,Alias> entry: mTalkgroupRangeAliasMap.entrySet())
{
if(talkgroupRange.overlaps(entry.getKey()))
if(talkgroupRange.overlaps(entry.getKey()) && !entry.getValue().equals(alias))
{
mLog.warn("Alias [" + alias.getName() + "] with talkgroup range [" + talkgroupRange.toString() +
"] overlaps with alias [" + entry.getValue().getName() +
"] with talkgroup range [" + entry.getKey().toString() + "] for alias list [" + getName() + "]");
talkgroupRange.setOverlap(true);
entry.getKey().setOverlap(true);
}
}
mTalkgroupRangeAliasMap.put(talkgroupRange, alias);
}
public void remove(Talkgroup talkgroup)
{
mTalkgroupAliasMap.remove(talkgroup.getValue());
}
public void remove(TalkgroupRange talkgroupRange)
{
mTalkgroupRangeAliasMap.remove(talkgroupRange);
}
/**
* Removes the alias from all internal maps
* Removes the alias from both the talkgroup and the talkgroup range maps.
*/
public void remove(Alias alias)
{
AliasList.remove(alias, mTalkgroupAliasMap);
AliasList.remove(alias, mTalkgroupRangeAliasMap);
mTalkgroupAliasMap.values().removeAll(Collections.singleton(alias));
mTalkgroupRangeAliasMap.values().removeAll(Collections.singleton(alias));
}
}
@@ -598,16 +686,17 @@ public class AliasList implements Listener<AliasEvent>
{
int value = identifier.getValue();
if(mRadioAliasMap.containsKey(value))
Alias mapValue = mRadioAliasMap.get(value);
if(mapValue != null)
{
return mRadioAliasMap.get(value);
return mapValue;
}
for(RadioRange radioRange: mRadioRangeAliasMap.keySet())
for(Map.Entry<RadioRange, Alias> entry : mRadioRangeAliasMap.entrySet())
{
if(radioRange.contains(value))
if(entry.getKey().contains(value))
{
return mRadioRangeAliasMap.get(radioRange);
return entry.getValue();
}
}
@@ -621,9 +710,18 @@ public class AliasList implements Listener<AliasEvent>
{
Alias existing = mRadioAliasMap.get(radio.getValue());
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() + "]");
if(!existing.equals(alias))
{
radio.setOverlap(true);
for(AliasID aliasID: existing.getAliasIdentifiers())
{
if(aliasID instanceof Radio && ((Radio)aliasID).getValue() == radio.getValue())
{
aliasID.setOverlap(true);
}
}
}
}
mRadioAliasMap.put(radio.getValue(), alias);
@@ -634,34 +732,23 @@ public class AliasList implements Listener<AliasEvent>
//Log warning if the new range overlaps with any existing ranges
for(Map.Entry<RadioRange,Alias> entry: mRadioRangeAliasMap.entrySet())
{
if(radioRange.overlaps(entry.getKey()))
if(radioRange.overlaps(entry.getKey()) && !entry.getValue().equals(alias))
{
mLog.warn("Alias [" + alias.getName() + "] with radio ID range [" + radioRange.toString() +
"] overlaps with alias [" + entry.getValue().getName() +
"] with range [" + entry.getKey().toString() + "] for alias list [" + getName() + "]");
radioRange.setOverlap(true);
entry.getKey().setOverlap(true);
}
}
mRadioRangeAliasMap.put(radioRange, alias);
}
public void remove(Radio radio)
{
mRadioAliasMap.remove(radio.getValue());
}
public void remove(RadioRange radioRange)
{
mRadioRangeAliasMap.remove(radioRange);
}
/**
* Removes the alias from all internal maps
* Removes the alias from both the radio and the radio range maps.
*/
public void remove(Alias alias)
{
AliasList.remove(alias, mRadioAliasMap);
AliasList.remove(alias, mRadioRangeAliasMap);
mRadioAliasMap.values().removeAll(Collections.singleton(alias));
mRadioRangeAliasMap.values().removeAll(Collections.singleton(alias));
}
}
}

View File

@@ -1,59 +1,70 @@
/*
* ******************************************************************************
* 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 Broadcaster<AliasEvent> mAliasEventBroadcaster = new Broadcaster<>();
private final static Logger mLog = LoggerFactory.getLogger(AliasModel.class);
public static final String NO_ALIAS_LIST = "(No Alias List)";
private ObservableList<Alias> mAliases = FXCollections.observableArrayList(Alias.extractor());
private ObservableList<String> mAliasListNames = FXCollections.observableArrayList();
private Map<String,AliasList> mAliasListMap = new HashMap<>();
public AliasModel()
{
//Register a listener to detect alias changes and broadcast change events to cause playlist save requests
mAliases.addListener(new AliasListChangeListener());
}
public ObservableList<Alias> aliasList()
{
return mAliases;
}
public ObservableList<String> aliasListNames()
{
return mAliasListNames;
}
/**
@@ -64,14 +75,19 @@ public class AliasModel extends AbstractTableModel
return Collections.unmodifiableList(mAliases);
}
public Alias getAliasAtIndex(int row)
/**
* Removes all aliases from the list and broadcasts the alias delete event for each
*/
public void clear()
{
if(mAliases.size() >= row)
List<Alias> aliasToRemove = new ArrayList<>(mAliases);
for(Alias alias: aliasToRemove)
{
return mAliases.get(row);
removeAlias(alias);
}
return null;
mAliasListNames.clear();
}
/**
@@ -117,16 +133,17 @@ public class AliasModel extends AbstractTableModel
return new AliasList(name);
}
if(mAliasListMap.containsKey(name))
AliasList mapValue = mAliasListMap.get(name);
if (mapValue != null)
{
return mAliasListMap.get(name);
return mapValue;
}
AliasList aliasList = new AliasList(name);
for(Alias alias : mAliases)
{
if(alias.hasList() && alias.getList().equalsIgnoreCase(name))
if(alias.hasList() && alias.getAliasListName().equalsIgnoreCase(name))
{
aliasList.addAlias(alias);
}
@@ -134,9 +151,6 @@ public class AliasModel extends AbstractTableModel
mAliasListMap.put(name, aliasList);
//Register the new alias list to receive updates from this model
addListener(aliasList);
return aliasList;
}
@@ -145,19 +159,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 +182,6 @@ public class AliasModel extends AbstractTableModel
return groupNames;
}
/**
* Returns a list of alias group names for all aliases that have a matching
* list name value
*/
public List<String> getGroupNames(String listName)
{
List<String> groupNames = new ArrayList<>();
if(listName != null)
{
for(Alias alias : mAliases)
{
if(alias.hasList() &&
alias.hasGroup() &&
listName.equals(alias.getList()) &&
!groupNames.contains(alias.getGroup()))
{
groupNames.add(alias.getGroup());
}
}
}
Collections.sort(groupNames);
return groupNames;
}
/**
* Bulk loading of aliases
*/
@@ -221,173 +196,177 @@ public class AliasModel extends AbstractTableModel
/**
* Adds the alias to the model
*/
public int addAlias(Alias alias)
public void addAlias(Alias alias)
{
if(alias != null)
if(mAliases.contains(alias))
{
mAliases.add(alias);
int index = mAliases.size() - 1;
fireTableRowsInserted(index, index);
broadcast(new AliasEvent(alias, AliasEvent.Event.ADD));
return index;
removeAlias(alias);
}
return -1;
mAliases.add(alias);
}
private boolean hasAliasList(String aliasListName)
{
return aliasListName != null && mAliasListMap.containsKey(aliasListName);
}
public void addAliasList(String aliasListName)
{
if(aliasListName != null && !aliasListName.isEmpty())
{
if(!mAliasListNames.contains(aliasListName))
{
mAliasListNames.add(aliasListName);
}
}
else if(!mAliasListNames.contains(NO_ALIAS_LIST))
{
//This list allows users to view unassigned aliases so that they can move them to a valid alias list, but
// it is not assignable to a channel
mAliasListNames.add(NO_ALIAS_LIST);
}
}
/**
* Removes the channel from the model and broadcasts a channel remove event
* Removes the alias from this model and an alias list (if one exists)
*/
public void removeAlias(Alias alias)
{
if(alias != null)
{
int index = mAliases.indexOf(alias);
mAliases.remove(alias);
fireTableRowsDeleted(index, index);
broadcast(new AliasEvent(alias, AliasEvent.Event.DELETE));
if(hasAliasList(alias.getAliasListName()))
{
getAliasList(alias.getAliasListName()).removeAlias(alias);
}
}
}
/**
* Renames any broadcast channels that have the previous name.
*
* @param previousName to rename
* @param newName to assign to the broadcast channel
* Retrieves all aliases that match the alias list and have at least one alias ID of the specified type
* @param aliasListName to search
* @param type to find
* @return list of aliases
*/
public void renameBroadcastChannel(String previousName, String newName)
public List<Alias> getAliases(String aliasListName, AliasIDType type)
{
if(previousName == null || previousName.isEmpty() || newName == null || newName.isEmpty())
List<Alias> aliases = new ArrayList<>();
for(Alias alias : mAliases)
{
if(alias.hasList() && alias.getAliasListName().equalsIgnoreCase(aliasListName))
{
for(AliasID aliasID: alias.getAliasIdentifiers())
{
if(aliasID.getType() == type)
{
aliases.add(alias);
break;
}
}
}
}
return aliases;
}
/**
* Indicates that one or more of the aliases managed by this model are configured to stream to the specified
* broadcast channel argument.
* @param broadcastChannel to check
* @return true if the broadcast channel is non-null, non-empty and at least one alias is configured to stream to
* the specified stream name.
*/
public boolean hasAliasesWithBroadcastChannel(String broadcastChannel)
{
if(broadcastChannel == null || broadcastChannel.isEmpty())
{
return false;
}
for(Alias alias: mAliases)
{
if(alias.hasBroadcastChannel(broadcastChannel))
{
return true;
}
}
return false;
}
/**
* Updates all aliases configured to stream to the previousStreamName with the updatedStreamName
* @param previousStreamName to be removed
* @param updatedStreamName to be added
*/
public void updateBroadcastChannel(String previousStreamName, String updatedStreamName)
{
if(previousStreamName == null || previousStreamName.isEmpty() || updatedStreamName == null || updatedStreamName.isEmpty())
{
return;
}
for(Alias alias : mAliases)
for(Alias alias: mAliases)
{
if(alias.hasBroadcastChannel(previousName))
if(alias.hasBroadcastChannel(previousStreamName))
{
for(BroadcastChannel broadcastChannel : alias.getBroadcastChannels())
for(BroadcastChannel broadcastChannel: alias.getBroadcastChannels())
{
if(broadcastChannel.getChannelName().contentEquals(previousName))
if(broadcastChannel.getChannelName().contentEquals(previousStreamName))
{
broadcastChannel.setChannelName(newName);
alias.removeAliasID(broadcastChannel);
if(!alias.hasBroadcastChannel(updatedStreamName))
{
alias.addAliasID(new BroadcastChannel(updatedStreamName));
}
}
}
}
}
}
@Override
public int getRowCount()
/**
* Monitors alias additions and removals and updates alias lists
*/
public class AliasListChangeListener implements ListChangeListener<Alias>
{
return mAliases.size();
}
@Override
public int getColumnCount()
{
return 5;
}
@Override
public String getColumnName(int columnIndex)
{
switch(columnIndex)
@Override
public void onChanged(ListChangeListener.Change<? extends Alias> change)
{
case COLUMN_LIST:
return "List";
case COLUMN_GROUP:
return "Group";
case COLUMN_NAME:
return "Name";
case COLUMN_ICON:
return "Icon";
case COLUMN_COLOR:
return "Color";
while(change.next())
{
if(change.wasAdded())
{
for(Alias alias: change.getAddedSubList())
{
addAliasList(alias.getAliasListName());
if(hasAliasList(alias.getAliasListName()))
{
getAliasList(alias.getAliasListName()).addAlias(alias);
}
}
}
else if(change.wasRemoved())
{
for(Alias alias: change.getRemoved())
{
if(hasAliasList(alias.getAliasListName()))
{
AliasList aliasList = getAliasList(alias.getName());
if(alias != null)
{
aliasList.removeAlias(alias);
}
}
}
}
}
}
return null;
}
@Override
public Class<?> getColumnClass(int columnIndex)
{
if(columnIndex == COLUMN_COLOR)
{
return Integer.class;
}
return String.class;
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex)
{
return false;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex)
{
Alias alias = mAliases.get(rowIndex);
switch(columnIndex)
{
case COLUMN_LIST:
return alias.getList();
case COLUMN_GROUP:
return alias.getGroup();
case COLUMN_NAME:
return alias.getName();
case COLUMN_ICON:
return alias.getIconName();
case COLUMN_COLOR:
return alias.getColor();
}
return null;
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex)
{
throw new IllegalArgumentException("Not yet implemented");
}
public void broadcast(AliasEvent event)
{
Alias alias = event.getAlias();
//Validate the alias following a user action that changed the alias or any alias IDs
if(alias != null)
{
alias.validate();
}
if(event.getEvent() == AliasEvent.Event.CHANGE)
{
int index = mAliases.indexOf(event.getAlias());
fireTableRowsUpdated(index, index);
}
mAliasEventBroadcaster.broadcast(event);
}
public void addListener(Listener<AliasEvent> listener)
{
mAliasEventBroadcaster.addListener(listener);
}
public void removeListener(Listener<AliasEvent> listener)
{
mAliasEventBroadcaster.removeListener(listener);
}
}

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[groups.size()])));
;
}
}
setModified(true);
}
});
add(mListCombo, "wrap");
add(new JLabel("Group:"));
mGroupCombo.setEditable(true);
mGroupCombo.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(mGroupCombo, "wrap");
add(new JLabel("Color:"));
mButtonColor = new JButton("Select ...");
mButtonColor.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
Color newColor = JColorChooser.showDialog(
AliasNameEditor.this,
"Choose color for this alias",
(hasItem() ? getItem().getDisplayColor() : null));
if(newColor != null)
{
mButtonColor.setForeground(newColor);
mButtonColor.setBackground(newColor);
setModified(true);
}
}
});
add(mButtonColor, "wrap");
add(new JLabel("Icon:"));
mIconCombo = new JComboBox<Icon>(mIconManager.getIcons());
IconListCellRenderer renderer = new IconListCellRenderer(mIconManager);
renderer.setPreferredSize(new Dimension(200, 30));
mIconCombo.setRenderer(renderer);
mIconCombo.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(mIconCombo, "wrap");
//Dummy place holder
add(new JLabel());
mBtnIconManager = new JButton("Icon Manager");
mBtnIconManager.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent arg0)
{
mIconManager.showEditor(AliasNameEditor.this);
}
});
add(mBtnIconManager, "span 2,wrap");
setModified(false);
}
private void refreshIcons()
{
if(mIconCombo != null)
{
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
mIconCombo.setModel(new DefaultComboBoxModel<>(mIconManager.getIcons()));
if(hasItem())
{
String iconName = getItem().getIconName();
Icon aliasIcon = mIconManager.getModel().getIcon(iconName);
if(aliasIcon != null)
{
mIconCombo.setSelectedItem(aliasIcon);
}
}
}
});
}
}
@Override
public void setItem(Alias alias)
{
super.setItem(alias);
if(hasItem())
{
mName.setText(alias.getName());
mIconCombo.setModel(new DefaultComboBoxModel<>(mIconManager.getIcons()));
List<String> listNames = mAliasModel.getListNames();
if(listNames.isEmpty())
{
mListCombo.setModel(EMPTY_LIST_MODEL);
}
else
{
mListCombo.setModel(new DefaultComboBoxModel<String>(
listNames.toArray(new String[listNames.size()])));
;
}
mListCombo.setSelectedItem(alias.getList());
List<String> groupNames = mAliasModel.getGroupNames(alias.getList());
if(groupNames.isEmpty())
{
mGroupCombo.setModel(EMPTY_GROUP_MODEL);
}
else
{
mGroupCombo.setModel(new DefaultComboBoxModel<String>(
groupNames.toArray(new String[groupNames.size()])));
;
}
mGroupCombo.setSelectedItem(alias.getGroup());
Color color = alias.getDisplayColor();
mButtonColor.setBackground(color);
mButtonColor.setForeground(color);
String iconName = alias.getIconName();
if(iconName == null)
{
iconName = IconTableModel.DEFAULT_ICON;
}
Icon savedIcon = mIconManager.getModel().getIcon(iconName);
if(savedIcon != null)
{
mIconCombo.setSelectedItem(savedIcon);
}
}
else
{
mListCombo.setModel(EMPTY_LIST_MODEL);
mGroupCombo.setModel(EMPTY_GROUP_MODEL);
mName.setText(null);
mButtonColor.setBackground(getBackground());
mButtonColor.setForeground(getForeground());
}
repaint();
setModified(false);
}
@Override
public void save()
{
if(hasItem() && isModified())
{
Alias alias = getItem();
if(mListCombo.getSelectedItem() != null)
{
alias.setList((String) mListCombo.getSelectedItem());
}
if(mGroupCombo.getSelectedItem() != null)
{
alias.setGroup((String) mGroupCombo.getSelectedItem());
}
alias.setName(mName.getText());
alias.setColor(mButtonColor.getBackground().getRGB());
if(mIconCombo.getSelectedItem() != null)
{
alias.setIconName(((Icon) mIconCombo.getSelectedItem()).getName());
}
setModified(false);
//Broadcast an alias change event to save the updates
mAliasModel.broadcast(new AliasEvent(getItem(), AliasEvent.Event.CHANGE));
}
}
}

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[listNames.size()])));
List<String> groupNames = mAliasModel.getGroupNames();
groupNames.add(0, "");
mGroupCombo.setModel(new DefaultComboBoxModel<String>(
groupNames.toArray(new String[groupNames.size()])));
List<String> streamNames = mBroadcastModel.getBroadcastConfigurationNames();
mStreamCombo.setModel(new DefaultComboBoxModel<String>(streamNames.toArray(new String[streamNames.size()])));
mIconCombo.setModel(new DefaultComboBoxModel<>(mIconManager.getIcons()));
setCheckBoxesUnselected();
setModified(false);
}
private void setCheckBoxesUnselected()
{
mListCheckBox.setSelected(false);
mGroupCheckBox.setSelected(false);
mColorCheckBox.setSelected(false);
mIconCheckBox.setSelected(false);
mRecordCheckBox.setSelected(false);
mPriorityCheckBox.setSelected(false);
mStreamCheckBox.setSelected(false);
}
@Override
public void reset()
{
List<String> listNames = mAliasModel.getListNames();
listNames.add(0, "");
mListCombo.setModel(new DefaultComboBoxModel<String>(
listNames.toArray(new String[listNames.size()])));
List<String> groupNames = mAliasModel.getGroupNames();
groupNames.add(0, "");
mGroupCombo.setModel(new DefaultComboBoxModel<String>(
groupNames.toArray(new String[groupNames.size()])));
mButtonColor.setForeground(getForeground());
mButtonColor.setBackground(getBackground());
super.reset();
}
private boolean hasRequestedChanges()
{
return mListCheckBox.isSelected() ||
mGroupCheckBox.isSelected() ||
mColorCheckBox.isSelected() ||
mIconCheckBox.isSelected() ||
mRecordCheckBox.isSelected() ||
mPriorityCheckBox.isSelected() ||
mStreamCheckBox.isSelected();
}
@Override
public void save()
{
if (hasRequestedChanges() && hasItem())
{
List<Alias> aliases = getAliases();
for (Alias alias : aliases)
{
if (mListCheckBox.isSelected())
{
String list = null;
if (mListCombo.getSelectedItem() != null)
{
list = (String) mListCombo.getSelectedItem();
}
alias.setList(list);
}
if (mGroupCheckBox.isSelected())
{
String group = null;
if (mGroupCombo.getSelectedItem() != null)
{
group = (String) mGroupCombo.getSelectedItem();
}
alias.setGroup(group);
}
if (mColorCheckBox.isSelected())
{
alias.setColor(mButtonColor.getForeground().getRGB());
}
if (mIconCheckBox.isSelected())
{
if (mIconCombo.getSelectedItem() != null)
{
alias.setIconName(((Icon)mIconCombo.getSelectedItem()).getName());
}
else
{
alias.setIconName(null);
}
}
if (mRecordCheckBox.isSelected())
{
Record action =
(Record) mRecordActionCombo.getSelectedItem();
alias.setRecordable(action == Record.RECORDABLE);
}
if (mPriorityCheckBox.isSelected())
{
int priority = mPrioritySlider.getValue();
//This is a work-around -- we use max priority + 1 in the
//gui to indicate do not monitor, and change the value here
if (priority == Priority.MAX_PRIORITY + 1)
{
priority = Priority.DO_NOT_MONITOR;
}
alias.setCallPriority(priority);
}
if(mStreamCheckBox.isSelected())
{
if(mStreamCombo.getSelectedItem() != null)
{
String streamName = (String)mStreamCombo.getSelectedItem();
boolean hasChannel = false;
//Ensure the alias doesn't already have the channel
for(BroadcastChannel channel: alias.getBroadcastChannels())
{
if(channel.getChannelName().equals(streamName))
{
hasChannel = true;
continue;
}
}
if(!hasChannel)
{
BroadcastChannel channel = (BroadcastChannel)AliasFactory
.getAliasID(AliasIDType.BROADCAST_CHANNEL);
channel.setChannelName(streamName);
alias.addAliasID(channel);
}
}
}
}
for (Alias alias : aliases)
{
//Broadcast an alias change event to save the updates
mAliasModel.broadcast(new AliasEvent(alias, AliasEvent.Event.CHANGE));
}
}
}
@Override
public void receive(AliasEvent event)
{
if (event.getEvent() == AliasEvent.Event.DELETE &&
hasItem() &&
getAliases().contains(event.getAlias()))
{
getAliases().remove(event.getAlias());
setItem(getAliases());
}
}
public enum Record
{
NON_RECORDABLE("Non-Recordable"),
RECORDABLE("Recordable");
private String mLabel;
private Record(String label)
{
mLabel = label;
}
public String toString()
{
return mLabel;
}
}
}

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,339 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias.action;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasEvent;
import io.github.dsheirer.alias.AliasEvent.Event;
import io.github.dsheirer.alias.AliasFactory;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.gui.editor.EmptyEditor;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class AliasActionEditor extends Editor<Alias>
{
private static final long serialVersionUID = 1L;
private static ListModel<AliasAction> EMPTY_MODEL = new DefaultListModel<>();
private JList<AliasAction> mAliasActionList = new JList<>( EMPTY_MODEL );
private JButton mNewActionButton;
private JButton mCloneActionButton;
private JButton mDeleteActionButton;
private EditorContainer mEditorContainer = new EditorContainer();
private AliasModel mAliasModel;
public AliasActionEditor( AliasModel model )
{
mAliasModel = model;
init();
}
private void init()
{
setLayout( new MigLayout( "fill,wrap 3",
"[grow,fill][grow,fill][grow,fill]", "[][grow,fill][]" ) );
mAliasActionList.setVisibleRowCount( 6 );
mAliasActionList.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
mAliasActionList.setLayoutOrientation( JList.VERTICAL );
mAliasActionList.addListSelectionListener( new ListSelectionListener()
{
@Override
public void valueChanged( ListSelectionEvent event )
{
if( !event.getValueIsAdjusting() )
{
JList<?> list = (JList<?>)event.getSource();
Object selectedItem = list.getSelectedValue();
if( selectedItem != null && selectedItem instanceof AliasAction )
{
AliasAction selected = (AliasAction)selectedItem;
mEditorContainer.setAliasAction( selected );
mCloneActionButton.setEnabled( true );
mDeleteActionButton.setEnabled( true );
}
else
{
mCloneActionButton.setEnabled( false );
mDeleteActionButton.setEnabled( false );
}
}
}
} );
JScrollPane scroller = new JScrollPane( mAliasActionList );
add( scroller, "span,grow" );
add( mEditorContainer, "span,grow" );
mNewActionButton = new JButton( "New ..." );
mNewActionButton.setToolTipText( "Create a new Alias Action" );
mNewActionButton.addMouseListener( new MouseAdapter()
{
@Override
public void mouseClicked( MouseEvent e )
{
JPopupMenu menu = new JPopupMenu();
menu.add( new AddAliasActionItem( AliasActionType.BEEP ) );
menu.add( new AddAliasActionItem( AliasActionType.CLIP ) );
menu.add( new AddAliasActionItem( AliasActionType.SCRIPT ) );
menu.show( e.getComponent(), e.getX(), e.getY() );
}
} );
add( mNewActionButton );
mCloneActionButton = new JButton( "Clone" );
mCloneActionButton.setToolTipText( "Create a copy of the currently selected action" );
mCloneActionButton.setEnabled( false );
mCloneActionButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
AliasAction selected = mAliasActionList.getSelectedValue();
if( selected != null )
{
AliasAction clone = AliasFactory.copyOf( selected );
if( clone != null )
{
addAliasActionToList( clone );
}
}
}
} );
add( mCloneActionButton );
mDeleteActionButton = new JButton( "Delete" );
mDeleteActionButton.setToolTipText( "Delete the currently selected action" );
mDeleteActionButton.setEnabled( false );
mDeleteActionButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
final AliasAction selected = mAliasActionList.getSelectedValue();
if( selected != null )
{
int choice = JOptionPane.showConfirmDialog( mDeleteActionButton,
"Do you want to delete [" + selected.toString() + "]",
"Are you sure?", JOptionPane.YES_NO_OPTION );
if( choice == JOptionPane.YES_OPTION && hasItem() )
{
Alias alias = getItem();
alias.removeAliasAction( selected );
mAliasModel.broadcast( new AliasEvent( alias, Event.CHANGE ) );
setItem( alias );
}
}
}
} );
add( mDeleteActionButton, "wrap" );
}
@Override
public void setItem( Alias alias )
{
super.setItem( alias );
if( alias == null || alias.getAction().isEmpty() )
{
mAliasActionList.setModel( EMPTY_MODEL );
}
else
{
DefaultListModel<AliasAction> model = new DefaultListModel<>();
List<AliasAction> actions = alias.getAction();
Collections.sort( actions, new Comparator<AliasAction>()
{
@Override
public int compare( AliasAction o1, AliasAction o2 )
{
return o1.toString().compareTo( o2.toString() );
}
} );
for( AliasAction action: actions )
{
model.addElement( action );
}
mAliasActionList.setModel( model );
}
mEditorContainer.setAliasAction( null );
}
@Override
public void save()
{
if( isModified() || mEditorContainer.isModified() )
{
if( mEditorContainer.isModified() )
{
mEditorContainer.save();
}
setModified( false );
if( hasItem() )
{
mAliasModel.broadcast( new AliasEvent( getItem(), Event.CHANGE ) );
}
}
}
public class EditorContainer extends JPanel
{
private static final long serialVersionUID = 1L;
private Editor<AliasAction> mEditor = new EmptyEditor<>();
public EditorContainer()
{
setLayout( new MigLayout( "","[grow,fill]", "[grow,fill]" ) );
add( mEditor );
}
public boolean isModified()
{
if( mEditor != null )
{
return mEditor.isModified();
}
return false;
}
public void save()
{
if( mEditor != null )
{
mEditor.save();
}
}
public void setAliasAction( AliasAction aliasAction )
{
if( mEditor != null )
{
if( mEditor.isModified() )
{
int option = JOptionPane.showConfirmDialog(
EditorContainer.this,
"Action settings have changed. Do you want to save these changes?",
"Save Changes?",
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE );
if( option == JOptionPane.YES_OPTION )
{
mEditor.save();
}
}
}
removeAll();
//This will always give us an editor
mEditor = AliasFactory.getEditor( aliasAction );
add( mEditor );
revalidate();
}
}
private void addAliasActionToList( final AliasAction action )
{
if( action != null && hasItem() )
{
final Alias alias = getItem();
alias.addAliasAction( action );
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
setItem( alias );
mAliasActionList.setSelectedValue( action, true );
AliasActionEditor.this.setModified( true );
}
} );
}
}
public class AddAliasActionItem extends JMenuItem
{
private static final long serialVersionUID = 1L;
private AliasActionType mAliasActionType;
public AddAliasActionItem( AliasActionType type )
{
super( type.toString() );
mAliasActionType = type;
addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
final AliasAction action =
AliasFactory.getAliasAction( mAliasActionType );
addAliasActionToList( action );
}
} );
}
}
}

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,377 +0,0 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.alias.id;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasEvent;
import io.github.dsheirer.alias.AliasFactory;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.BroadcastModel;
import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.gui.editor.EmptyEditor;
import net.miginfocom.swing.MigLayout;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class AliasIdentifierEditor extends Editor<Alias>
{
private static final long serialVersionUID = 1L;
private static ListModel<AliasID> EMPTY_MODEL = new DefaultListModel<>();
private JList<AliasID> mAliasIDList = new JList<>(EMPTY_MODEL);
private JButton mNewIDButton;
private JButton mCloneIDButton;
private JButton mDeleteIDButton;
private EditorContainer mEditorContainer = new EditorContainer();
private AliasModel mAliasModel;
private BroadcastModel mBroadcastModel;
public AliasIdentifierEditor(AliasModel aliasModel, BroadcastModel broadcastModel)
{
mAliasModel = aliasModel;
mBroadcastModel = broadcastModel;
init();
}
private void init()
{
setLayout(new MigLayout("fill,wrap 3",
"[grow,fill][grow,fill][grow,fill]", "[][grow,fill][]"));
mAliasIDList.setVisibleRowCount(6);
mAliasIDList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mAliasIDList.setLayoutOrientation(JList.VERTICAL);
mAliasIDList.addListSelectionListener(new ListSelectionListener()
{
@Override
public void valueChanged(ListSelectionEvent event)
{
if(!event.getValueIsAdjusting())
{
JList<?> list = (JList<?>)event.getSource();
Object selectedItem = list.getSelectedValue();
if(selectedItem != null && selectedItem instanceof AliasID)
{
AliasID selected = (AliasID)selectedItem;
mEditorContainer.setAliasID(selected);
mCloneIDButton.setEnabled(true);
mDeleteIDButton.setEnabled(true);
}
else
{
mCloneIDButton.setEnabled(false);
mDeleteIDButton.setEnabled(false);
}
}
}
});
JScrollPane scroller = new JScrollPane(mAliasIDList);
add(scroller, "span,grow");
add(mEditorContainer, "span,grow");
mNewIDButton = new JButton("New ...");
mNewIDButton.setToolTipText("Create a new Alias Identifier");
mNewIDButton.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
JPopupMenu menu = new JPopupMenu();
menu.add(new AddAliasIdentifierItem(AliasIDType.TALKGROUP));
menu.add(new AddAliasIdentifierItem(AliasIDType.TALKGROUP_RANGE));
menu.add(new AddAliasIdentifierItem(AliasIDType.RADIO_ID));
menu.add(new AddAliasIdentifierItem(AliasIDType.RADIO_ID_RANGE));
menu.add(new AddAliasIdentifierItem(AliasIDType.ESN));
// menu.add(new AddAliasIdentifierItem(AliasIDType.LOJACK));
menu.add(new AddAliasIdentifierItem(AliasIDType.STATUS));
menu.addSeparator();
menu.add(new AddAliasIdentifierItem(AliasIDType.BROADCAST_CHANNEL));
if(hasItem())
{
Alias alias = getItem();
if(!alias.isRecordable())
{
menu.add(new AddAliasIdentifierItem(AliasIDType.RECORD));
}
if(!alias.hasCallPriority())
{
menu.add(new AddAliasIdentifierItem(AliasIDType.PRIORITY));
}
}
menu.show(e.getComponent(), e.getX(), e.getY());
}
});
add(mNewIDButton);
mCloneIDButton = new JButton("Clone");
mCloneIDButton.setToolTipText("Create a copy of the currently selected identifier");
mCloneIDButton.setEnabled(false);
mCloneIDButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
AliasID selected = mAliasIDList.getSelectedValue();
if(selected != null)
{
AliasID clone = AliasFactory.copyOf(selected);
if(clone != null)
{
addAliasIDToList(clone);
}
}
}
});
add(mCloneIDButton);
mDeleteIDButton = new JButton("Delete");
mDeleteIDButton.setToolTipText("Delete the currently selected identifier");
mDeleteIDButton.setEnabled(false);
mDeleteIDButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
final AliasID selected = mAliasIDList.getSelectedValue();
if(selected != null)
{
int choice = JOptionPane.showConfirmDialog(mDeleteIDButton,
"Do you want to delete [" + selected.toString() + "]",
"Are you sure?", JOptionPane.YES_NO_OPTION);
if(choice == JOptionPane.YES_OPTION && hasItem())
{
Alias alias = getItem();
alias.removeAliasID(selected);
mAliasModel.broadcast(new AliasEvent(alias, AliasEvent.Event.CHANGE));
setItem(alias);
}
}
}
});
add(mDeleteIDButton, "wrap");
}
@Override
public void setItem(Alias alias)
{
super.setItem(alias);
if(alias == null || alias.getId().isEmpty())
{
mAliasIDList.setModel(EMPTY_MODEL);
}
else
{
DefaultListModel<AliasID> model = new DefaultListModel<AliasID>();
List<AliasID> ids = alias.getId();
Collections.sort(ids, new Comparator<AliasID>()
{
@Override
public int compare(AliasID o1, AliasID o2)
{
return o1.toString().compareTo(o2.toString());
}
});
for(AliasID id : ids)
{
model.addElement(id);
}
mAliasIDList.setModel(model);
}
mEditorContainer.setAliasID(null);
}
@Override
public void save()
{
if(isModified() || mEditorContainer.isModified())
{
if(mEditorContainer.isModified())
{
mEditorContainer.save();
}
setModified(false);
if(hasItem())
{
mAliasModel.broadcast(new AliasEvent(getItem(), AliasEvent.Event.CHANGE));
}
}
}
public class EditorContainer extends JPanel
{
private static final long serialVersionUID = 1L;
private Editor<AliasID> mEditor = new EmptyEditor<>();
public EditorContainer()
{
setLayout(new MigLayout("", "[grow,fill]", "[grow,fill]"));
add(mEditor);
}
public boolean isModified()
{
if(mEditor != null)
{
return mEditor.isModified();
}
return false;
}
public void save()
{
if(mEditor != null)
{
mEditor.save();
}
}
public void setAliasID(AliasID aliasID)
{
if(mEditor != null)
{
if(mEditor.isModified())
{
int option = JOptionPane.showConfirmDialog(
EditorContainer.this,
"Identifier settings have changed. Do you want to save these changes?",
"Save Changes?",
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE);
if(option == JOptionPane.YES_OPTION)
{
mEditor.save();
}
}
}
removeAll();
//This will always give us an editor
mEditor = AliasFactory.getEditor(aliasID, mBroadcastModel);
add(mEditor);
revalidate();
}
}
private void addAliasIDToList(final AliasID id)
{
if(id != null && hasItem())
{
final Alias alias = getItem();
alias.addAliasID(id);
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
setItem(alias);
mAliasIDList.setSelectedValue(id, true);
AliasIdentifierEditor.this.setModified(true);
}
});
}
}
public class AddAliasIdentifierItem extends JMenuItem
{
private static final long serialVersionUID = 1L;
private AliasIDType mAliasIDType;
public AddAliasIdentifierItem(AliasIDType type)
{
super(type.toString());
mAliasIDType = type;
addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
final AliasID id = AliasFactory.getAliasID(mAliasIDType);
addAliasIDToList(id);
}
});
}
}
}

View File

@@ -1,113 +0,0 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.alias.id;
import org.apache.commons.lang3.Validate;
import java.util.regex.Pattern;
public class WildcardID implements Comparable<WildcardID>
{
public static final String WILDCARD = "*";
public static final String REGEX_WILDCARD = ".";
private String mValue;
private Pattern mPattern;
private int mWeight;
/**
* Wildcard identifier for matching to string identifiers containing single-character (*) wildcard values.
*
* Supports ordering from most-specific to least-specific using the build-in weighting calculation where identifier
* patterns containing fewer wildcards closer to the least significant digit will be weighted more heavily than
* identifier patterns containing more wildcards, or wildcard characters in the most significant digits.
*
* @param value is a string value containing one or more asterisk (*) single character wildcard with a maximum
* length of 10 characters.
*
* @throws AssertionError if the value is null, longer than 10 characters, or doesn't contain at least 1 asterisk
* @throws IllegalArgumentException if the value cannot be compiled as a regular expression after converting the
* asterisk characters to the regex single-character wildcard value (.)
*/
public WildcardID(String value)
{
Validate.isTrue(value != null && value.contains(WILDCARD) && value.length() < 10);
mValue = value;
try
{
mPattern = Pattern.compile(value.replace(WILDCARD, REGEX_WILDCARD));
}
catch(Exception e)
{
throw new IllegalArgumentException("Invalid regex pattern for alias ID value [" + value + "]", e);
}
mWeight = calculateWeight();
}
public String value()
{
return mValue;
}
/**
* Indicates if the id matches the internal regex pattern for this wildcard alias ID
* @param id
* @return
*/
public boolean matches(String id)
{
return id != null && mPattern.matcher(id).matches();
}
public int weight()
{
return mWeight;
}
/**
* Calculates a weighting value for wildcard character quantity and significant digit location
*/
private int calculateWeight()
{
int weight = 0;
int characterCount = -1;
for( int x = 0; x < mValue.length(); x++)
{
if(mValue.substring(x, x + 1).equals(WILDCARD))
{
weight += (int)(Math.pow(2, mValue.length() - x - 1)); //Position weight
characterCount++;
}
}
weight += (int)(Math.pow(2, characterCount)) * 1000; //Character count weight
return weight;
}
@Override
public int compareTo(WildcardID otherWildcardID)
{
return Integer.compare(this.weight(), otherWildcardID.weight());
}
}

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[channelNames.size()]));
mBroadcastConfigurations.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(mBroadcastConfigurations, "grow");
JLabel help = new JLabel( "Help ..." );
help.setForeground( Color.BLUE.brighter() );
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
help.addMouseListener( new MouseAdapter()
{
@Override
public void mouseClicked( MouseEvent e )
{
JOptionPane.showMessageDialog( BroadcastChannelEditor.this,
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
}
} );
add( help, "align left" );
}
@Override
public void setItem(AliasID item)
{
super.setItem(item);
if(item instanceof BroadcastChannel)
{
BroadcastChannel channel = (BroadcastChannel)item;
if(channel.isValid())
{
mBroadcastConfigurations.setSelectedItem(channel.getChannelName());
}
}
setModified(false);
}
@Override
public void save()
{
String channel = (String)mBroadcastConfigurations.getSelectedItem();
BroadcastConfiguration selectedConfiguration = mBroadcastModel.getBroadcastConfiguration(channel);
if(selectedConfiguration != null)
{
((BroadcastChannel)getItem()).setChannelName(selectedConfiguration.getName());
}
else
{
((BroadcastChannel)getItem()).setChannelName(null);
}
}
}

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
@@ -78,8 +85,8 @@ public class Radio extends AliasID
return false;
}
TalkgroupFormat talkgroupFormat = TalkgroupFormat.get(mProtocol);
return talkgroupFormat.getMinimumValidValue() <= mValue && mValue <= talkgroupFormat.getMaximumValidValue();
RadioFormat radioFormat = RadioFormat.get(mProtocol);
return radioFormat.getMinimumValidValue() <= mValue && mValue <= radioFormat.getMaximumValidValue();
}
public String toString()
@@ -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,13 +22,16 @@
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;
import java.text.ParseException;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
@@ -38,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 HashMap<>();
private static Map<Protocol,AbstractIntegerFormatter> mFormatterMap = new EnumMap<>(Protocol.class);
static
{
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
mFormatterMap.put(Protocol.UNKNOWN, new IntegerFormatter());
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
}
public RadioFormatter()
@@ -55,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)
{
@@ -70,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)
{
@@ -79,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,18 +20,21 @@
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;
import java.text.ParseException;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
@@ -41,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 HashMap<>();
private static Map<Protocol,AbstractIntegerFormatter> mFormatterMap = new EnumMap<>(Protocol.class);
static
{
@@ -50,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()
@@ -66,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)
{
@@ -81,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)
{
@@ -90,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

@@ -20,41 +20,156 @@
package io.github.dsheirer.audio;
import io.github.dsheirer.audio.squelch.ISquelchStateListener;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.identifier.IdentifierUpdateListener;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base audio module implementation.
*/
public abstract class AbstractAudioModule extends Module implements IAudioPacketProvider, IdentifierUpdateListener,
ISquelchStateListener
public abstract class AbstractAudioModule extends Module implements IAudioSegmentProvider, IdentifierUpdateListener
{
//Static unique audio channel identifier.
private static int AUDIO_CHANNEL_ID_GENERATOR = 1;
private final static Logger mLog = LoggerFactory.getLogger(AbstractAudioModule.class);
private static final int DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH = 60 * 8000; // 1 minute @ 8kHz
private int mAudioChannelId = AUDIO_CHANNEL_ID_GENERATOR++;
private Listener<ReusableAudioPacket> mAudioPacketListener;
private MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
private final int mMaxSegmentAudioSampleLength;
private Listener<AudioSegment> mAudioSegmentListener;
protected MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
private Broadcaster<IdentifierUpdateNotification> mIdentifierUpdateNotificationBroadcaster = new Broadcaster<>();
private AliasList mAliasList;
private AudioSegment mAudioSegment;
private int mAudioSampleCount = 0;
private boolean mRecordAudioOverride;
/**
* Constructs an abstract audio module
*
* @param aliasList for aliasing identifiers
* @param maxSegmentAudioSampleLength in milliseconds
*/
public AbstractAudioModule(AliasList aliasList, long maxSegmentAudioSampleLength)
{
mAliasList = aliasList;
mMaxSegmentAudioSampleLength = (int)(maxSegmentAudioSampleLength * 8); //Convert milliseconds to samples
mIdentifierUpdateNotificationBroadcaster.addListener(mIdentifierCollection);
}
/**
* Constructs an abstract audio module
*/
public AbstractAudioModule()
public AbstractAudioModule(AliasList aliasList)
{
this(aliasList, DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH);
}
protected abstract int getTimeslot();
/**
* Closes the current audio segment
*/
protected void closeAudioSegment()
{
synchronized(this)
{
if(mAudioSegment != null)
{
mAudioSegment.completeProperty().set(true);
mIdentifierUpdateNotificationBroadcaster.removeListener(mAudioSegment);
mAudioSegment.decrementConsumerCount();
mAudioSegment = null;
}
}
}
@Override
public void stop()
{
closeAudioSegment();
}
/**
* Unique channel identifier for use in tagging audio packets with an audio channel ID so that they can
* be identified within a combined audio packet stream
* Gets the current audio segment, or creates a new audio segment as necessary and broadcasts it to any registered
* listener(s).
*/
public int getAudioChannelId()
protected AudioSegment getAudioSegment()
{
return mAudioChannelId;
synchronized(this)
{
if(mAudioSegment == null)
{
mAudioSegment = new AudioSegment(mAliasList, getTimeslot());
mAudioSegment.incrementConsumerCount();
mAudioSegment.addIdentifiers(mIdentifierCollection.getIdentifiers());
mIdentifierUpdateNotificationBroadcaster.addListener(mAudioSegment);
if(mRecordAudioOverride)
{
mAudioSegment.recordAudioProperty().set(true);
}
if(mAudioSegmentListener != null)
{
mAudioSegment.incrementConsumerCount();
mAudioSegmentListener.receive(mAudioSegment);
}
mAudioSampleCount = 0;
}
return mAudioSegment;
}
}
protected void addAudio(float[] audioBuffer)
{
AudioSegment audioSegment = getAudioSegment();
//If the current segment exceeds the max samples length, close it so that a new segment gets generated
//and then link the segments together
if(mAudioSampleCount >= mMaxSegmentAudioSampleLength)
{
AudioSegment previous = getAudioSegment();
closeAudioSegment();
audioSegment = getAudioSegment();
audioSegment.linkTo(previous);
}
try
{
audioSegment.addAudio(audioBuffer);
mAudioSampleCount += audioBuffer.length;
}
catch(Exception e)
{
closeAudioSegment();
}
}
/**
* Sets all audio segments as recordable when the argument is true. Otherwise, defers to the aliased identifiers
* from the identifier collection to determine whether to record the audio or not.
* @param recordAudio set to true to mark all audio as recordable.
*/
public void setRecordAudio(boolean recordAudio)
{
mRecordAudioOverride = recordAudio;
if(mRecordAudioOverride)
{
synchronized(this)
{
if(mAudioSegment != null)
{
mAudioSegment.recordAudioProperty().set(true);
}
}
}
}
/**
@@ -63,7 +178,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioPacket
@Override
public Listener<IdentifierUpdateNotification> getIdentifierUpdateListener()
{
return mIdentifierCollection;
return mIdentifierUpdateNotificationBroadcaster;
}
/**
@@ -75,37 +190,20 @@ public abstract class AbstractAudioModule extends Module implements IAudioPacket
}
/**
* Registers an audio packet listener to receive the output from this audio module.
* Registers an audio segment listener to receive the output from this audio module.
*/
@Override
public void setAudioPacketListener(Listener<ReusableAudioPacket> listener)
public void setAudioSegmentListener(Listener<AudioSegment> listener)
{
mAudioPacketListener = listener;
mAudioSegmentListener = listener;
}
/**
* Unregisters the audio packet listener from receiving audio packets from this module.
* Unregisters the audio segment listener from receiving audio segments from this module.
*/
@Override
public void removeAudioPacketListener()
public void removeAudioSegmentListener()
{
mAudioPacketListener = null;
mAudioSegmentListener = null;
}
/**
* Registered listener for receiving audio packets produced by this module
*/
public Listener<ReusableAudioPacket> getAudioPacketListener()
{
return mAudioPacketListener;
}
/**
* Indicates if there is a listener registered to receive audio packets from this audio module.
*/
public boolean hasAudioPacketListener()
{
return mAudioPacketListener != null;
}
}

View File

@@ -1,85 +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.audio;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
/**
* Processes audio packets to assign attributes to each packet based on alias list settings. Updates the
* monitoring, recording and streaming attributes.
*/
public class AudioMetadataProcessor
{
private static final Logger mLog = LoggerFactory.getLogger(AudioMetadataProcessor.class);
private AliasModel mAliasModel;
private Map<Integer, AliasList> mAliasListMap = new HashMap<>();
private Set<Integer> mAliasListRetrievedSet = new TreeSet<>();
public AudioMetadataProcessor(AliasModel aliasModel)
{
mAliasModel = aliasModel;
}
public void process(ReusableAudioPacket audioPacket)
{
if(audioPacket.hasIdentifierCollection() && audioPacket.getIdentifierCollection().hasAliasListConfiguration())
{
AliasList aliasList = null;
if(mAliasListRetrievedSet.contains(audioPacket.getAudioChannelId()))
{
aliasList = mAliasListMap.get(audioPacket.getAudioChannelId());
}
else
{
aliasList = mAliasModel.getAliasList(audioPacket.getIdentifierCollection().getAliasListConfiguration());
if(aliasList != null)
{
mAliasListMap.put(audioPacket.getAudioChannelId(), aliasList);
mAliasListRetrievedSet.add(audioPacket.getAudioChannelId());
}
}
if(aliasList != null)
{
audioPacket.addBroadcastChannels(aliasList.getBroadcastChannels(audioPacket.getIdentifierCollection()));
audioPacket.setMonitoringPriority(aliasList.getAudioPlaybackPriority(audioPacket.getIdentifierCollection()));
//If the audio packet is already marked recordable, leave it alone, otherwise attempt to determine
//if we should record the audio packet from the aliased identifiers
if(!audioPacket.isRecordable())
{
audioPacket.setRecordable(aliasList.isRecordable(audioPacket.getIdentifierCollection()));
}
}
}
}
}

View File

@@ -21,6 +21,8 @@
*/
package io.github.dsheirer.audio;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.audio.squelch.ISquelchStateListener;
import io.github.dsheirer.audio.squelch.SquelchState;
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
@@ -29,23 +31,19 @@ import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
import io.github.dsheirer.sample.buffer.ReusableAudioPacketQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides packaging of demodulated audio sample buffers into audio packets for broadcast to registered audio packet
* listeners. Includes audio packet metadata in constructed audio packets.
* Provides packaging of demodulated audio sample buffers into audio segments for broadcast to registered listeners.
* Includes audio packet metadata in constructed audio segments.
*
* Incorporates audio squelch state listener to control if audio packets are broadcast or ignored.
*
* This class is designed to support 8 kHz sample rate demodulated audio.
*/
public class AudioModule extends AbstractAudioModule implements IReusableBufferListener, Listener<ReusableFloatBuffer>
public class AudioModule extends AbstractAudioModule implements ISquelchStateListener, IReusableBufferListener,
Listener<ReusableFloatBuffer>
{
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
private static float[] sHighPassFilterCoefficients;
@@ -75,36 +73,42 @@ public class AudioModule extends AbstractAudioModule implements IReusableBufferL
}
}
private ReusableAudioPacketQueue mAudioPacketQueue = new ReusableAudioPacketQueue("AudioModule");
private RealFIRFilter2 mHighPassFilter = new RealFIRFilter2(sHighPassFilterCoefficients);
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private SquelchState mSquelchState = SquelchState.SQUELCH;
private boolean mRecordAudioOverride;
/**
* Creates an Audio Module.
*
* @param aliasList for aliasing identifiers
* @param maxAudioSegmentLength in milliseconds
*/
public AudioModule(AliasList aliasList, long maxAudioSegmentLength)
{
super(aliasList, maxAudioSegmentLength);
}
/**
* Creates an Audio Module.
*/
public AudioModule()
public AudioModule(AliasList aliasList)
{
super(aliasList);
}
@Override
protected int getTimeslot()
{
return 0;
}
@Override
public void dispose()
{
removeAudioPacketListener();
removeAudioSegmentListener();
mSquelchStateListener = null;
}
/**
* Sets all audio packets as recordable when the argument is true. Otherwise, defers to the aliased identifiers
* from the identifier collection to determine whether to record the audio or not.
* @param recordAudio set to true to mark all audio as recordable.
*/
public void setRecordAudio(boolean recordAudio)
{
mRecordAudioOverride = recordAudio;
}
@Override
public void reset()
{
@@ -114,30 +118,8 @@ public class AudioModule extends AbstractAudioModule implements IReusableBufferL
@Override
public void start()
{
/* No start operations provided */
}
@Override
public void stop()
{
/* Issue an end-audio packet in case a recorder is still rolling */
if(hasAudioPacketListener())
{
ReusableAudioPacket endAudioPacket = mAudioPacketQueue.getEndAudioBuffer();
endAudioPacket.resetAttributes();
endAudioPacket.setAudioChannelId(getAudioChannelId());
endAudioPacket.setIdentifierCollection(getIdentifierCollection().copyOf());
if(mRecordAudioOverride)
{
endAudioPacket.setRecordable(true);
}
endAudioPacket.incrementUserCount();
getAudioPacketListener().receive(endAudioPacket);
}
}
@Override
public Listener<SquelchStateEvent> getSquelchStateListener()
{
@@ -145,29 +127,17 @@ public class AudioModule extends AbstractAudioModule implements IReusableBufferL
}
@Override
public void receive(ReusableFloatBuffer reusableFloatBuffer)
public void receive(ReusableFloatBuffer audioBuffer)
{
if(hasAudioPacketListener() && mSquelchState == SquelchState.UNSQUELCH)
if(mSquelchState == SquelchState.UNSQUELCH)
{
ReusableFloatBuffer highPassFiltered = mHighPassFilter.filter(reusableFloatBuffer);
ReusableAudioPacket audioPacket = mAudioPacketQueue.getBuffer(highPassFiltered.getSampleCount());
audioPacket.resetAttributes();
audioPacket.setAudioChannelId(getAudioChannelId());
audioPacket.loadAudioFrom(highPassFiltered);
if(mRecordAudioOverride)
{
audioPacket.setRecordable(true);
}
audioPacket.setIdentifierCollection(getIdentifierCollection().copyOf());
getAudioPacketListener().receive(audioPacket);
highPassFiltered.decrementUserCount();
ReusableFloatBuffer highPassFilteredAudio = mHighPassFilter.filter(audioBuffer);
addAudio(highPassFilteredAudio.getSamplesCopy());
highPassFilteredAudio.decrementUserCount();
}
else
{
reusableFloatBuffer.decrementUserCount();
audioBuffer.decrementUserCount();
}
}
@@ -186,16 +156,12 @@ public class AudioModule extends AbstractAudioModule implements IReusableBufferL
@Override
public void receive(SquelchStateEvent event)
{
if(event.getSquelchState() == SquelchState.SQUELCH && hasAudioPacketListener())
{
ReusableAudioPacket endAudioPacket = mAudioPacketQueue.getEndAudioBuffer();
endAudioPacket.resetAttributes();
endAudioPacket.setAudioChannelId(getAudioChannelId());
endAudioPacket.setIdentifierCollection(getIdentifierCollection().copyOf());
getAudioPacketListener().receive(endAudioPacket);
}
mSquelchState = event.getSquelchState();
if(mSquelchState == SquelchState.SQUELCH)
{
closeAudioSegment();
}
}
}
}

View File

@@ -1,98 +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.audio;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.dsp.filter.channelizer.ContinuousReusableBufferProcessor;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* Processes audio packets from all decoder channels, assigns alias attributes and distributes packets
* for playback, streaming and recording.
*/
public class AudioPacketManager implements Listener<ReusableAudioPacket>
{
private final static Logger mLog = LoggerFactory.getLogger(AudioPacketManager.class);
private AudioMetadataProcessor mAudioMetadataProcessor;
private ReusableBufferBroadcaster<ReusableAudioPacket> mBroadcaster = new ReusableBufferBroadcaster<>();
private ContinuousReusableBufferProcessor<ReusableAudioPacket> mBufferProcessor;
public AudioPacketManager(AliasModel aliasModel)
{
mAudioMetadataProcessor = new AudioMetadataProcessor(aliasModel);
mBufferProcessor = new ContinuousReusableBufferProcessor<>(10000, 500);
mBufferProcessor.setListener(new Listener<List<ReusableAudioPacket>>()
{
@Override
public void receive(List<ReusableAudioPacket> reusableAudioPackets)
{
for(ReusableAudioPacket audioPacket: reusableAudioPackets)
{
mAudioMetadataProcessor.process(audioPacket);
mBroadcaster.broadcast(audioPacket);
}
}
});
}
public void start()
{
mLog.info("Audio packet manager started");
mBufferProcessor.start();
}
public void stop()
{
mLog.info("Audio packet manager stopped");
mBufferProcessor.stop();
}
/**
* Primary processing method for receiving audio packets from all processing channels.
*/
@Override
public void receive(ReusableAudioPacket audioPacket)
{
mBufferProcessor.receive(audioPacket);
}
/**
* Adds the listener to receive enriched audio packets from this manager
*/
public void addListener(Listener<ReusableAudioPacket> listener)
{
mBroadcaster.addListener(listener);
}
/**
* Removes the listener from receiving enriched audio packets from this manager
*/
public void removeListener(Listener<ReusableAudioPacket> listener)
{
mBroadcaster.removeListener(listener);
}
}

View File

@@ -0,0 +1,480 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2020 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.audio;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Audio segment containing all related metadata and a dynamic collection of audio packets. An audio segment can be
* a discrete (ie start/stop) audio event, or it may be a time-constrained portion of an ongoing continuous audio
* broadcast. Since the audio segment is held in memory until all consumers have finished processing the segment,
* producers should constrain the duration of each audio segment to a reasonable duration.
*
* Producers can link time-constrained audio segments from a continous broadcast (e.g. FM radio station) so that
* consumers can identify audio segments that belong to a continous stream. This linkage presents a memory leak
* potential. Therefore, the audio segment uses the consumer counter to trigger an unlinking via the dispose() mehtod.
* Accurate accounting of consumer count via the increment/decrementConsumerCount() methods is essential for good
* memory management.
*
* Producers will add audio buffers and update identifiers throughout the life-cycle of an audio segment. The producer
* will signal the completion of an audio segment by setting the complete property to true. This allows consumers the
* option to process the audio buffers throughout the life-cycle of the segment, or to process all of the buffers once
* the segment is complete.
*/
public class AudioSegment implements Listener<IdentifierUpdateNotification>
{
private final static Logger mLog = LoggerFactory.getLogger(AudioSegment.class);
private BooleanProperty mComplete = new SimpleBooleanProperty(false);
private BooleanProperty mDuplicate = new SimpleBooleanProperty(false);
private BooleanProperty mRecordAudio = new SimpleBooleanProperty(false);
private IntegerProperty mMonitorPriority = new SimpleIntegerProperty(Priority.DEFAULT_PRIORITY);
private ObservableSet<BroadcastChannel> mBroadcastChannels = FXCollections.observableSet(new HashSet<>());
private MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
private Broadcaster<IdentifierUpdateNotification> mIdentifierUpdateNotificationBroadcaster = new Broadcaster<>();
private List<float[]> mAudioBuffers = new CopyOnWriteArrayList();
private AtomicInteger mConsumerCount = new AtomicInteger();
private AliasList mAliasList;
private long mStartTimestamp = System.currentTimeMillis();
private long mSampleCount = 0;
private boolean mDisposing = false;
private AudioSegment mLinkedAudioSegment;
private int mTimeslot;
/**
* Constructs an instance
*
* @param aliasList for accessing aliases associated with identifiers for this audio segment.
*/
public AudioSegment(AliasList aliasList, int timeslot)
{
mAliasList = aliasList;
mTimeslot = timeslot;
mIdentifierCollection.setTimeslot(timeslot);
}
/**
* Timeslot for this audio segment
*/
public int getTimeslot()
{
return mTimeslot;
}
/**
* Creation timestamp for this audio segment
* @return long milliseconds since epoch
*/
public long getStartTimestamp()
{
return mStartTimestamp;
}
/**
* End timestamp as calculated from start timestamp and current sample count
*/
public long getEndTimestamp()
{
return mStartTimestamp + getDuration();
}
/**
* Current duration of this audio segment. Note: this is a dynamic value until the complete property is set to true.
*
* @return duration in milliseconds
*/
public long getDuration()
{
return (mSampleCount / 8); //8 kHz audio generates 8 samples per millisecond
}
/**
* The complete property is used by the audio segment producer to signal that the segment is complete and no
* additional audio or identifiers will be added to the segment.
*
* @return complete property
*/
public BooleanProperty completeProperty()
{
return mComplete;
}
/**
* Duplicate call audio property. This flag is set to true whenever a duplicate call detection function detects
* an audio segment is a duplicate.
*/
public BooleanProperty duplicateProperty()
{
return mDuplicate;
}
/**
* An observable list of broadcast channels for this audio segment. Broadcast channels are added to this segment
* across the life-cycle of the segment. As each new alias identifier is added to the segment, any broadcast
* channels assigned to the alias are added to this list. The audio segment producer can also add broadcast
* channels to this list.
*
* @return observable set of broadcast channels
*/
public ObservableSet<BroadcastChannel> broadcastChannelsProperty()
{
return mBroadcastChannels;
}
/**
* Set of broadcast channels from identifier associated aliases for this segment.
*/
public Set<BroadcastChannel> getBroadcastChannels()
{
return Collections.unmodifiableSet(mBroadcastChannels);
}
/**
* Indicates if this segment has audio streaming broadcast channels specified.
*/
public boolean hasBroadcastChannels()
{
return !mBroadcastChannels.isEmpty();
}
/**
* Property to signal that this audio segment should be recorded. This property can either be set by the producer
* of the audio segment, or it can be flipped to true by any aliases that are added to this segment that require
* audio associated with the identifier to be recorded.
*
* @return
*/
public BooleanProperty recordAudioProperty()
{
return mRecordAudio;
}
/**
* Audio playback/monitor priority specified by identifier associated aliases for this segment.
*/
public IntegerProperty monitorPriorityProperty()
{
return mMonitorPriority;
}
/**
* Indicates if at least one of the identifier associated aliases for this segments specifies Do Not Monitor.
*/
public boolean isDoNotMonitor()
{
return mMonitorPriority.get() <= Priority.DO_NOT_MONITOR;
}
/**
* Alias list for this audio segment
*/
public AliasList getAliasList()
{
return mAliasList;
}
/**
* Indicates if this audio segment is linked to a preceding audio segment.
*/
public boolean isLinked()
{
return mLinkedAudioSegment != null;
}
/**
* Indicates if this audio segment is linked to the argument audio segment
* @param audioSegment to check for linkage
* @return true if this segment is linked to the argument
*/
public boolean isLinkedTo(AudioSegment audioSegment)
{
return isLinked() && audioSegment != null && mLinkedAudioSegment.equals(audioSegment);
}
/**
* Optional linked audio segment is the audio segment that precedes this audio segment in a continuous audio
* broadcast.
*
* @return linked audio segment or null.
*/
public AudioSegment getLinkedAudioSegment()
{
return mLinkedAudioSegment;
}
/**
* Links this audio segment to a preceeding/previous audio segment
* @param previousAudioSegment to set as the predecessor
*/
public void linkTo(AudioSegment previousAudioSegment)
{
mLinkedAudioSegment = previousAudioSegment;
}
/**
* Immutable identifier collection for this audio segment
*/
public IdentifierCollection getIdentifierCollection()
{
return mIdentifierCollection;
}
/**
* Adds the collection of identifiers to this segment's identifier collection
* @param identifiers to pre-load into this audio segment
*/
void addIdentifiers(Collection<Identifier> identifiers)
{
for(Identifier identifier: identifiers)
{
addIdentifier(identifier);
}
}
/**
* Unmodifiable copy of the list of audio buffers for this segment.
*
* @return list of audio buffers
*/
public List<float[]> getAudioBuffers()
{
return Collections.unmodifiableList(mAudioBuffers);
}
/**
* Count of audio buffers contained in this segment.
*
* Note: audio buffers can be added to an audio segment throughout the segment's life-cycle by the audio producer.
*/
public int getAudioBufferCount()
{
return mAudioBuffers.size();
}
/**
* Gets the audio buffer at the specified index
* @param index of the buffer to fetch
* @return audio buffer
* @throws IllegalArgumentException if requested index is not valid
*/
public float[] getAudioBuffer(int index)
{
if(0 <= index && index < getAudioBufferCount())
{
return mAudioBuffers.get(index);
}
else
{
throw new IllegalArgumentException("Requested audio buffer at index [" + index + "] does not exist");
}
}
/**
* Indicates if this audio segment has one or more audio buffers
*/
public boolean hasAudio()
{
return !mAudioBuffers.isEmpty();
}
/**
* Removes all audio buffers and decrements the user count on each so that the audio buffer can be reclaimed.
*/
private void dispose()
{
mDisposing = true;
mAudioBuffers.clear();
mIdentifierCollection.clear();
mIdentifierUpdateNotificationBroadcaster.clear();
mLinkedAudioSegment = null;
}
/**
* Increments the consumer count to indicate that a consumer is currently processing this segment. When the
* consumer count returns to zero, this indicates that all consumers are finished with the audio segment and the
* resources can be reclaimed.
*
* Consumer count should only be increased by the producer of the audio segment, or if a consumer distributes the
* segment to additional consumers.
*/
public void incrementConsumerCount()
{
mConsumerCount.incrementAndGet();
}
/**
* Decrements the consumer count. Consumers of this audio segment should invoke this method to signal that they
* will no longer need this audio segment. When all consumers are finished with an audio segment, the audio
* segment resources will be reclaimed.
*/
public void decrementConsumerCount()
{
int count = mConsumerCount.decrementAndGet();
if(count <= 0)
{
dispose();
}
}
/**
* Adds an audio buffer to this segment. Note the producer of the audio buffer should increment the user count
* of the buffer prior to adding it to this segment. This segment will decrement the audio buffer user count once
* all consumers of this audio segment have de-registered via the decrementConsumerCount() method.
*
* @param audioBuffer to add to this segment
*/
public void addAudio(float[] audioBuffer)
{
if(audioBuffer == null)
{
throw new IllegalArgumentException("Can't add null audio buffer");
}
if(mDisposing)
{
throw new IllegalStateException("Can't add audio to an audio segment that is being disposed");
}
mAudioBuffers.add(audioBuffer);
mSampleCount += audioBuffer.length;
}
/**
* Adds a listener to receive identifier update notifications
*/
public void addIdentifierUpdateNotificationListener(Listener<IdentifierUpdateNotification> listener)
{
mIdentifierUpdateNotificationBroadcaster.addListener(listener);
}
/**
* Removes the identifier update listener.
*/
public void removeIdentifierUpdateNotificationListener(Listener<IdentifierUpdateNotification> listener)
{
mIdentifierUpdateNotificationBroadcaster.removeListener(listener);
}
/**
* Adds the identifier to this audio segment and updates record, priority and streaming properties.
*
* Note: identifiers pass to this method must be checked for timeslot match.
*/
public void addIdentifier(Identifier identifier)
{
mIdentifierCollection.update(identifier);
List<Alias> aliases = mAliasList.getAliases(identifier);
for(Alias alias: aliases)
{
if(alias.isRecordable())
{
mRecordAudio.set(true);
}
//Add all broadcast channels for the alias ... let the set handle duplication.
mBroadcastChannels.addAll(alias.getBroadcastChannels());
//Only assign a playback priority if it is lower priority than the current setting.
int playbackPriority = alias.getPlaybackPriority();
if(playbackPriority < mMonitorPriority.get())
{
mMonitorPriority.set(playbackPriority);
}
}
}
/**
* Indicates if this audio segment has been flagged as a duplicate audio call
*/
public boolean isDuplicate()
{
return mDuplicate.get();
}
/**
* Sets the duplicate audio call flag for this audio segment
* @param duplicate true if this is a duplicate audio segment
*/
public void setDuplicate(boolean duplicate)
{
mDuplicate.set(duplicate);
}
/**
* Processes identifier update notifications and updates audio segment properties using the associated aliases
* from the alias list.
*
* @param identifierUpdateNotification containing an identifier.
*/
@Override
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
{
//Only process add updates that match this timeslot
if(identifierUpdateNotification.getTimeslot() == getTimeslot())
{
if(identifierUpdateNotification.isAdd() || identifierUpdateNotification.isSilentAdd())
{
addIdentifier(identifierUpdateNotification.getIdentifier());
}
mIdentifierUpdateNotificationBroadcaster.broadcast(identifierUpdateNotification);
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Audio Segment\n Timeslot:").append(getTimeslot()).append("\n");
sb.append(" Start Time:").append(new Date(getStartTimestamp()));
sb.append(" Linked:").append(isLinked()).append("\n");
sb.append(" Recordable:").append(recordAudioProperty().get()).append("\n");
sb.append("Do Not Monitor:").append(isDoNotMonitor()).append("\n");
sb.append(" Stream To:").append(getBroadcastChannels()).append("\n");
sb.append(" Identifiers:\n").append(getIdentifierCollection()).append("\n");
return sb.toString();
}
}

View File

@@ -0,0 +1,52 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.AbstractReusableBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AudioSegmentBroadcaster<T extends AudioSegment> extends Broadcaster<T>
{
private final static Logger mLog = LoggerFactory.getLogger(AudioSegmentBroadcaster.class);
/**
* Increments the consumer count for the reusable complex buffer and then broadcasts the buffer to all registered
* listeners.
*
* The total consumer count is established and applied to the buffer prior to dispatching. If we were to simply
* increment the consumer count prior to sending to each consumer, there is a possibility that the consumer could
* immediately decrement the consumer count and prematurely signal that the buffer is ready for disposal before we
* send the buffer to all consumers.
*/
@Override
public void broadcast(T audioSegment)
{
for(Listener<T> listener : mListeners)
{
audioSegment.incrementConsumerCount();
listener.receive(audioSegment);
}
//Decrement consumer counter for this broadcaster
audioSegment.decrementConsumerCount();
}
}

View File

@@ -16,7 +16,6 @@
package io.github.dsheirer.audio;
import io.github.dsheirer.sample.ConversionUtils;
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,18 +31,17 @@ public class AudioUtils
/**
* Converts the audio packets into a byte array of 16-bit, little-endian audio samples
*/
public static byte[] convertTo16BitSamples(List<ReusableAudioPacket> audioPackets)
public static byte[] convertTo16BitSamples(List<float[]> buffers)
{
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try
{
for(ReusableAudioPacket audioPacket: audioPackets)
for(float[] audioBuffer: buffers)
{
//Converting from 32-bit floats to signed 16-bit samples
ByteBuffer buffer = ConversionUtils.convertToSigned16BitSamples(audioPacket.getAudioSamples());
ByteBuffer buffer = ConversionUtils.convertToSigned16BitSamples(audioBuffer);
stream.write(buffer.array());
audioPacket.decrementUserCount();
}
}
catch(IOException e)
@@ -53,4 +51,29 @@ public class AudioUtils
return stream.toByteArray();
}
/**
* Converts the audio packets into a byte array of 16-bit, little-endian audio samples
*/
public static byte[] convert(List<float[]> audioBuffers)
{
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try
{
for(float[] audioBuffer: audioBuffers)
{
//Converting from 32-bit floats to signed 16-bit samples
ByteBuffer buffer = ConversionUtils.convertToSigned16BitSamples(audioBuffer);
stream.write(buffer.array());
}
}
catch(IOException e)
{
mLog.error("Error writing converted PCM bytes to output stream");
}
return stream.toByteArray();
}
}

View File

@@ -0,0 +1,310 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.duplicate.DuplicateCallDetectionPreference;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Detects duplicate calls that occur within the same system. This detector is thread safe for the receive() method.
*
* Note: system in this context refers to the system name value that is used in channel configurations. All decoder
* channels must share the same system name for call duplication detection.
*/
public class DuplicateCallDetector implements Listener<AudioSegment>
{
private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallDetector.class);
private DuplicateCallDetectionPreference mDuplicateCallDetectionPreference;
private Map<String,SystemDuplicateCallDetector> mDetectorMap = new HashMap();
public DuplicateCallDetector(UserPreferences userPreferences)
{
mDuplicateCallDetectionPreference = userPreferences.getDuplicateCallDetectionPreference();
}
@Override
public void receive(AudioSegment audioSegment)
{
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionEnabled())
{
Identifier identifier = audioSegment.getIdentifierCollection()
.getIdentifier(IdentifierClass.CONFIGURATION, Form.SYSTEM, Role.ANY);
if(identifier instanceof SystemConfigurationIdentifier)
{
String system = ((SystemConfigurationIdentifier)identifier).getValue();
synchronized(mDetectorMap)
{
SystemDuplicateCallDetector detector = mDetectorMap.get(system);
if(detector == null)
{
detector = new SystemDuplicateCallDetector();
mDetectorMap.put(system, detector);
}
detector.add(audioSegment);
}
}
}
}
public class SystemDuplicateCallDetector
{
private LinkedTransferQueue<AudioSegment> mAudioSegmentQueue = new LinkedTransferQueue<>();
private List<AudioSegment> mAudioSegments = new ArrayList<>();
private AtomicBoolean mMonitoring = new AtomicBoolean();
private ScheduledFuture<?> mProcessorFuture;
public SystemDuplicateCallDetector()
{
}
public void add(AudioSegment audioSegment)
{
//Block on audio segment queue so that we don't interfere with monitoring shutdown
synchronized(mAudioSegmentQueue)
{
mAudioSegmentQueue.add(audioSegment);
startMonitoring();
}
}
private void startMonitoring()
{
if(mMonitoring.compareAndSet(false, true))
{
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(() -> process(),
0, 25, TimeUnit.MILLISECONDS);
}
}
private void stopMonitoring()
{
if(mMonitoring.compareAndSet(true, false))
{
if(mProcessorFuture != null)
{
mProcessorFuture.cancel(true);
}
}
}
/**
* Checks if either the TO or FROM integer identifiers for the audio segments are the same
* @param segment1 to compare
* @param segment2 to compare
* @return true if integer identifiers are the same
*/
private boolean isDuplicate(AudioSegment segment1, AudioSegment segment2)
{
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionByTalkgroupEnabled())
{
//Step 1 check for duplicate TO values
List<Identifier> to1 = segment1.getIdentifierCollection().getIdentifiers(Role.TO);
List<Identifier> to2 = segment2.getIdentifierCollection().getIdentifiers(Role.TO);
if(isDuplicate(to1, to2))
{
return true;
}
}
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionByRadioEnabled())
{
//Step 2 check for duplicate FROM values
List<Identifier> from1 = segment1.getIdentifierCollection().getIdentifiers(Role.FROM);
List<Identifier> from2 = segment2.getIdentifierCollection().getIdentifiers(Role.FROM);
return isDuplicate(from1, from2);
}
return false;
}
/**
* Checks both lists of identifiers to determine if there are talkgroups or radio identifiers that are
* the same in both lists. Note: talkgroup check compares both talkgroups and patch groups.
*
* @param identifiers1
* @param identifiers2
* @return
*/
private boolean isDuplicate(List<Identifier> identifiers1, List<Identifier> identifiers2)
{
for(Identifier identifier1: identifiers1)
{
if(identifier1 instanceof TalkgroupIdentifier)
{
int talkgroup1 = ((TalkgroupIdentifier)identifier1).getValue();
for(Identifier identifier2: identifiers2)
{
if(identifier2 instanceof TalkgroupIdentifier &&
((TalkgroupIdentifier)identifier2).getValue() == talkgroup1)
{
return true;
}
else if(identifier2 instanceof PatchGroupIdentifier &&
((PatchGroupIdentifier)identifier2).getValue().getPatchGroup().getValue() == talkgroup1)
{
return true;
}
}
}
else if(identifier1 instanceof PatchGroupIdentifier)
{
int talkgroup1 = ((PatchGroupIdentifier)identifier1).getValue().getPatchGroup().getValue();
for(Identifier identifier2: identifiers2)
{
if(identifier2 instanceof TalkgroupIdentifier &&
((TalkgroupIdentifier)identifier2).getValue() == talkgroup1)
{
return true;
}
else if(identifier2 instanceof PatchGroupIdentifier &&
((PatchGroupIdentifier)identifier2).getValue().getPatchGroup().getValue() == talkgroup1)
{
return true;
}
}
}
else if(identifier1 instanceof RadioIdentifier)
{
int radio1 = ((RadioIdentifier)identifier1).getValue();
for(Identifier identifier2: identifiers2)
{
if(identifier2 instanceof RadioIdentifier && ((RadioIdentifier)identifier2).getValue() == radio1)
{
return true;
}
}
}
}
return false;
}
/**
* Processes audio segments to detect duplicates
*/
private void process()
{
//Transfer in newly arrived audio segments
mAudioSegmentQueue.drainTo(mAudioSegments);
//Remove any completed audio segments.
mAudioSegments.removeIf(audioSegment -> {
boolean complete = audioSegment.completeProperty().get();
if(complete)
{
audioSegment.decrementConsumerCount();
}
return complete;
});
//Only check for duplicates if there is more than one call
if(mAudioSegments.size() > 1)
{
List<AudioSegment> duplicates = new ArrayList<>();
int currentIndex = 0;
while(currentIndex < mAudioSegments.size() - 1)
{
AudioSegment current = mAudioSegments.get(currentIndex);
if(!current.isDuplicate())
{
int checkIndex = currentIndex + 1;
while(checkIndex < mAudioSegments.size())
{
AudioSegment toCheck = mAudioSegments.get(checkIndex);
if(!toCheck.isDuplicate())
{
if(isDuplicate(current, toCheck))
{
toCheck.setDuplicate(true);
toCheck.decrementConsumerCount();
duplicates.add(toCheck);
}
}
checkIndex++;
}
}
currentIndex++;
}
mAudioSegments.removeAll(duplicates);
}
//Finally, if the audio segment queue is empty, shutdown montitoring until a new segment arrives
if(mAudioSegments.isEmpty())
{
//Block on the audio segment queue so that we can shutdown before any new segments are added, and
//allow the add(segment) to restart monitoring as soon as needed.
synchronized(mAudioSegmentQueue)
{
if(mAudioSegmentQueue.isEmpty())
{
try
{
stopMonitoring();
}
catch(Exception e)
{
mLog.error("Unexpected error during duplicate audio segment monitoring shutdown", e);
//Do nothing, we got interrupted
}
}
}
}
}
}
}

View File

@@ -1,9 +0,0 @@
package io.github.dsheirer.audio;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
public interface IAudioPacketListener
{
public Listener<ReusableAudioPacket> getAudioPacketListener();
}

View File

@@ -1,10 +0,0 @@
package io.github.dsheirer.audio;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
public interface IAudioPacketProvider
{
public void setAudioPacketListener( Listener<ReusableAudioPacket> listener );
public void removeAudioPacketListener();
}

View File

@@ -0,0 +1,33 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio;
import io.github.dsheirer.sample.Listener;
/**
* Interface for audio segment listener
*/
public interface IAudioSegmentListener
{
/**
* Get the audio segment listener
*/
Listener<AudioSegment> getAudioSegmentListener();
}

View File

@@ -0,0 +1,39 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio;
import io.github.dsheirer.sample.Listener;
/**
* Interface for audio segment provider
*/
public interface IAudioSegmentProvider
{
/**
* Registers the audio segment listener
* @param listener of audio segments
*/
void setAudioSegmentListener(Listener<AudioSegment> listener);
/**
* De-registers the audio segment listener
*/
void removeAudioSegmentListener();
}

View File

@@ -0,0 +1,177 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio.broadcast;
import io.github.dsheirer.sample.Listener;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base Audio Broadcaster class
*/
public abstract class AbstractAudioBroadcaster<T extends BroadcastConfiguration> implements Listener<AudioRecording>
{
private final static Logger mLog = LoggerFactory.getLogger(AbstractAudioBroadcaster.class);
private Listener<BroadcastEvent> mBroadcastEventListener;
private T mBroadcastConfiguration;
protected ObjectProperty<BroadcastState> mBroadcastState = new SimpleObjectProperty<>(BroadcastState.READY);
protected int mStreamedAudioCount = 0;
protected int mErrorAudioCount = 0;
protected int mAgedOffAudioCount = 0;
/**
* Constructs an instance
* @param broadcastConfiguration to use for this broadcaster
*/
public AbstractAudioBroadcaster(T broadcastConfiguration)
{
mBroadcastConfiguration = broadcastConfiguration;
}
/**
* Observable broadcast state property
*/
public ObjectProperty<BroadcastState> broadcastStateProperty()
{
return mBroadcastState;
}
/**
* Current state of the broadcastAudio connection
*/
public BroadcastState getBroadcastState()
{
return mBroadcastState.get();
}
/**
* Sets or changes the broadcast state
*/
public void setBroadcastState(BroadcastState broadcastState)
{
mBroadcastState.setValue(broadcastState);
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));
}
/**
* Starts the broadcaster
*/
public abstract void start();
/**
* Stops the broadcaster
*/
public abstract void stop();
/**
* Perform any shutdown related tasks.
*/
public abstract void dispose();
/**
* Access the configuration for the broadcaster
*/
public T getBroadcastConfiguration()
{
return mBroadcastConfiguration;
}
/**
* Registers the listener to receive broadcast events/state changes
*/
public void setListener(Listener<BroadcastEvent> listener)
{
mBroadcastEventListener = listener;
}
/**
* Removes the listener from receiving broadcast events/state changes
*/
public void removeListener()
{
mBroadcastEventListener = null;
}
/**
* Broadcasts the event to any registered listener
*/
protected void broadcast(BroadcastEvent event)
{
if(mBroadcastEventListener != null)
{
mBroadcastEventListener.receive(event);
}
}
/**
* Number of audio recordings streamed to remote server
*/
public int getStreamedAudioCount()
{
return mStreamedAudioCount;
}
/**
* Increments the streamed or uploaded audio count by one
*/
public void incrementStreamedAudioCount()
{
mStreamedAudioCount++;
}
/**
* Number of audio recordings that were removed for exceeding age limit
*/
public int getAgedOffAudioCount()
{
return mAgedOffAudioCount;
}
/**
* Increments the aged-off audio count by one
*/
public void incrementAgedOffAudioCount()
{
mAgedOffAudioCount++;
}
/**
* Number of audio recordings awaiting streaming or upload
*/
public abstract int getAudioQueueSize();
/**
* Total audio upload/stream error count
*/
public int getAudioErrorCount()
{
return mErrorAudioCount;
}
/**
* Increments the error audio count by one
*/
public void incrementErrorAudioCount()
{
mErrorAudioCount++;
}
}

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