Compare commits

...

231 Commits

Author SHA1 Message Date
Denny Sheirer
e4a85a4ed7 #1386 Updates version to 0.5.0 (final) (#1387)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-25 07:11:25 -05:00
Denny Sheirer
f61a415b43 #1383 Updates tuner editor to resolve sequencing issue where a recently enabled tuner was having its enabled state set to true and then enabling the tuner, which caused the tuner editor to mis-configure the tuner editor panel. (#1385)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-25 07:03:28 -05:00
Denny Sheirer
29ed3b4553 #1287 Disables event panel filtering until the feature is fully implemented with #1368. The current partial implementation is preventing valid events from displaying for protocols like DMR and likely others. (#1384)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-25 06:45:50 -05:00
Denny Sheirer
a87309e792 #1378 Resolves potential error where tuner shutdown (because of USB I/O error) hangs and causes application to run out of memory. This patch doesn't resolve the original issue with the USB I/O error, but it does ensure that the tuner shutdown which is happening on the USB processing thread, can complete in a timely fashion so that the application doesn't (potentially) crash from out of memory. (#1382)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-24 09:10:17 -05:00
Denny Sheirer
ddb5b5ef87 #1380 Improves Icecast streaming error handling and preserves streaming error messages for broadcast state to display in streaming status panel. Resolves issue with streaming status panel not showing icons and stream type. (#1381)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-24 07:48:04 -05:00
Denny Sheirer
7a58bf85b6 #1312 Resolves issue with radio reference editor attempting to access county info for a site that has county ID set to 0 (ie no county specified). (#1379)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-24 04:45:21 -05:00
Doug Hoffman
8707eb2c88 Correctly identify padded MP3 frames (#1375) 2022-12-23 06:12:44 -05:00
Denny Sheirer
08499ac73f #1237 Ensure MP3 input sample rate is supported by LAME encoder settings. (#1376)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-23 06:06:10 -05:00
Dennis Sheirer
70170ff70f Revert "Disable resampling if requested sample rate not supported"
This reverts commit 7de28519a1.
2022-12-18 08:29:35 -05:00
Denny Sheirer
bad5589c0d #1369 Updates to Version 0.5.0 Beta 7 (#1370)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-18 07:17:23 -05:00
Doug Hoffman
7de28519a1 Disable resampling if requested sample rate not supported 2022-12-18 04:57:21 -05:00
Doug Hoffman
bbd907514e Handle streaming audio as frames (#1244) 2022-12-18 04:46:07 -05:00
Denny Sheirer
4a68cd9407 #1364 Enhancements to resolve issue with tuner shutdown hang on application shutdown. (#1365)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-18 04:29:55 -05:00
Denny Sheirer
dc327a2855 #1362 Updates heterodyne channelizer with alternate sorted set implementation to resolve issue when determining if the tuner can tune a requested channel. (#1363)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-17 09:28:29 -05:00
Denny Sheirer
9af593a785 #1360 Resolves issue with Polyphase channelizer and decoded message timestamps for P25 and other protocols. (#1361)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-17 08:50:29 -05:00
Denny Sheirer
24f4c22cdd #1358 Resolves issue where decoded AMBE (DMR & P25-2) tone identifiers are not sent to the alias action manager. (#1359)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-17 07:35:16 -05:00
Denny Sheirer
bc462b9652 #1351 Restores semi-accurate timestamps of sample buffers to ensure P25 & DMR decoded messages have accurate timestamps and MBE recording voice frames have accurate timestamps. (#1357)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-17 06:39:35 -05:00
Denny Sheirer
047dec6da1 #1355 Updates logging libraries to latest version and updates logging libraries to latest version. (#1356)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-17 04:06:44 -05:00
Denny Sheirer
bf935555bb #1353 Resolves issue where polyphase channelizer would mis-tune channels after a period of time resulting in idle decoding channels. (#1354)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-17 03:54:50 -05:00
Dennis Sheirer
efe36b0682 Updates README 2022-12-03 09:29:20 -05:00
Denny Sheirer
a18d4498aa #1346 Updates radio reference library to version 15.1.9 (#1347)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-11-25 09:11:10 -05:00
Denny Sheirer
803dc2531c #1318 Resolves issue where bitstream recorder no longer works. Solution submitted by @apankowski. (#1345)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-11-24 06:16:44 -05:00
Denny Sheirer
1b062bcd06 #1343 Resolves issue where user-initiated FFT size change causes waterfall panel pixel array to be updated/resized while a memory image source repaint has been requested and the parameters become incongruent, causing an array index out of bounds error. Pushes the pixel array update operation onto the swing thread to ensure only a single thread is modifying the pixel map for rendering. (#1344)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-11-24 04:23:48 -05:00
Denny Sheirer
a24c3736f4 #1340 Updates tuner and frequency controllers to apply locking to ensure multi-threaded access to freuqency control planes doesn't allow center tuned frequency to become indeterminant which can result in channels being mistuned and stop decoding. (#1341)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-11-20 07:55:30 -05:00
Denny Sheirer
ac61b681e6 #1338 Updates logger to include date in log entries. Removes info logger when creating channel log files. (#1339)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-11-20 05:54:05 -05:00
Denny Sheirer
0866bfd555 #1336 Resolves issue with color setting changes not inheriting the correct translucency which causes the channel overlays in the spectral display to be solid color instead of semi-transparent. (#1337)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-11-20 05:43:28 -05:00
Denny Sheirer
79fed701d9 #1337 Updates build to use JDK19 for distribution images and updates all 3rd-party libraries to latest. Note: build system continues to be compatible with JDK 17 and 18, but Gradle 7.6 to build distribution images. Gradle 7.6 is not released yet, but RC3 is available. (#1335)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-11-20 05:36:41 -05:00
Denny Sheirer
48a93990e8 #1291 Updates DC removal processing across tuner sample streams to run on a periodic timer to reduce overall processing requirements. Updates build version to 0.5.0 beta 5 (#1292)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-07-23 08:14:31 -04:00
Denny Sheirer
414ba25d61 #1284 Resolves issue with inherent DC bias (-0.5) in RTL2832 sample conversion code. (#1285)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-07-16 08:58:09 -04:00
Shahar
f67f90e8b0 moved RecordingTuner ChannelizerType set to after the recording tuner was started. This way the sample rate is not 0. (#1279) 2022-07-11 04:34:17 -04:00
Dennis Sheirer
5751c70212 Updates to release versio 0.5.0 2022-07-11 04:21:03 -04:00
Denny Sheirer
4e15c426a8 #1253 Updates USB tuner controller to handle LibUsb bug where on resubmitting a completed transfer LibUsb tells us that the transfer is already submitted. This is happening on some Window systems. (#1263)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-24 04:11:26 -04:00
Denny Sheirer
ceda189260 #1179 Resolves issue with JMBE Creator running on OSX. (#1262)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-24 03:57:33 -04:00
Denny Sheirer
fce0ae33e7 #1181 Updates libusb osx aarch64 driver library and gradle build. (#1261)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-23 04:06:42 -04:00
Denny Sheirer
fc5c6ef841 #1257 restores recording tuner feature. sdrtrunk now supports multiple recording tuners. Recordings are automatically loaded/restored on application start and each recording tuner has to be manually enabled for use. (#1258)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-22 08:24:42 -04:00
Denny Sheirer
d0033578d5 #1181 Adds logging for mac users to install libusb if sdrtrunk can't load library. (#1251)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-15 07:05:53 -04:00
Dennis Sheirer
670ec3a097 Adds debug logging to USB tuner controller. 2022-05-15 06:37:25 -04:00
Denny Sheirer
8bfb1d5470 #1243 Updates USB tuner error handling to prevent stack overflow when tuner error occurs while streaming. (#1247)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-09 07:16:35 -04:00
Denny Sheirer
46a916a945 #1245 Updates spectral display show tuner options to allow new display without changing disabled state of primary display. (#1246)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-08 05:01:52 -04:00
Denny Sheirer
459e77390a #1241 Adds user preference to enable/disable audio normalization prior to MP3 encoding/recording. (#1242)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-07 07:11:40 -04:00
Doug Hoffman
f4ab46b79d Prefer talkgroup ID over radio ID for Calls (#1227) 2022-05-07 06:51:27 -04:00
Denny Sheirer
3c58e98788 #1239 Resolves issue where a user can't view the spectrum of a tuner when the spectral display is disabled. Tuner editor button overrides the property setting so that the view completes correctly. (#1240)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-07 06:36:42 -04:00
Doug Hoffman
36de81248c Don't show tuner on spectral display while disabled (#1236) 2022-05-07 06:30:11 -04:00
Dennis Sheirer
925353d515 Bump Version 0.5.0 Beta 3, Gradle 7.4.2, Embedded JDK 18.0.1 2022-05-04 14:51:27 -04:00
Denny Sheirer
7c32897321 #1229 Resolves issue with correctly identifying the port number for USB tuners that is causing some tuners not to be recognized and also generating udev errors when sdrtrunk attempts to start an already started device because it was incorrectly identified. (#1230)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-03 04:03:18 -04:00
Doug Hoffman
cbeda19d27 Generate audio metadata with correct to/from (#1215)
#1215 Generate audio metadata with correct to/from, preferring patch group, then talkgroup then other, for TO talkgroups.
2022-05-01 06:06:31 -04:00
Doug Hoffman
8148ca48af Normalize audio before recording (#1213)
#1213 Normalize audio before recording
2022-05-01 05:57:32 -04:00
VolFFProCoder
adcb55ffbc Add filter button to messages (#1098)
Adds filtering to channel decoded events panel.
2022-04-30 05:44:57 -04:00
Andrzej Pańkowski
1152b13220 Merge pull request #1177
* Fixes message filtering that was not being initialized.  Fixes minor spotted code issues
2022-04-30 04:25:33 -04:00
Doug Hoffman
166383e1ed Make Broadcastify Calls scheduled test configurable (#1218)
* Broadcastify Calls periodic keep-alive to avoid showing offline status
2022-04-30 04:04:10 -04:00
Denny Sheirer
0d5d1b20e3 #1223 Resolves NPE when Airspy and HackRF tuners are re-enabled after startup. Slight tweaks to TunerManager to change when discovered USB device startup takes place to potentially address intermittent fail on start for some tuners that users are seeing. Resolves issue where a started device was not unreferenced in LibUsb on shutdown. (#1224)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-04-29 08:00:18 -04:00
Denny Sheirer
77ea43f61a #1221 resolves null pointer exception in tuner editor when attempting to restart an error tuner. (#1222)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-04-28 04:40:52 -04:00
Denny Sheirer
f988344222 #1219 resolves issues with NPE and restarting an error tuner. (#1220)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-04-26 04:02:36 -04:00
Doug Hoffman
60489e1fef (icecast) Add support for inline metadata (#1212) 2022-04-25 04:38:54 -04:00
Doug Hoffman
ac8aa17a80 Check in with Broadcastify Calls to stay online while otherwise idle (#1214) 2022-04-24 05:15:34 -04:00
Denny Sheirer
25a42126df #1210 Resolves issue of tuner instances can't be garbage collected due to retained references. Updates several of the tuner controller and tuner editor classes to add/invoke dispose() methods to clear retained instance references. (#1216)
Co-authored-by: Denny Sheirer <sheirerd@ainfosec.com>
2022-04-24 04:56:10 -04:00
Denny Sheirer
1b90e0b50c #1209 updates tuner manager to properly deref usb devices on discovery. Updates tuner editors to separate enable/disable from tuner error state. Updates settings manager to disable auto-fail when loading xml file when unknown tags are encountered so that users' existing configs don't fail on startup and erase their existing settings. (#1211)
Co-authored-by: Denny Sheirer <sheirerd@ainfosec.com>
2022-04-23 06:05:12 -04:00
Denny Sheirer
7f5ad7eb20 Update README.md 2022-04-19 07:03:19 -04:00
Denny Sheirer
0e6713b6e2 Update README.md 2022-04-19 07:02:32 -04:00
Denny Sheirer
96d3996bec #1190 Tuner management redesign. Updates all tuners with direct threaded support. Incorporates USB Hotplug support. Adds black/white listing of tuners. Updates all tuner editors to have a common look and feel. Changes location of tuner configuration file and changes format to JSON. (#1208)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-04-18 07:30:58 -04:00
Denny Sheirer
0fb148d31e #1194 Resolves issue with audio stream bit rate scheduling that was causing stream disconnects for data rate under flows. (#1195)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-26 05:48:08 -04:00
Denny Sheirer
cebca28b19 Patch from @ImagoTrigger to implement headless operation: 9e4d2adfa9..c597d1967b (#1191)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-25 04:19:38 -04:00
Denny Sheirer
af8cc0255e #1186 removes error logging that indicates that a channel cannot be stopped because it doesn't exist. This happens when a second successive attempt to stop a channel fails to find the channel because the first attempt has already removed the channel from the list. (#1187)
Co-authored-by: Denny Sheirer <sheirerd@ainfosec.com>
2022-03-21 19:09:49 -04:00
Dennis Sheirer
775ae00861 Update version to 0.5.0 beta 2 2022-03-21 05:48:16 -04:00
Denny Sheirer
1b80f5f1b3 #1178 removes synchronization on channel processing manager's source event handler to enable more fine-grained channel start/stop event processing. This was causing the channel start and stop events to occur sequentially. Adds stream processing with coordinated heartbeat. (#1185)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-21 05:47:20 -04:00
Dennis Sheirer
f9a7bbeddf Update version to 0.5.0 beta 1 2022-03-18 11:53:03 -04:00
Denny Sheirer
58a0264565 #1175 increases scheduled thread pool size from 2 to 4 threads. (#1176)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-18 11:45:24 -04:00
Denny Sheirer
6d2af95e9a #1173 updates DMR decoder to support DCDM (#1174)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-18 07:00:19 -04:00
Denny Sheirer
bf46b19dc9 #1169 Resolves issue where P25 C4FM decoder can request an interpolated sample value that is less than 0. Not sure why or what is causing this but this change guards against an ArrayIndexOutOfBounds exception when it does happen. (#1172)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-16 05:08:31 -04:00
Denny Sheirer
7749fd10f1 #1170 resolves issue with MP3 encoded audio raspy. Adds auto-clipping of PCM audio prior to encoding and adds 4x new 32-bit input sample rate selections to choose from, in addition to original 4x 16-bit input formats. (#1171)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-16 05:00:07 -04:00
Denny Sheirer
90bc321aca #1165 reverts changes to gradle runtimeZip task behavior. Enables 3 options for building target OS and CPU images: 1)build using local JDK, 2)build all targets using downloaded JDKs, or 3)download all JDKs and build all targets. (#1166)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-13 07:47:32 -04:00
Dennis Sheirer
bad4fae1b8 Updates README 2022-03-12 09:09:09 -05:00
Dennis Sheirer
c3fc435fdc Updates github action to use liberica jdk 17 plus fx libraries 2022-03-12 08:55:46 -05:00
JaredLThompson
2ab0c78728 Add error message for R828D Tuner (#1140) 2022-03-12 08:49:40 -05:00
hans109h
187989f6b8 Update README.md (#1158)
Corrects link to Bellsoft JDK download rather than duplicating the Azul link.
2022-03-12 08:48:09 -05:00
Steve Myers
3b83412bcb Update gradle.yml (#1143) 2022-03-12 08:46:25 -05:00
Denny Sheirer
968ea458da #1163 resolves issue with airspy unpacked calibrations hanging. Updates each test to check elapsed timer on a more granular scale on the theory that the SIMD vector implementation is horribly slow on some systems. (#1164)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-12 08:33:55 -05:00
Denny Sheirer
a9f4fbbdd4 #1160 OSX aarch64 (M1) libusb support and JDK auto-download for target OS images. (#1161)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-12 07:43:58 -05:00
Dennis Sheirer
6b3b45e9af Cleanup. 2022-03-12 03:39:16 -05:00
Denny Sheirer
2355dfce07 #939 Adds MP3 encoder preferences to the User Preferences dialog and provides user control over sample rate and MP3 encoder settings. MP3 preferences are global and affect both recording and streaming. (#1159)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-10 04:17:35 -05:00
Dennis Sheirer
0e8c9591e4 #1154 updates JMBE creator to work correctly with arm32 and aarch64 architectures. 2022-03-06 07:30:48 -05:00
Denny Sheirer
54e4771f36 #1154 updates JMBE creator to work correctly with arm32 and aarch64 architectures. (#1155)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-06 07:00:49 -05:00
Denny Sheirer
b2eb245feb #1152 Resolves issue where patch groups were not being properly formatted when uploading to broadcastify calls api. (#1153)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-05 05:56:16 -05:00
Denny Sheirer
e826314d06 #1150 resolves issue with inability to create HTTPS SSL connections. (#1151)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-05 03:32:07 -05:00
Denny Sheirer
0eec2e7ab8 Updates complex decimation filters to use either scalar or vector optimal implementation. (#1148)
Updates HackRF native byte buffers with automatic DC removal.
Creates new half-band channel source to replace the cic-based channel source in the heterodyne channelizer.  Resolves issue in RTL native buffer processing.  Creates a CACHED thread pool and reduces the number of threads for the SCHEDULED thread pool to 2.  Changes each of the channel source implementations to use a dedicated thread (Dispatcher class) instead of the scheduled thread pool, to reduce the amount of buffered/retained samples objects in memory. Lots of other small changes and tweaks.
Removes last of Reusable buffer code.  Updates DFTProcessor to use a single thread scheduled executor.  Updates Polyphase channelizer to use threads instead of an executor.  Removes a number of unused classes.
Commenting out window calibration.  Scalar is faster in both implementations.
Adds vector and scalar implementations for windows.  Repackages window and window type classes.
WIP adds vector implementations for Airspy non-interleaved buffer iterator and calibration plugins for both interleaved and non-interleaved buffer iterators.
WIP adds vector implementations for Airspy interleaved buffer iterator.
Work on developing scalar and vector implementations of airspy sample conversion and airspy native buffer processing.
Introduces new INativeBuffer framework where tuner raw samples are retained in their native (compressed) format until when they're needed and they are converted on-demand into smaller complex sample buffers to aid garbage collection.
Removes all usage of ReusableComplexBuffers and ReusableChannelREsultsBuffers.  Updates polyphase channelizer to be more efficient in output channel processing.
Updates ViterbiDecoder to change Map usage to array for memory efficiency.
Updates channel processors to used scalar implementation for gain and mixer, until the polyphase channel results can be fixed to be a multiple of the SIMD lane width.
Resolves final changes for the conversion from interleaved sample arrays to complexsamples buffers for distributing baseband samples to channels and processing chains.
Updates tuner channel sources and all decoders to receive new complex samples buffers, removing the previous reusable complex buffers as the default baseband samples buffer object.
Still to resolve is the ComplexMixer implementations.  Needs validation and a calibration implementation with an update to the complex mixer factory.
Updates the DMR decoder to use optimal (scalar vs vector) implementations of filter and AGC controls.
Updates the AM demodulator for separated I&Q float sample processing.
Code review and testing changes.
updates build script for runtime configuration.
Integrates calibration manager with main sdrtrunk application.  Updates NBFMDecoder to use new optimized FIR and Decimation filter options.
Updates real half-band decimation filters to select the optimal (scalar vs vector) implementation.
Adds calibration manager plugins.
Adds calibration manager and plugins to determine the optimal (scalar vs vector) implementation type to use for various classes of dsp components.
Adds vector squelching FM demodulator and cleans up the IIR filter and power squelch to use all floats.
Adds vector implementation of FM demodulator.  This introduces a new interface that accepts i/q samples as two separate float arrays, versus an interleaved array, which enables more efficient processing.  This new complex sample interface will be used across the codebase eventually.
Adds ComplexOscillator and a SIMD version VectorComplexOscillator.  Deprecates the LowPhaseNoiseOscillator because the ComplexOscillator is about 35% faster.  The vectorized version is about 43% faster when SIMD lane width is 8x.
Adds new vector complex filters and new real oscillators.  Note: the RealOscillator using straight Java math sin() function is 3x slower than the existing legacy implementation.  Will replace this with a complex oscillator that only uses the real part.
Updates ComplexFIRFilter to be 50% more efficient.
Adds 23 tap filter implementations.  Will not add 63-tap implementations because they are slower than the default vector implementation.
Adds 15-tap customized vector half band filters.
Adds initial custom complex vector half-band decimation filters for 11-tap and 15-tap decimation filters..
Adds vector complex half band filter implementations.  Note: performance of the generic 64-bit 2-lane filter is horrible and is not included.
Optimizes complex half-band filter to be 27% more efficient.
Implements vectorized real half-band filter implementations.
Implements a new FIR filter class that processes sample buffers instead of single sample at a time.
Refactored all of the calibratons to be time-duration based.  Testing on windows i7 gen 3 CPU showed that it took 1000x longer for one of the calibrations to run, versus my AMD 5800x.
Changed gradle jvm args so that the windows plaf export is always applied.
Updates gradle build script to ensure incubator module is included and to cleanup javafx library management for building runtime images.
Testing tweaks and changes.
Adds logging limiter to dispatcher for buffer overflows
Adds User Preferences editor for performing Vector/SIMD calibrations and new startup dialog prompting the user to perform calibrations.
Updates calibration manager and all calibration implementations to add warm-up and test phases and to reduce the overall time it takes to calibrate a system.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-04 08:45:05 -05:00
Denny Sheirer
5e9c239ea3 #1133 Resolves decimation rate too small issue for NBFM decoder. (#1134)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-01-10 04:26:57 -05:00
Denny Sheirer
1fd9b0fbcb Resolves #1130 - restores real time PPM measured frequency error broadcasts from DMR, P25P1 and P2 decoders. (#1131)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-01-02 09:01:32 -05:00
Denny Sheirer
8413343e76 Resolves 1122. Adds decimation to NBFMDecoder so that channel bandwidth is reduced to min sample rate needed. This reduces the impact on channel power measurement from adjacent channels. (#1129)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-01-02 08:35:19 -05:00
Denny Sheirer
2a0427c559 Adds support for power-of-2 decimation for both real and complex sample buffers. Creates new class to analyze the filter attenuation rates using a variety of filter lengths and window options to enable selecting the optimal combination of cascaded power-of-2 half band decimation filters. (#1127)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-12-31 10:20:15 -05:00
Denny Sheirer
5d9d232585 #1110 Updates DMR MBE frame recorder to correctly handle calls on separate timeslots and to fully capture identifiers associated with calls. (#1120)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-28 07:12:40 -05:00
Denny Sheirer
f0f0329f9b #1118 LRRP Latitude Hemisphere parsing. Updates parser to treat latitude value as a 1-bit hemisphere flag followed by a 31-bit integer. Previously, this field was parsed as a 32-bit two's complement field. (#1119)
There may be two separate formats that are used for LRRP position fields where one treats the value as a two's complement field and the other as a 1-bit hemisphere indicator followed by a 31-bit positive integer.  If so, we'll need to expose a user-selectable flag to toggle one format or the other.

For now, this change won't impact any Northern hemisphere users.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-28 06:14:54 -05:00
Denny Sheirer
2a45849e6d #1115 Adds more parsing updates for LRRP packets. (#1116)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-27 13:42:04 -05:00
Denny Sheirer
aa89079e28 #1113 Updates LRRP protocol to correctly identify geo-fence positions and to add support for packet type identification. Corrects issue with N/S and E/W hemisphere calculations. (#1114)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-25 05:58:42 -05:00
Dennis Sheirer
31b9ee5910 #1014 Adds AMBE voice frame recording support. 2021-11-21 04:57:39 -05:00
Denny Sheirer
6a54fe101c #1014 Adds AMBE voice frame recording support. (#1108)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-21 04:56:58 -05:00
Denny Sheirer
87b3d34024 #1106 Adds half-band filter create to filter factory (#1107)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-21 04:17:54 -05:00
Denny Sheirer
b8bbe9264a #1104 Resolves issue where all assembled DMR packet sequences are occurring only on timeslot 2 (#1105)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-20 06:04:49 -05:00
Dennis Sheirer
0c7dd192b0 Disabled failing tests until test can be updated to work across all platforms. 2021-11-18 04:30:43 -05:00
Denny Sheirer
8b487d79e4 Adds Comparable to identifiers that are used in TreeSets. (#1103)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-15 04:20:08 -05:00
Justin Bull
cb83a8e870 Properly format and safely escape to CSV in DecodeEventLogger (#1092)
* Add failing test for toCSV()

* Add missing " to timeslot value

* Add failing test for quotes and commas in details

* Separate comma and quote tests

* Escape toCSV cells using utility

* Use CSVFormatter instead of escape utils

Allows us to control exactly how we want to format a CSV-valid row.

* Never have a null cell

Instead of using a stream map, opting for an explicit handling so the logger can explode when an unexpected null shows up. Prevents the situation where things are mysteriously blank, forcing developers to handle known null scenarios.
2021-11-15 03:53:52 -05:00
Denny Sheirer
0bc11a07cf Update README.md 2021-11-14 07:59:26 -05:00
Dennis Sheirer
560cbad5d7 Code cleanup. 2021-11-14 07:57:14 -05:00
Noah Lynch
87e9bf8749 Don't sort Alias TableView while bulk changes are applied (#1039)
* Don't sort backing SortedList while AliasBulkEditor is applying changes.

SortedList takes too long to sort and can cause large slowdowns and UI
freezes while many changes to Aliases are applied at once via the
AliasBulkEditor.
2021-11-14 07:47:27 -05:00
Wojciech
770f58c668 Show geo locations from DMR on map (#1001)
Thank you!
2021-11-14 07:36:34 -05:00
Denny Sheirer
4b806ecfde #36 NBFM squelch control. Updates NBFM decoder to incorporate squelch control. Squelch is a configuration item and also available to adjust at runtime with new 'Channel' tab in Now Playing window. All decoders were updated to produce continuous channel power (dB) measurements that are displayed in the Channel tab's new Power Meter. (#1100)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-14 06:59:58 -05:00
Denny Sheirer
7303e06943 Resolves #1094. (#1097)
Updates gradle build system to address classes that are now hidden in Java 17 and no longer exported from the base modules.

Updated build system to specifically call out JavaFX OS-specific libraries and target these to each of the OS-specific build products.

Removes jcenter as a maven repo since it's now deprecated.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-06 09:47:27 -04:00
Denny Sheirer
846b249fb3 Update README.md 2021-11-06 09:05:23 -04:00
Denny Sheirer
67f23494e2 Update README.md 2021-11-02 04:39:31 -04:00
Denny Sheirer
d7efb627d7 Update README.md
Updated JMBE audio library instructions.
2021-11-02 04:38:42 -04:00
Denny Sheirer
08bc0b9963 Update README.md
Updated readme with Java 17 requirement
2021-11-02 04:30:49 -04:00
Dennis Sheirer
d1ed46860c Updates gradle wrapper to use version 7.2 and updates build script for 7.2 compatibility. 2021-10-30 10:00:19 -04:00
Dennis Sheirer
38f694d262 Updates gradle wrapper to use version 7.2 and updates build script for 7.2 compatibility. 2021-10-30 07:52:27 -04:00
Denny Sheirer
75ef3217ac #1021 Corrects previous implementation of dot-product filtering using Math.fma() (#1025)
Co-authored-by: Denny <denny@denny-desktop>
2020-12-29 03:50:24 -05:00
Denny Sheirer
2847dff64a #1021 Updates FIR filter to use the java Math.fma floating point multiply and accumulate instruction for more efficient processing. (#1022)
Co-authored-by: Denny <denny@denny-desktop>
2020-12-27 06:48:37 -05:00
Denny Sheirer
f0d9ef99ea Resolves multi-threading issue in buffer assembler where sample listener is nullified while buffer processing is still in process. (#1005)
Co-authored-by: Denny <denny@denny-desktop>
2020-12-01 04:06:03 -05:00
Robert Scullin
67702096d7 RR User Interface Improvements, including Premium notices (#992)
* RR UX Improvements, including Premium notices

* Fixing Error handling bug in LoginDialog
2020-11-02 03:40:30 -05:00
Denny
88f124af9d Updates build to 0.5.0 Alpha 6. 2020-10-26 04:27:20 -04:00
Denny Sheirer
d34460c4e3 #986 Resolves issue with HackRF tuner not loading correctly. (#987)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-26 04:26:29 -04:00
Denny Sheirer
f4f3db7436 #973 Adds concurrent locking to primary tuner controller methods. (#983)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-25 08:59:06 -04:00
Denny
3ebfa90ca1 Updates build to 0.5.0 Alpha 5 2020-10-25 07:51:13 -04:00
Denny Sheirer
733fdd0d7a #981 Sets channel short title as ignore for serialization. (#982)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-25 07:49:12 -04:00
Noah Lynch
09893605eb Only update ViewByIdentifierEditor's list when the editor is selected (#944)
* Add type parameters to TableColumns to fix compiler warnings

* Only update AliasViewByIdentifierEditor list when the editor is selected

This prevents the editor's list from being rebuilt in the background as
aliases are updated.  This caused a massive slowdown when editing many
aliases with the AliasBulkEditor.

Bulk editing 1000 aliases apply time reduced from over a minute to under
5 seconds.
2020-10-25 06:54:04 -04:00
Robert Scullin
57468ef932 Fixes #740 - Adds Encrypted state to the Status column for P25 Phase 1 (#975) 2020-10-25 06:46:02 -04:00
Robert Scullin
c6afb1c133 Fixes #815 - Change usable bandwidth percentage based on Airspy samplerate (#976) 2020-10-25 06:33:25 -04:00
Denny Sheirer
05c0b51fde #979 Updates DMR Tier III channel number issues and provides user option to ignore CRC checksums for DMR decoder with RAS-enabled systems. Adds support for Tier III traffic channel system parameters SLCO message. (#980)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-25 06:09:57 -04:00
Denny Sheirer
de8cfa24f9 #959 Updates DMR decode event tracking and Con+ channel grant management. (#978)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-25 04:51:36 -04:00
Denny Sheirer
fe714c69a7 #956 Resolves issue with null pointer in identifier collection on channel start. (#977)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-24 04:43:15 -04:00
Denny Sheirer
ac219cf2ba #969 Resolves issue where P25P2 NAC value is saved incorrectly by configuration editor. (#970)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-17 04:47:48 -04:00
Denny Sheirer
77b1102100 #942 Adds channel number to neighbor report logging. (#965)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-16 07:02:29 -04:00
Denny Sheirer
5117e6cbe8 #961 Resolves issue where invoking channel actions from Swing thread causes error on JavaFX GUI thread. Removes the Start/Disable/Delete commands from the Swing menu options and replaces with a single View/Edit call to the JavaFX channel editor. Although this reduces ease/functionality, it sidesteps code complications until the main GUI can be recreated in JavaFX. (#964)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-16 06:38:05 -04:00
Denny Sheirer
61c4e13c3e #954 Channel rotation delay default value too small for MPT1327 and P25 channels. (#963)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-16 06:04:17 -04:00
Denny Sheirer
f1190b7be3 #960 Adds handling to broadcastify streaming to not log an error when broadcastify tells us to skip an audio upload. This is not an error because another broadcaster has already uploaded the audio segment. (#962)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-16 05:19:50 -04:00
Denny Sheirer
49e7087b9b #952 Resolves issue with concurrent access to processing chain map. (#953)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-12 09:33:49 -04:00
Denny Sheirer
c397b5a601 #950 Resolves issue where message and event panels were aggregating across all selected now playing channels. (#951)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-12 09:26:59 -04:00
Denny Sheirer
c809dcc609 #948 Updates build to 0.5.0 Alpha 4 and updates gradle script to embed JDK15 as the Java runtime. (#949)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-12 09:18:53 -04:00
Denny Sheirer
7eec82deee #6 DMR Decoder (#947)
* #6 DMR decoder feature - work in progress

* #6 DMR Decoder.

* #6 DMR Decoder.  Code review updates.

Co-authored-by: Zhenyu Mao <maozhenyu@foxmail.com>
Co-authored-by: Denny <denny@denny-desktop>
2020-10-12 04:57:59 -04:00
Denny Sheirer
b061db1189 #903 updates rrapi library to resolve radio reference web service deserialization issue. (#923)
Co-authored-by: Denny <denny@denny-desktop>
2020-09-04 04:46:08 -04:00
Denny Sheirer
8715dd2d6b #908 Add AMBE tone info to audio recording file names. (#921)
Co-authored-by: Denny <denny@denny-desktop>
2020-09-03 05:02:52 -04:00
Denny Sheirer
63b0aaaa7f Resolves #909 MPT1327 call events don't show talkgroup aliases. (#920)
Co-authored-by: Denny <denny@denny-desktop>
2020-09-03 04:21:28 -04:00
Denny Sheirer
d20dd1af8c Resolves #910 MPT-1327 talkgroup editor tooltip. (#919)
Co-authored-by: Denny <denny@denny-desktop>
2020-09-03 04:14:24 -04:00
Denny Sheirer
e1f32d3d0b Resolves #905. Adds try/catch block to duplicate call detection processing block to ensure that any error generated during duplicate call processing doesn't kill the processing thread and cause a queue-backup memory leak. (#918)
Co-authored-by: Denny <denny@denny-desktop>
2020-09-03 04:07:30 -04:00
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
Denny Sheirer
2e942113c6 Resolves #644 NoClassDefFound error when Icecast metadata updater runs. Updates build to version 0.4.0 Beta 4 (#645) 2020-01-12 04:57:38 -05:00
1608 changed files with 132318 additions and 55438 deletions

34
.github/workflows/gradle.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
name: Java CI with Gradle
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
java-version: '17'
distribution: 'liberica'
java-package: 'jdk+fx'
- name: Build with Gradle
uses: gradle/gradle-build-action@937999e9cc2425eddc7fd62d1053baf041147db7
with:
arguments: clean build

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

@@ -2,8 +2,11 @@
<dictionary name="denny">
<words>
<w>bahroo</w>
<w>basebanded</w>
<w>broadcastify</w>
<w>correlator</w>
<w>deinterleave</w>
<w>descrambled</w>
<w>dinf</w>
<w>esnid</w>
<w>fleetsync</w>
@@ -23,6 +26,7 @@
<w>shoutcast</w>
<w>streamable</w>
<w>talkgroup</w>
<w>talkgroups</w>
<w>tgid</w>
<w>ultravox</w>
<w>wacn</w>

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>

6
.idea/misc.xml generated
View File

@@ -1,6 +1,10 @@
<?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="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="liberica-19.0.1.fx" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

12
.idea/modules.xml generated
View File

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

@@ -1,66 +1,24 @@
![Gradle Build](https://github.com/dsheirer/sdrtrunk/actions/workflows/gradle.yml/badge.svg)
# sdrtrunk
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted_V0.3.0)
* [User's Manual Version 0.3.0 and 0.4.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual_V0.3.0)
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/Getting-Started)
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/User-Manual)
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
* [Support Group](https://groups.google.com/forum/#!forum/sdrtrunk)
* [Discord Support Channel](https://discord.gg/HJQaKYE)
* [Support](https://github.com/DSheirer/sdrtrunk/wiki/Support)
![sdrtrunk Application Overview - Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/v0.3/images/ApplicationOverview_V0.3.0.png)
**Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot
![sdrtrunk Application](https://github.com/DSheirer/sdrtrunk/wiki/images/sdrtrunk.png)
**Figure 1:** sdrtrunk Application Screenshot
# End User Instructions:
If you simply want to download and run the program, please follow these instructions.
## Download the latest sdrtrunk release for your operating system
## Download the Latest Release
All release versions of sdrtrunk are available from the [releases](https://github.com/DSheirer/sdrtrunk/releases) tab.
* **(alpha)** These versions are under development feature previews and likely to contain bugs and unexpected behavior.
* **(beta)** These versions are currently being tested for bugs and functionality prior to final release.
* **(final)** These versions have been tested and are the current release version.
## Unzip the release
Use 7-zip or any zip utility to unzip the release file
## Start the application
Once unzipped, open a command prompt to where you unzipped the release. Change to the **/bin** directory and use the launch script to start the application:
* **Windows** sdr-trunk.bat
* **Linux/OSX** ./sdr-trunk
## Optional - P25 Audio
If you're using sdrtrunk with a P25 trunked radio system, the [JMBE](https://github.com/DSheirer/sdrtrunk/wiki/JMBE) wiki page contains instructions for downloading the JMBE audio library source code and compiling the JMBE library. Once you have compiled the library, launch the sdrtrunk application. From the menu bar, choose **View >> Preferences**. In the **JMBE Audio Codec** section, update the path to where your compiled JMBE library is located. Any channels that are started after you set the path will be able to produce P25 audio.
## Minimum System Requirements
* **Operating System:** Windows (32 or 64-bit), Linux (32 or 64-bit) or Mac/Linux (64-bit, 10.14 or higher)
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac/Linux (64-bit, 10.14 or higher)
* **CPU:** 4-core
* **RAM:** 8GB or more (preferred). Depending on usage, 4GB may be sufficient.
# Developer Instructions:
If you're interested in modifying and/or compiling the source code, please follow these instructions to use gradle to compile the code.
## Build the project
sdrtrunk uses the gradle build system. This requires OpenJDK 11 or higher installed on your local compuber. Use the gradle wrapper to build the source code:
### Linux
```
./gradlew clean build
```
### Windows
```
gradlew.bat clean build
```
The **/build/distributions** folder will contain the zip file of the compiled program. Unzip it and launch the program from the scripts in the **/bin** directory.
## Development
All dependencies/versions are controlled from build.gradle.
To change the new release version tag of artifact - change property:
```
version = '0.4.0'
```

View File

@@ -1,89 +1,121 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
plugins {
id 'application'
id 'java'
id 'idea'
id 'org.openjfx.javafxplugin' version '0.0.6'
id 'org.beryx.runtime' version '1.8.0'
id 'org.beryx.runtime' version '1.12.7'
}
repositories {
jcenter()
mavenCentral()
maven { url "https://jitpack.io" }
}
version = '0.4.0-beta.3'
sourceCompatibility = '11'
version = '0.5.0'
sourceCompatibility = '17'
sourceSets {
main.java.srcDirs 'src/main'
test.java.srcDirs 'src/test'
}
test {
useJUnitPlatform()
}
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'
// JUnit Tests
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.assertj:assertj-core:3.23.1'
testImplementation('org.junit.jupiter:junit-jupiter-api:5.9.0')
testImplementation 'org.mockito:mockito-core:4.8.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
//Jitpack imports
implementation 'com.github.dnault:libresample4j:master'
implementation 'com.github.DSheirer.jmbe:jmbe-api:1.0.0'
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
//mavenCentral/jcenter imports
implementation 'ch.qos.logback:logback-classic:1.4.5'
implementation 'ch.qos.logback:logback-core:1.4.5'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.0'
implementation 'com.fazecast:jSerialComm:2.9.3'
implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
implementation 'com.github.jiconfont:jiconfont-javafx:1.0.0'
implementation 'com.github.jiconfont:jiconfont-swing:1.0.1'
implementation 'com.github.wendykierp:JTransforms:3.1'
implementation 'com.google.code.gson:gson:2.10'
implementation 'com.google.guava:guava:31.1-jre'
implementation 'com.jidesoft:jide-oss:3.6.18'
implementation 'com.miglayout:miglayout-swing:11.0'
implementation 'com.mpatric:mp3agic:0.9.1'
implementation 'commons-io:commons-io:2.11.0'
implementation 'eu.hansolo:charts:1.0.5'
implementation 'io.github.dsheirer:radio-reference-api:15.1.9'
implementation 'javax.usb:usb-api:1.0.2'
implementation 'net.coderazzi:tablefilter-swing:5.5.4'
implementation 'org.apache.commons:commons-compress:1.21'
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'org.apache.commons:commons-math3:3.6.1'
implementation 'org.apache.commons:commons-csv:1.9.0'
implementation 'org.apache.mina:mina-core:2.2.1'
implementation 'org.apache.mina:mina-http:2.2.1'
implementation 'org.controlsfx:controlsfx:11.1.2'
implementation 'org.rauschig:jarchivelib:1.2.0'
implementation 'org.slf4j:slf4j-api:2.0.5'
implementation 'org.usb4java:libusb4java:1.3.0'
implementation 'org.usb4java:usb4java:1.3.0'
implementation 'org.usb4java:usb4java-javax:1.3.0'
implementation 'io.github.dsheirer:libusb4java-darwin-aarch64:1.3.1' //usb4java native lib for OSX M1 cpu
implementation 'pl.edu.icm:JLargeArrays:1.6'
}
/**
* This is needed for the JDK17 vector API ... until it moves out of incubation
*/
tasks.withType(JavaCompile) {
options.compilerArgs.add("--add-modules=jdk.incubator.vector") //Needed while Panama Vector API remains in incubator
}
application {
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
applicationDefaultJvmArgs =
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
'--add-modules=jdk.incubator.vector', //Needed while Panama Vector API remains in incubator
'--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED'] //Windows Swing - jide-oss library
}
jar {
manifest {
attributes (
'Implementation-Title' : 'sdrtrunk project',
'Implementation-Version' : archiveVersion,
'Created-By' : "Gradle ${gradle.gradleVersion}",
'Build-Timestamp' : new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
'Build-OS' : "${System.properties['os.name']} (${System.properties['os.arch']} ${System.properties['os.version']}"
)
}
}
idea {
@@ -93,112 +125,102 @@ idea {
}
}
javafx {
modules = ['javafx.base', 'javafx.controls', 'javafx.graphics', 'javafx.swing']
}
jar {
manifest {
attributes (
'Implementation-Title' : 'sdrtrunk project',
'Implementation-Version' : archiveVersion,
'Class-Path' : 'jmbe-1.0.1.jar',
'Created-By' : "Gradle ${gradle.gradleVersion}",
'Build-Timestamp' : new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
'Build-OS' : "${System.properties['os.name']} (${System.properties['os.arch']} ${System.properties['os.version']}"
)
}
}
/**
* Runtime target OS and CPU image names. We append version here so that version details are at end of zip filename.
*/
String targetLinuxAarch64 = 'linux-aarch64-v' + version
String targetLinuxX86_64 = 'linux-x86_64-v' + version
String targetOsxAarch64 = 'osx-aarch64-v' + version
String targetOsxX86_64 = 'osx-x86_64-v' + version
String targetWindowsX86_64 = 'windows-x86_64-v' + version
/**
* Java Development Kit (JDK) locations. In order to build OS-specific images, these paths must point to the 'bin'
* directory within a JDK for each of the specified architectures. These JDKs are used by the runtime and runtimeZip
* tasks to produce platform-specific builds. If none of these paths exists, then an image will be created for the
* host OS using the installed JDK.
* Optional pre-downloaded Java Development Kit (JDK) for each target OS and CPU. These locations are hard-coded to the
* author's development environment, but can be changed to your environment if you want to use this build pattern.
*/
def jdk_linux_x86_64 = '/media/denny/WD250GB/java/bellsoft/linux-x64/jdk-13.0.1'
def jdk_linux_x86_32 = '/media/denny/WD250GB/java/bellsoft/linux-x86/jdk-13.0.1'
def jdk_osx_x86_64 = '/media/denny/WD250GB/java/bellsoft/osx-x64/jdk-13.0.1.jdk'
def jdk_windows_x86_64 = '/media/denny/WD250GB/java/bellsoft/windows-x64/jdk-13.0.1'
def jdk_windows_x86_32 = '/media/denny/WD250GB/java/bellsoft/windows-x86/jdk-13.0.1'
String jdk_base = '/home/denny/java_jdks/'
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-19.0.1-full'
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-19.0.1-full'
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-19.0.1-full.jdk'
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-19.0.1-full.jdk'
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-19.0.1-full'
def 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)
boolean hasTargetJdks = file(jdk_linux_x86_64).exists() || file(jdk_linux_aarch64).exists() ||
file(jdk_osx_x86_64).exists() || file(jdk_osx_aarch64).exists() || file(jdk_windows_x86_64).exists()
/**
* Auto-download target JDKs. Defaults to false, or the user can override via the gradle command line by
* including this option: '-PdownloadJdks=true'
*/
boolean downloadJdks = project.hasProperty('downloadJdks') ? project.getProperty('downloadJdks') : false
/**
* Beryx Runtime plugin for creating OS and CPU targeted build(s) of the software.
*
* Option 1: Create a single target build using the locally installed JDK, or optionally create target builds for
* all OS and CPU targets when pre-downloaded target JDKs are available (see 'jdk_*' properties above).
*
* >: gradle runtimeZip
*
* Option 2: Create all OS and CPU target builds by automatically downloading all JDKs needed to build the target images
*
* >: gradle runtimeZip -PdownloadJdks=true
*/
runtime {
if(directory_linux_x86_64.exists())
{
targetPlatform('linux-x86_64', jdk_linux_x86_64)
//Build by automatically downloading JDKs for the OS and CPU targets
if(downloadJdks) {
println("Building all runtime images")
println("The [ > jre: ] build phase downloads several target JDKs and this may take a while ... please wait")
targetPlatform(targetLinuxAarch64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-aarch64-full.tar.gz")
}
targetPlatform(targetLinuxX86_64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-amd64-full.tar.gz")
}
targetPlatform(targetOsxAarch64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-aarch64-full.tar.gz")
}
targetPlatform(targetOsxX86_64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-amd64-full.tar.gz")
}
targetPlatform(targetWindowsX86_64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-windows-amd64-full.zip")
}
}
//Build using pre-downloaded JDKs
else if(hasTargetJdks) {
println("Building all runtime images using pre-downloaded JDKs")
if(file(jdk_linux_aarch64).exists()) {
targetPlatform(targetLinuxAarch64, jdk_linux_aarch64)
}
if(file(jdk_linux_x86_64).exists()) {
targetPlatform(targetLinuxX86_64, jdk_linux_x86_64)
}
if(file(jdk_osx_aarch64).exists()) {
targetPlatform(targetOsxAarch64, jdk_osx_aarch64)
}
if(file(jdk_osx_x86_64).exists()) {
targetPlatform(targetOsxX86_64, jdk_osx_x86_64)
}
if(file(jdk_windows_x86_64).exists()) {
targetPlatform(targetWindowsX86_64, jdk_windows_x86_64)
}
}
//Build using locally installed JDK only
else {
println("Building a single runtime image using locally installed JDK")
}
if(directory_linux_x86_32.exists())
{
targetPlatform('linux-x86_32', jdk_linux_x86_32)
}
//Additional modules to include with auto-detected modules.
modules = ['jdk.crypto.ec'] //jdk.crypto.ec needed for HTTPS connections (broadcastify calls & map tile server)
if(directory_osx_x86_64.exists())
{
targetPlatform('osx-x86_64', jdk_osx_x86_64)
}
if(directory_windows_x86_64.exists())
{
targetPlatform('windows-x86_64', jdk_windows_x86_64)
}
if(directory_windows_x86_32.exists())
{
targetPlatform('windows-x86_32', jdk_windows_x86_32)
}
//Use auto-detected modules and 'add' any specified modules.
additive = true
//Recommended options for reducing/shrinking runtime target image size
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
modules = ['java.desktop', 'java.naming', 'jdk.unsupported', 'jdk.unsupported.desktop']
imageZip = file("$buildDir/image/sdr-trunk-" + version + ".zip")
}
/**
* Check for existence of JDK folders for each supported platform before creating runtime images
*/
tasks.runtime.doFirst {
if(!directory_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())
{
println("Skipping OS Image - Linux x86 32-bit JDK was not found at " + jdk_linux_x86_32)
}
if(!directory_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())
{
println("Skipping OS Image - Windows x86 64-bit JDK was not found at " + jdk_windows_x86_64)
}
if(!directory_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
*/
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/ })
//Runtime images have auto appended version in image zip name. Override to include version in single target image.
imageZip = (hasTargetJdks | downloadJdks) ? file("$buildDir/image/sdr-trunk.zip") :
file("$buildDir/image/sdr-trunk-" + version + ".zip")
}

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,25 @@
#
# /*
# * ******************************************************************************
# * Copyright (C) 2014-2019 Dennis Sheirer
# *
# * This program is free software: you can redistribute it and/or modify
# * it under the terms of the GNU General Public License as published by
# * the Free Software Foundation, either version 3 of the License, or
# * (at your option) any later version.
# *
# * This program is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# * GNU General Public License for more details.
# *
# * You should have received a copy of the GNU General Public License
# * along with this program. If not, see <http://www.gnu.org/licenses/>
# * *****************************************************************************
# */
# ******************************************************************************
# Copyright (C) 2014-2022 Dennis Sheirer
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
# *****************************************************************************
#
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

305
gradlew vendored
View File

@@ -1,78 +1,132 @@
#!/usr/bin/env sh
#!/bin/sh
#
# ******************************************************************************
# Copyright (C) 2014-2022 Dennis Sheirer
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
# *****************************************************************************
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m"'
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -81,7 +135,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@@ -89,84 +143,105 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

56
gradlew.bat vendored
View File

@@ -1,4 +1,20 @@
@if "%DEBUG%" == "" @echo off
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -9,19 +25,23 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m"
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -35,7 +55,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -45,38 +65,26 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

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,78 +1,60 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias;
import io.github.dsheirer.alias.action.AliasAction;
import io.github.dsheirer.alias.action.AliasActionType;
import io.github.dsheirer.alias.action.beep.BeepAction;
import io.github.dsheirer.alias.action.beep.BeepActionEditor;
import io.github.dsheirer.alias.action.clip.ClipAction;
import io.github.dsheirer.alias.action.clip.ClipActionEditor;
import io.github.dsheirer.alias.action.script.ScriptAction;
import io.github.dsheirer.alias.action.script.ScriptActionEditor;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannelEditor;
import io.github.dsheirer.alias.id.esn.ESNEditor;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncIDEditor;
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200IDEditor;
import io.github.dsheirer.alias.id.legacy.mobileID.MINEditor;
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327ID;
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327IDEditor;
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordable;
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordableEditor;
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
import io.github.dsheirer.alias.id.legacy.siteID.SiteIDEditor;
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupIDEditor;
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueIDEditor;
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
import io.github.dsheirer.alias.id.lojack.LoJackIDEditor;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.priority.PriorityEditor;
import io.github.dsheirer.alias.id.radio.Radio;
import io.github.dsheirer.alias.id.radio.RadioEditor;
import io.github.dsheirer.alias.id.radio.RadioRange;
import io.github.dsheirer.alias.id.radio.RadioRangeEditor;
import io.github.dsheirer.alias.id.record.Record;
import io.github.dsheirer.alias.id.record.RecordEditor;
import io.github.dsheirer.alias.id.status.StatusID;
import io.github.dsheirer.alias.id.status.StatusIDEditor;
import io.github.dsheirer.alias.id.status.UnitStatusID;
import io.github.dsheirer.alias.id.status.UserStatusID;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupEditor;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRangeEditor;
import io.github.dsheirer.audio.broadcast.BroadcastModel;
import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.gui.editor.EmptyEditor;
import io.github.dsheirer.alias.id.tone.TonesID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class AliasFactory
{
private static final Logger mLog = LoggerFactory.getLogger(AliasFactory.class);
public static AliasID copyOf(AliasID id)
{
switch(id.getType())
@@ -87,11 +69,6 @@ public class AliasFactory
Esn copyESN = new Esn();
copyESN.setEsn(originalESN.getEsn());
return copyESN;
case LTR_NET_UID:
UniqueID originalUniqueID = (UniqueID)id;
UniqueID copyUniqueID = new UniqueID();
copyUniqueID.setUid(originalUniqueID.getUid());
return copyUniqueID;
case LOJACK:
LoJackFunctionAndID originalLoJackFunctionAndID = (LoJackFunctionAndID)id;
LoJackFunctionAndID copyLoJackFunctionAndID = new LoJackFunctionAndID();
@@ -111,11 +88,13 @@ public class AliasFactory
case RADIO_ID:
Radio originalRadio = (Radio)id;
Radio copyRadio = new Radio(originalRadio.getProtocol(), originalRadio.getValue());
copyRadio.setOverlap(originalRadio.overlapProperty().get());
return copyRadio;
case RADIO_ID_RANGE:
RadioRange originalRadioRange = (RadioRange)id;
RadioRange copyRadioRange = new RadioRange(originalRadioRange.getProtocol(), originalRadioRange.getMinRadio(),
originalRadioRange.getMaxRadio());
copyRadioRange.setOverlap(originalRadioRange.overlapProperty().get());
return copyRadioRange;
case RECORD:
return new Record();
@@ -125,27 +104,45 @@ public class AliasFactory
copySiteID.setSite(originalSiteID.getSite());
return copySiteID;
case STATUS:
StatusID originalStatusID = (StatusID)id;
StatusID copyStatusID = new StatusID();
copyStatusID.setStatus(originalStatusID.getStatus());
return copyStatusID;
UserStatusID originalUserStatusID = (UserStatusID)id;
UserStatusID copyUserStatusID = new UserStatusID();
copyUserStatusID.setStatus(originalUserStatusID.getStatus());
copyUserStatusID.setOverlap(originalUserStatusID.overlapProperty().get());
return copyUserStatusID;
case TALKGROUP:
Talkgroup originalTalkgroup = (Talkgroup)id;
Talkgroup copyTalkgroup = new Talkgroup(originalTalkgroup.getProtocol(), originalTalkgroup.getValue());
copyTalkgroup.setOverlap(originalTalkgroup.overlapProperty().get());
return copyTalkgroup;
case TALKGROUP_RANGE:
TalkgroupRange originalRange = (TalkgroupRange)id;
TalkgroupRange copyRange = new TalkgroupRange(originalRange.getProtocol(), originalRange.getMinTalkgroup(),
originalRange.getMaxTalkgroup());
copyRange.setOverlap(originalRange.overlapProperty().get());
return copyRange;
case TONES:
TonesID originalTones = (TonesID)id;
TonesID copyTones = new TonesID();
copyTones.setToneSequence(originalTones.getToneSequence().copyOf());
return copyTones;
case UNIT_STATUS:
UnitStatusID originalUnitStatus = (UnitStatusID)id;
UnitStatusID copyUnitStatusID = new UnitStatusID();
copyUnitStatusID.setStatus(originalUnitStatus.getStatus());
copyUnitStatusID.setOverlap(originalUnitStatus.overlapProperty().get());
return copyUnitStatusID;
//Legacy identifiers ... not supported
case FLEETSYNC:
case LEGACY_TALKGROUP:
case LTR_NET_UID:
case MDC1200:
case MPT1327:
case NON_RECORDABLE:
return null;
default:
mLog.warn("Unrecognized Alias ID Type [" + id.getType() + "] cannot make copy of instance");
break;
}
return null;
@@ -155,7 +152,11 @@ public class AliasFactory
{
if(action instanceof BeepAction)
{
return new BeepAction();
BeepAction original = (BeepAction)action;
BeepAction copyBeep = new BeepAction();
copyBeep.setInterval(original.getInterval());
copyBeep.setPeriod(original.getPeriod());
return copyBeep;
}
else if(action instanceof ClipAction)
{
@@ -179,106 +180,59 @@ public class AliasFactory
return null;
}
public static Alias copyOf(Alias original)
public static Alias shallowCopyOf(Alias original)
{
Alias copy = new Alias(original.getName());
copy.setList(original.getList());
copy.setAliasListName(original.getAliasListName());
copy.setGroup(original.getGroup());
copy.setColor(original.getColor());
copy.setIconName(original.getIconName());
for(AliasID id : original.getId())
{
AliasID copyID = copyOf(id);
if(copyID != null)
{
copy.addAliasID(copyID);
}
}
for(AliasAction action : original.getAction())
{
AliasAction copyAction = copyOf(action);
if(copyAction != null)
{
copy.addAliasAction(copyAction);
}
}
return copy;
}
public static Editor<AliasID> getEditor(AliasID aliasID, BroadcastModel broadcastModel)
public static List<AliasID> copyAliasIDs(Alias original)
{
if(aliasID != null)
{
switch(aliasID.getType())
{
case BROADCAST_CHANNEL:
return new BroadcastChannelEditor(aliasID, broadcastModel);
case ESN:
return new ESNEditor(aliasID);
case LTR_NET_UID:
return new UniqueIDEditor(aliasID);
case LOJACK:
return new LoJackIDEditor(aliasID);
case MIN:
return new MINEditor(aliasID);
case PRIORITY:
return new PriorityEditor(aliasID);
case RADIO_ID:
return new RadioEditor(aliasID);
case RADIO_ID_RANGE:
return new RadioRangeEditor(aliasID);
case RECORD:
return new RecordEditor(aliasID);
case SITE:
return new SiteIDEditor(aliasID);
case STATUS:
return new StatusIDEditor(aliasID);
case TALKGROUP:
return new TalkgroupEditor(aliasID);
case TALKGROUP_RANGE:
return new TalkgroupRangeEditor(aliasID);
List<AliasID> aliasIDS = new ArrayList<>();
case FLEETSYNC:
return new FleetsyncIDEditor(aliasID);
case LEGACY_TALKGROUP:
return new LegacyTalkgroupIDEditor(aliasID);
case MDC1200:
return new MDC1200IDEditor(aliasID);
case MPT1327:
return new MPT1327IDEditor(aliasID);
case NON_RECORDABLE:
return new NonRecordableEditor(aliasID);
default:
break;
}
for(AliasID id : original.getAliasIdentifiers())
{
aliasIDS.add(copyOf(id));
}
return new EmptyEditor<AliasID>();
return aliasIDS;
}
public static Editor<AliasAction> getEditor(AliasAction aliasAction)
public static List<AliasAction> copyAliasActions(Alias original)
{
if(aliasAction != null)
List<AliasAction> actions = new ArrayList<>();
for(AliasAction aliasAction: original.getAliasActions())
{
switch(aliasAction.getType())
{
case BEEP:
return new BeepActionEditor(aliasAction);
case CLIP:
return new ClipActionEditor(aliasAction);
case SCRIPT:
return new ScriptActionEditor(aliasAction);
default:
break;
}
actions.add(copyOf(aliasAction));
}
return new EmptyEditor<AliasAction>();
return actions;
}
public static Alias copyOf(Alias original)
{
Alias copy = shallowCopyOf(original);
List<AliasID> aliasIDS = copyAliasIDs(original);
for(AliasID aliasID: aliasIDS)
{
copy.addAliasID(aliasID);
}
List<AliasAction> actions = copyAliasActions(original);
for(AliasAction action: actions)
{
copy.addAliasAction(action);
}
return copy;
}
public static AliasID getAliasID(AliasIDType type)
@@ -318,7 +272,7 @@ public class AliasFactory
case SITE:
return new SiteID();
case STATUS:
return new StatusID();
return new UserStatusID();
case LEGACY_TALKGROUP:
return new LegacyTalkgroupID();
default:

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias;
@@ -28,9 +25,11 @@ import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.radio.Radio;
import io.github.dsheirer.alias.id.radio.RadioRange;
import io.github.dsheirer.alias.id.status.StatusID;
import io.github.dsheirer.alias.id.status.UnitStatusID;
import io.github.dsheirer.alias.id.status.UserStatusID;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.alias.id.tone.TonesID;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.esn.ESNIdentifier;
@@ -40,32 +39,41 @@ import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.status.UnitStatusIdentifier;
import io.github.dsheirer.identifier.status.UserStatusIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.identifier.tone.ToneIdentifier;
import io.github.dsheirer.identifier.tone.ToneSequence;
import io.github.dsheirer.protocol.Protocol;
import io.github.dsheirer.sample.Listener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* List of aliases that share the same alias list name and provides convenient methods for looking up alias
* objects that match an identifier.
*/
public class AliasList implements Listener<AliasEvent>
public class AliasList
{
private final static Logger mLog = LoggerFactory.getLogger(AliasList.class);
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new HashMap<>();
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new HashMap<>();
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new EnumMap<>(Protocol.class);
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new EnumMap<>(Protocol.class);
private Map<String,Alias> mESNMap = new HashMap<>();
private Map<Integer,Alias> mStatusMap = new HashMap<>();
private Map<Integer,Alias> mUnitStatusMap = new HashMap<>();
private Map<Integer,Alias> mUserStatusMap = new HashMap<>();
private Map<ToneSequence,Alias> mToneSequenceMap = new HashMap<>();
private boolean mHasAliasActions = false;
private String mName;
private ObservableList<Alias> mAliases = FXCollections.observableArrayList(Alias.extractor());
/**
* List of aliases where all aliases share the same list name. Contains
@@ -78,6 +86,14 @@ public class AliasList implements Listener<AliasEvent>
mName = name;
}
/**
* Observable list of aliases contained in this alias list
*/
public ObservableList<Alias> aliases()
{
return mAliases;
}
/**
* Adds the alias to this list
*/
@@ -85,16 +101,18 @@ public class AliasList implements Listener<AliasEvent>
{
if(alias != null)
{
for(AliasID aliasID : alias.getId())
{
addAliasID(aliasID, alias);
}
alias.getAliasIdentifiers().stream().forEach(aliasID -> addAliasID(aliasID, alias));
}
if(alias.hasActions())
{
mHasAliasActions = true;
}
if(!mAliases.contains(alias))
{
mAliases.add(alias);
}
}
/**
@@ -161,7 +179,7 @@ public class AliasList implements Listener<AliasEvent>
radioRangeAliasList.add(radioRange, alias);
break;
case ESN:
String esn = ((Esn) id).getEsn();
String esn = ((Esn)id).getEsn();
if(esn != null && !esn.isEmpty())
{
@@ -169,7 +187,67 @@ public class AliasList implements Listener<AliasEvent>
}
break;
case STATUS:
mStatusMap.put(((StatusID) id).getStatus(), alias);
int userStatus = ((UserStatusID)id).getStatus();
if(mUserStatusMap.containsKey(userStatus) && !mUserStatusMap.get(userStatus).equals(alias))
{
id.setOverlap(true);
Alias existing = mUserStatusMap.get(userStatus);
for(AliasID aliasID: existing.getAliasIdentifiers())
{
if(aliasID instanceof UserStatusID && ((UserStatusID)aliasID).getStatus() == userStatus)
{
aliasID.setOverlap(true);
}
}
}
mUserStatusMap.put(userStatus, alias);
break;
case UNIT_STATUS:
int unitStatus = ((UnitStatusID)id).getStatus();
if(mUnitStatusMap.containsKey(unitStatus) && !mUnitStatusMap.get(unitStatus).equals(alias))
{
id.setOverlap(true);
Alias existing = mUnitStatusMap.get(unitStatus);
for(AliasID aliasID: existing.getAliasIdentifiers())
{
if(aliasID instanceof UnitStatusID && ((UnitStatusID)aliasID).getStatus() == unitStatus)
{
aliasID.setOverlap(true);
}
}
}
mUnitStatusMap.put(unitStatus, alias);
break;
case TONES:
ToneSequence toneSequence = ((TonesID)id).getToneSequence();
if(toneSequence != null)
{
if(mToneSequenceMap.containsKey(toneSequence) && !mToneSequenceMap.get(toneSequence).equals(alias))
{
id.setOverlap(true);
Alias existing = mToneSequenceMap.get(toneSequence);
for(AliasID aliasID: existing.getAliasIdentifiers())
{
if(aliasID instanceof TonesID && aliasID.equals(id))
{
aliasID.setOverlap(true);
}
}
}
else
{
mToneSequenceMap.put(toneSequence, alias);
}
}
break;
}
}
@@ -185,13 +263,45 @@ public class AliasList implements Listener<AliasEvent>
*/
public void removeAlias(Alias alias)
{
for(TalkgroupAliasList talkgroupAliasList: mTalkgroupProtocolMap.values())
//Note: because the alias' identifiers could have changed from when we initially added the alias, we have to
//inspect every collection and map to remove the alias completely.
mAliases.remove(alias);
mTalkgroupProtocolMap.values().stream().forEach(talkgroupAliasList -> talkgroupAliasList.remove(alias));
mRadioProtocolMap.values().stream().forEach(radioAliasList -> radioAliasList.remove(alias));
Collection<Alias> collection = Collections.singleton(alias);
mESNMap.values().removeAll(collection);
mUnitStatusMap.values().removeAll(collection);
mUserStatusMap.values().removeAll(collection);
mToneSequenceMap.values().removeAll(collection);
validate();
}
/**
* Identifies all aliases with an alias identifier that has the overlap flag set, resets the flag, and then readds
* each alias back to this alias list so that overlap can be detected again.
*/
public void validate()
{
Set<Alias> overlapAliases = new HashSet<>();
List<Alias> aliases = new ArrayList<>(mAliases);
for(Alias alias: aliases)
{
talkgroupAliasList.remove(alias);
for(AliasID aliasID: alias.getAliasIdentifiers())
{
if(aliasID.overlapProperty().get())
{
aliasID.setOverlap(false);
overlapAliases.add(alias);
}
}
}
remove(alias, mStatusMap);
remove(alias, mESNMap);
overlapAliases.stream().forEach(alias -> addAlias(alias));
}
/**
@@ -235,39 +345,15 @@ public class AliasList implements Listener<AliasEvent>
}
/**
* Receive alias change event notifications and modify this list accordingly
* Updates the alias by removing it from this list and then adding it back to this list when the list name matches.
*/
@Override
public void receive(AliasEvent event)
public void updateAlias(Alias alias)
{
if(hasName())
{
Alias alias = event.getAlias();
removeAlias(alias);
switch(event.getEvent())
{
case ADD:
if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList()))
{
addAlias(alias);
}
break;
case CHANGE:
if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList()))
{
removeAlias(alias);
addAlias(alias);
}
break;
case DELETE:
if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList()))
{
removeAlias(alias);
}
break;
default:
break;
}
if(hasName() && getName().equals(alias.getAliasListName()))
{
addAlias(alias);
}
}
@@ -342,14 +428,31 @@ public class AliasList implements Listener<AliasEvent>
if(identifier instanceof UnitStatusIdentifier)
{
int status = ((UnitStatusIdentifier)identifier).getValue();
return toList(mStatusMap.get(status));
return toList(mUserStatusMap.get(status));
}
break;
case USER_STATUS:
if(identifier instanceof UserStatusIdentifier)
{
int status = ((UserStatusIdentifier)identifier).getValue();
return toList(mStatusMap.get(status));
return toList(mUserStatusMap.get(status));
}
break;
case TONE:
if(identifier instanceof ToneIdentifier)
{
ToneSequence toneSequence = ((ToneIdentifier)identifier).getValue();
if(toneSequence != null && toneSequence.hasTones())
{
for(Map.Entry<ToneSequence,Alias> entry: mToneSequenceMap.entrySet())
{
if(entry.getKey().isContainedIn(toneSequence))
{
return toList(entry.getValue());
}
}
}
}
break;
}
@@ -482,23 +585,6 @@ public class AliasList implements Listener<AliasEvent>
return channels;
}
/**
* Removes the alias (as a value) from the specified map
*/
public static void remove(Alias alias, Map map)
{
Iterator<Map.Entry> it = map.entrySet().iterator();
while(it.hasNext())
{
if(it.next().getValue().equals(alias))
{
it.remove();
}
}
}
/**
* Listing of talkgroups and ranges for a specific protocol
*/
@@ -515,16 +601,17 @@ public class AliasList implements Listener<AliasEvent>
{
int value = identifier.getValue();
if(mTalkgroupAliasMap.containsKey(value))
Alias mapValue = mTalkgroupAliasMap.get(value);
if (mapValue != null)
{
return mTalkgroupAliasMap.get(value);
return mapValue;
}
for(TalkgroupRange talkgroupRange: mTalkgroupRangeAliasMap.keySet())
for(Map.Entry<TalkgroupRange, Alias> entry : mTalkgroupRangeAliasMap.entrySet())
{
if(talkgroupRange.contains(value))
if(entry.getKey().contains(value))
{
return mTalkgroupRangeAliasMap.get(talkgroupRange);
return entry.getValue();
}
}
@@ -533,14 +620,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 +647,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 +683,17 @@ public class AliasList implements Listener<AliasEvent>
{
int value = identifier.getValue();
if(mRadioAliasMap.containsKey(value))
Alias mapValue = mRadioAliasMap.get(value);
if(mapValue != null)
{
return mRadioAliasMap.get(value);
return mapValue;
}
for(RadioRange radioRange: mRadioRangeAliasMap.keySet())
for(Map.Entry<RadioRange, Alias> entry : mRadioRangeAliasMap.entrySet())
{
if(radioRange.contains(value))
if(entry.getKey().contains(value))
{
return mRadioRangeAliasMap.get(radioRange);
return entry.getValue();
}
}
@@ -621,9 +707,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 +729,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,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,45 +14,54 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.alias;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Alias Model contains all aliases and is responsible for creation and management of alias lists. Alias lists are a
* set of aliases that all share a common alias list name and can be attached to a decoding channel for aliasing
* identifiers produced by channel decoder(s).
*/
public class AliasModel extends AbstractTableModel
public class AliasModel
{
private static final long serialVersionUID = 1L;
public static final int COLUMN_LIST = 0;
public static final int COLUMN_GROUP = 1;
public static final int COLUMN_NAME = 2;
public static final int COLUMN_ICON = 3;
public static final int COLUMN_COLOR = 4;
private List<Alias> mAliases = new CopyOnWriteArrayList<>();
private Broadcaster<AliasEvent> mAliasEventBroadcaster = new Broadcaster<>();
private final static Logger mLog = LoggerFactory.getLogger(AliasModel.class);
public static final String NO_ALIAS_LIST = "(No Alias List)";
private ObservableList<Alias> mAliases = FXCollections.observableArrayList(Alias.extractor());
private ObservableList<String> mAliasListNames = FXCollections.observableArrayList();
private Map<String,AliasList> mAliasListMap = new HashMap<>();
public AliasModel()
{
//Register a listener to detect alias changes and broadcast change events to cause playlist save requests
mAliases.addListener(new AliasListChangeListener());
}
public ObservableList<Alias> aliasList()
{
return mAliases;
}
public ObservableList<String> aliasListNames()
{
return mAliasListNames;
}
/**
@@ -64,14 +72,19 @@ public class AliasModel extends AbstractTableModel
return Collections.unmodifiableList(mAliases);
}
public Alias getAliasAtIndex(int row)
/**
* Removes all aliases from the list and broadcasts the alias delete event for each
*/
public void clear()
{
if(mAliases.size() >= row)
List<Alias> aliasToRemove = new ArrayList<>(mAliases);
for(Alias alias: aliasToRemove)
{
return mAliases.get(row);
removeAlias(alias);
}
return null;
mAliasListNames.clear();
}
/**
@@ -117,16 +130,17 @@ public class AliasModel extends AbstractTableModel
return new AliasList(name);
}
if(mAliasListMap.containsKey(name))
AliasList mapValue = mAliasListMap.get(name);
if (mapValue != null)
{
return mAliasListMap.get(name);
return mapValue;
}
AliasList aliasList = new AliasList(name);
for(Alias alias : mAliases)
{
if(alias.hasList() && alias.getList().equalsIgnoreCase(name))
if(alias.hasList() && alias.getAliasListName().equalsIgnoreCase(name))
{
aliasList.addAlias(alias);
}
@@ -134,9 +148,6 @@ public class AliasModel extends AbstractTableModel
mAliasListMap.put(name, aliasList);
//Register the new alias list to receive updates from this model
addListener(aliasList);
return aliasList;
}
@@ -145,19 +156,7 @@ public class AliasModel extends AbstractTableModel
*/
public List<String> getListNames()
{
List<String> listNames = new ArrayList<>();
for(Alias alias : mAliases)
{
if(alias.hasList() && !listNames.contains(alias.getList()))
{
listNames.add(alias.getList());
}
}
Collections.sort(listNames);
return listNames;
return mAliasListNames;
}
/**
@@ -180,33 +179,6 @@ public class AliasModel extends AbstractTableModel
return groupNames;
}
/**
* Returns a list of alias group names for all aliases that have a matching
* list name value
*/
public List<String> getGroupNames(String listName)
{
List<String> groupNames = new ArrayList<>();
if(listName != null)
{
for(Alias alias : mAliases)
{
if(alias.hasList() &&
alias.hasGroup() &&
listName.equals(alias.getList()) &&
!groupNames.contains(alias.getGroup()))
{
groupNames.add(alias.getGroup());
}
}
}
Collections.sort(groupNames);
return groupNames;
}
/**
* Bulk loading of aliases
*/
@@ -221,173 +193,177 @@ public class AliasModel extends AbstractTableModel
/**
* Adds the alias to the model
*/
public int addAlias(Alias alias)
public void addAlias(Alias alias)
{
if(alias != null)
if(mAliases.contains(alias))
{
mAliases.add(alias);
int index = mAliases.size() - 1;
fireTableRowsInserted(index, index);
broadcast(new AliasEvent(alias, AliasEvent.Event.ADD));
return index;
removeAlias(alias);
}
return -1;
mAliases.add(alias);
}
private boolean hasAliasList(String aliasListName)
{
return aliasListName != null && mAliasListMap.containsKey(aliasListName);
}
public void addAliasList(String aliasListName)
{
if(aliasListName != null && !aliasListName.isEmpty())
{
if(!mAliasListNames.contains(aliasListName))
{
mAliasListNames.add(aliasListName);
}
}
else if(!mAliasListNames.contains(NO_ALIAS_LIST))
{
//This list allows users to view unassigned aliases so that they can move them to a valid alias list, but
// it is not assignable to a channel
mAliasListNames.add(NO_ALIAS_LIST);
}
}
/**
* Removes the channel from the model and broadcasts a channel remove event
* Removes the alias from this model and an alias list (if one exists)
*/
public void removeAlias(Alias alias)
{
if(alias != null)
{
int index = mAliases.indexOf(alias);
mAliases.remove(alias);
fireTableRowsDeleted(index, index);
broadcast(new AliasEvent(alias, AliasEvent.Event.DELETE));
if(hasAliasList(alias.getAliasListName()))
{
getAliasList(alias.getAliasListName()).removeAlias(alias);
}
}
}
/**
* Renames any broadcast channels that have the previous name.
*
* @param previousName to rename
* @param newName to assign to the broadcast channel
* Retrieves all aliases that match the alias list and have at least one alias ID of the specified type
* @param aliasListName to search
* @param type to find
* @return list of aliases
*/
public void renameBroadcastChannel(String previousName, String newName)
public List<Alias> getAliases(String aliasListName, AliasIDType type)
{
if(previousName == null || previousName.isEmpty() || newName == null || newName.isEmpty())
List<Alias> aliases = new ArrayList<>();
for(Alias alias : mAliases)
{
if(alias.hasList() && alias.getAliasListName().equalsIgnoreCase(aliasListName))
{
for(AliasID aliasID: alias.getAliasIdentifiers())
{
if(aliasID.getType() == type)
{
aliases.add(alias);
break;
}
}
}
}
return aliases;
}
/**
* Indicates that one or more of the aliases managed by this model are configured to stream to the specified
* broadcast channel argument.
* @param broadcastChannel to check
* @return true if the broadcast channel is non-null, non-empty and at least one alias is configured to stream to
* the specified stream name.
*/
public boolean hasAliasesWithBroadcastChannel(String broadcastChannel)
{
if(broadcastChannel == null || broadcastChannel.isEmpty())
{
return false;
}
for(Alias alias: mAliases)
{
if(alias.hasBroadcastChannel(broadcastChannel))
{
return true;
}
}
return false;
}
/**
* Updates all aliases configured to stream to the previousStreamName with the updatedStreamName
* @param previousStreamName to be removed
* @param updatedStreamName to be added
*/
public void updateBroadcastChannel(String previousStreamName, String updatedStreamName)
{
if(previousStreamName == null || previousStreamName.isEmpty() || updatedStreamName == null || updatedStreamName.isEmpty())
{
return;
}
for(Alias alias : mAliases)
for(Alias alias: mAliases)
{
if(alias.hasBroadcastChannel(previousName))
if(alias.hasBroadcastChannel(previousStreamName))
{
for(BroadcastChannel broadcastChannel : alias.getBroadcastChannels())
for(BroadcastChannel broadcastChannel: alias.getBroadcastChannels())
{
if(broadcastChannel.getChannelName().contentEquals(previousName))
if(broadcastChannel.getChannelName().contentEquals(previousStreamName))
{
broadcastChannel.setChannelName(newName);
alias.removeAliasID(broadcastChannel);
if(!alias.hasBroadcastChannel(updatedStreamName))
{
alias.addAliasID(new BroadcastChannel(updatedStreamName));
}
}
}
}
}
}
@Override
public int getRowCount()
/**
* Monitors alias additions and removals and updates alias lists
*/
public class AliasListChangeListener implements ListChangeListener<Alias>
{
return mAliases.size();
}
@Override
public int getColumnCount()
{
return 5;
}
@Override
public String getColumnName(int columnIndex)
{
switch(columnIndex)
@Override
public void onChanged(ListChangeListener.Change<? extends Alias> change)
{
case COLUMN_LIST:
return "List";
case COLUMN_GROUP:
return "Group";
case COLUMN_NAME:
return "Name";
case COLUMN_ICON:
return "Icon";
case COLUMN_COLOR:
return "Color";
while(change.next())
{
if(change.wasAdded())
{
for(Alias alias: change.getAddedSubList())
{
addAliasList(alias.getAliasListName());
if(hasAliasList(alias.getAliasListName()))
{
getAliasList(alias.getAliasListName()).addAlias(alias);
}
}
}
else if(change.wasRemoved())
{
for(Alias alias: change.getRemoved())
{
if(hasAliasList(alias.getAliasListName()))
{
AliasList aliasList = getAliasList(alias.getName());
if(alias != null)
{
aliasList.removeAlias(alias);
}
}
}
}
}
}
return null;
}
@Override
public Class<?> getColumnClass(int columnIndex)
{
if(columnIndex == COLUMN_COLOR)
{
return Integer.class;
}
return String.class;
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex)
{
return false;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex)
{
Alias alias = mAliases.get(rowIndex);
switch(columnIndex)
{
case COLUMN_LIST:
return alias.getList();
case COLUMN_GROUP:
return alias.getGroup();
case COLUMN_NAME:
return alias.getName();
case COLUMN_ICON:
return alias.getIconName();
case COLUMN_COLOR:
return alias.getColor();
}
return null;
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex)
{
throw new IllegalArgumentException("Not yet implemented");
}
public void broadcast(AliasEvent event)
{
Alias alias = event.getAlias();
//Validate the alias following a user action that changed the alias or any alias IDs
if(alias != null)
{
alias.validate();
}
if(event.getEvent() == AliasEvent.Event.CHANGE)
{
int index = mAliases.indexOf(event.getAlias());
fireTableRowsUpdated(index, index);
}
mAliasEventBroadcaster.broadcast(event);
}
public void addListener(Listener<AliasEvent> listener)
{
mAliasEventBroadcaster.addListener(listener);
}
public void removeListener(Listener<AliasEvent> listener)
{
mAliasEventBroadcaster.removeListener(listener);
}
}

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,32 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias.action;
import io.github.dsheirer.alias.AliasEvent;
/**
* Interface for receiving alias events. Each listener will receive
* notification of all alias events.
*/
public interface AliasActionListener
{
/**
* Notifies the listener that an alias event has occurred
*/
public void aliasChanged( AliasEvent event );
}

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
@@ -91,12 +93,6 @@ public class AliasActionManager extends Module implements IMessageListener, List
//No actions neeeded
}
@Override
public void dispose()
{
//No actions neeeded
}
/**
* IMessageListener interface ... delegates to this class implementation of Listener<Message>
*/

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.alias.action;
@@ -26,8 +25,8 @@ import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.util.ThreadPool;
import javax.swing.JOptionPane;
import java.awt.EventQueue;
import javax.swing.*;
import java.awt.*;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -61,7 +60,7 @@ public abstract class RecurringAction extends AliasAction
break;
case UNTIL_DISMISSED:
mPerpetualAction = ThreadPool.SCHEDULED.scheduleAtFixedRate(
new PerformActionTask(alias, message), mPeriod, mPeriod, TimeUnit.SECONDS);
new PerformActionTask(alias, message), 0, mPeriod, TimeUnit.SECONDS);
StringBuilder sb = new StringBuilder();
sb.append("<html><div width='250'>Alias [");
@@ -92,7 +91,7 @@ public abstract class RecurringAction extends AliasAction
*/
private void performThreadedAction(final Alias alias, final IMessage message)
{
ThreadPool.SCHEDULED.schedule(() -> performAction(alias, message), 0, TimeUnit.SECONDS);
ThreadPool.CACHED.execute(() -> performAction(alias, message));
}
@Override
@@ -120,6 +119,7 @@ public abstract class RecurringAction extends AliasAction
public void setPeriod(int period)
{
mPeriod = period;
updateValueProperty();
}
@JacksonXmlProperty(isAttribute = true, localName = "interval")
@@ -131,14 +131,14 @@ public abstract class RecurringAction extends AliasAction
public void setInterval(Interval interval)
{
mInterval = interval;
mRunning.set(false);
updateValueProperty();
}
public enum Interval
{
ONCE("Once"),
DELAYED_RESET("Once, Reset After Delay"),
DELAYED_RESET("Once - Reset After Delay"),
UNTIL_DISMISSED("Until Dismissed");
private String mLabel;

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.alias.action.beep;
@@ -24,10 +23,43 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.action.AliasActionType;
import io.github.dsheirer.alias.action.RecurringAction;
import io.github.dsheirer.audio.AudioFormats;
import io.github.dsheirer.gui.preference.playback.ToneFrequency;
import io.github.dsheirer.gui.preference.playback.ToneUtil;
import io.github.dsheirer.gui.preference.playback.ToneVolume;
import io.github.dsheirer.message.IMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
public class BeepAction extends RecurringAction
{
private static final Logger mLog = LoggerFactory.getLogger(BeepAction.class);
private byte[] mToneBytes;
public BeepAction()
{
setInterval(Interval.ONCE);
float[] tone = ToneUtil.getTone(ToneFrequency.F700, ToneVolume.V10, 1000);
/* Little-endian byte buffer */
ByteBuffer buffer = ByteBuffer.allocate(tone.length * 2).order(ByteOrder.LITTLE_ENDIAN);
ShortBuffer shortBuffer = buffer.asShortBuffer();
for(float sample : tone)
{
shortBuffer.put((short) (sample * Short.MAX_VALUE));
}
mToneBytes = buffer.array();
}
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
@Override
@@ -39,12 +71,48 @@ public class BeepAction extends RecurringAction
@Override
public void performAction(Alias alias, IMessage message )
{
System.out.println( "\007" );
DataLine.Info info = new DataLine.Info(Clip.class, AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO);
if(!AudioSystem.isLineSupported(info))
{
mLog.error("Audio clip playback is not supported on this system");
return;
}
try
{
Clip clip = (Clip)AudioSystem.getLine(info);
clip.open(AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO, mToneBytes, 0, mToneBytes.length);
clip.start();
}
catch(Exception e)
{
mLog.error("Error attempting to play audio test tone");
}
}
@Override
public String toString()
{
return "Beep";
StringBuilder sb = new StringBuilder();
sb.append("Beep");
if(getInterval() != null)
{
switch(getInterval())
{
case ONCE:
sb.append(" Once");
break;
case DELAYED_RESET:
sb.append(" Once, Reset After ").append(getPeriod()).append(" Seconds");
break;
case UNTIL_DISMISSED:
sb.append(" Every ").append(getPeriod()).append(" Seconds Until Dismissed");
break;
}
}
return sb.toString();
}
}

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,10 @@ public enum RadioFormat
{
APCO25("********", 0, 0xFFFFFF, "0 to 16,777,215",
"<html>APCO25 valid range is 0 to 16,777,215"),
DMR("********", 0, 0xFFFFFF, "0 to 16,777,215",
"<html>DMR unit id valid range is 0 to 16,777,215"),
PASSPORT("********", 0, 0x7FFFFF, "0 to 8,388,607",
"<html>PASSPORT valid range is 0 to 8,388,607"),
UNKNOWN("********", 1, 0xFFFFFF, "1 to 16,777,215",
"Unknown protocol valid value range is 1-16,777,215");
@@ -110,6 +114,10 @@ public enum RadioFormat
{
case APCO25:
return APCO25;
case DMR:
return DMR;
case PASSPORT:
return PASSPORT;
default:
return UNKNOWN;
}

View File

@@ -22,14 +22,17 @@
package io.github.dsheirer.alias.id.radio;
import io.github.dsheirer.preference.identifier.IntegerFormat;
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.IntegerFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.AbstractIntegerFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.DMRTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.ParseException;
import java.util.HashMap;
import java.util.EnumMap;
import java.util.Map;
/**
@@ -38,12 +41,13 @@ import java.util.Map;
public class RadioFormatter
{
private final static Logger mLog = LoggerFactory.getLogger(RadioFormatter.class);
private static Map<Protocol,IntegerFormatter> mFormatterMap = new HashMap<>();
private static Map<Protocol,AbstractIntegerFormatter> mFormatterMap = new EnumMap<>(Protocol.class);
static
{
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
mFormatterMap.put(Protocol.UNKNOWN, new IntegerFormatter());
mFormatterMap.put(Protocol.DMR, new DMRTalkgroupFormatter());
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
}
public RadioFormatter()
@@ -55,7 +59,7 @@ public class RadioFormatter
*/
public static int parse(Protocol protocol, String value) throws ParseException
{
IntegerFormatter formatter = mFormatterMap.get(protocol);
AbstractIntegerFormatter formatter = mFormatterMap.get(protocol);
if(formatter == null)
{
@@ -70,7 +74,7 @@ public class RadioFormatter
*/
public static String format(Protocol protocol, int value)
{
IntegerFormatter formatter = mFormatterMap.get(protocol);
AbstractIntegerFormatter formatter = mFormatterMap.get(protocol);
if(formatter == null)
{
@@ -79,4 +83,19 @@ public class RadioFormatter
return formatter.format(value);
}
/**
* Formats the integer value to the specified integer format using the protocol specific formatter
*/
public static String format(Protocol protocol, int value, IntegerFormat format)
{
AbstractIntegerFormatter formatter = mFormatterMap.get(protocol);
if(formatter == null)
{
formatter = mFormatterMap.get(Protocol.UNKNOWN);
}
return formatter.format(value, format);
}
}

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

@@ -34,16 +34,20 @@ import io.github.dsheirer.protocol.Protocol;
*/
public enum TalkgroupFormat
{
APCO25("********", 0, 0xFFFFFF, "0 to 65,535",
APCO25("********", 0, 0xFFFF, "0 to 65,535",
"<html>APCO25 talkgroup valid range is 0 to 65,535"),
FLEETSYNC("###-####", 1, 0x7FFFFF, "001-0001 to 127-8192",
DMR("********", 0, 0xFFFFFF, "0 to 16,777,215",
"<html>DMR talkgroup valid range is 0 to 16,777,215"),
FLEETSYNC("###-####", 0, 0x7FFFFF, "001-0001 to 127-8192",
"<html>Fleetsync valid ranges are 1-127(prefix)<br>and 1-8192(ident) (ie. 001-0001 to 127-8192)"),
LTR("##-###", 257, 5375, "01-001 to 20-255",
LTR("##-###", 0x101, 0x3FFF, "01-001 to 20-255",
"<html>LTR valid ranges are 1-20(repeater) and 1-255(talkgroup) (ie. 01-001 to 20-255)"),
MDC1200("*****", 1, 0xFFFF, "1 to 65,535",
"MDC-1200 valid value range is 1-65,535"),
MPT1327("###-####", 1, 0x7FFFFF, "000-0001 to 127-8192",
"<html>MPT-1327 valid ranges are 0-127(prefix)<br>and 1-8192(ident) (ie. 000-0001 to 127-8192)"),
NBFM("*****", 1, 0xFFFF, "1 to 65,535",
"NBFM valid value range is 1-65,535"),
PASSPORT("*****", 1, 0xFFFF, "1 to 65,535",
"Passport valid value range is 1-65,535"),
UNKNOWN("********", 1, 0xFFFFFF, "1 to 16,777,215",
@@ -124,12 +128,13 @@ public enum TalkgroupFormat
return FLEETSYNC;
case LTR:
case LTR_NET:
case LTR_STANDARD:
return LTR;
case MDC1200:
return MDC1200;
case MPT1327:
return MPT1327;
case NBFM:
return NBFM;
case PASSPORT:
return PASSPORT;
default:

View File

@@ -20,19 +20,23 @@
package io.github.dsheirer.alias.id.talkgroup;
import io.github.dsheirer.preference.identifier.IntegerFormat;
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.AbstractIntegerFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.DMRTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.FleetsyncTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.IntegerFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.LTRTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.MDC1200TalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.MPT1327TalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.NBFMTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.PassportTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.ParseException;
import java.util.HashMap;
import java.util.EnumMap;
import java.util.Map;
/**
@@ -41,20 +45,21 @@ import java.util.Map;
public class TalkgroupFormatter
{
private final static Logger mLog = LoggerFactory.getLogger(TalkgroupFormatter.class);
private static Map<Protocol,IntegerFormatter> mFormatterMap = new HashMap<>();
private static Map<Protocol,AbstractIntegerFormatter> mFormatterMap = new EnumMap<>(Protocol.class);
static
{
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
mFormatterMap.put(Protocol.DMR, new DMRTalkgroupFormatter());
mFormatterMap.put(Protocol.FLEETSYNC, new FleetsyncTalkgroupFormatter());
LTRTalkgroupFormatter ltr = new LTRTalkgroupFormatter();
mFormatterMap.put(Protocol.LTR, ltr);
mFormatterMap.put(Protocol.LTR_NET, ltr);
mFormatterMap.put(Protocol.LTR_STANDARD, ltr);
mFormatterMap.put(Protocol.MDC1200, new MDC1200TalkgroupFormatter());
mFormatterMap.put(Protocol.MPT1327, new MPT1327TalkgroupFormatter());
mFormatterMap.put(Protocol.NBFM, new NBFMTalkgroupFormatter());
mFormatterMap.put(Protocol.PASSPORT, new PassportTalkgroupFormatter());
mFormatterMap.put(Protocol.UNKNOWN, new IntegerFormatter());
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
}
public TalkgroupFormatter()
@@ -66,7 +71,7 @@ public class TalkgroupFormatter
*/
public static int parse(Protocol protocol, String value) throws ParseException
{
IntegerFormatter formatter = mFormatterMap.get(protocol);
AbstractIntegerFormatter formatter = mFormatterMap.get(protocol);
if(formatter == null)
{
@@ -81,7 +86,7 @@ public class TalkgroupFormatter
*/
public static String format(Protocol protocol, int value)
{
IntegerFormatter formatter = mFormatterMap.get(protocol);
AbstractIntegerFormatter formatter = mFormatterMap.get(protocol);
if(formatter == null)
{
@@ -90,4 +95,19 @@ public class TalkgroupFormatter
return formatter.format(value);
}
/**
* Formats the integer value to the specified integer format using the protocol specific formatter
*/
public static String format(Protocol protocol, int value, IntegerFormat format)
{
AbstractIntegerFormatter formatter = mFormatterMap.get(protocol);
if(formatter == null)
{
formatter = mFormatterMap.get(Protocol.UNKNOWN);
}
return formatter.format(value, format);
}
}

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,165 @@
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 int mAudioChannelId = AUDIO_CHANNEL_ID_GENERATOR++;
private Listener<ReusableAudioPacket> mAudioPacketListener;
private MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
private final static Logger mLog = LoggerFactory.getLogger(AbstractAudioModule.class);
public static final long DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH = 60 * 8000; // 1 minute @ 8kHz
public static final int DEFAULT_TIMESLOT = 0;
private final int mMaxSegmentAudioSampleLength;
private Listener<AudioSegment> mAudioSegmentListener;
protected MutableIdentifierCollection mIdentifierCollection;
private Broadcaster<IdentifierUpdateNotification> mIdentifierUpdateNotificationBroadcaster = new Broadcaster<>();
private AliasList mAliasList;
private AudioSegment mAudioSegment;
private int mAudioSampleCount = 0;
private boolean mRecordAudioOverride;
private int mTimeslot;
/**
* Constructs an abstract audio module
*
* @param aliasList for aliasing identifiers
* @param maxSegmentAudioSampleLength in milliseconds
*/
public AbstractAudioModule()
public AbstractAudioModule(AliasList aliasList, int timeslot, long maxSegmentAudioSampleLength)
{
mAliasList = aliasList;
mMaxSegmentAudioSampleLength = (int)(maxSegmentAudioSampleLength * 8); //Convert milliseconds to samples
mTimeslot = timeslot;
mIdentifierCollection = new MutableIdentifierCollection(getTimeslot());
mIdentifierUpdateNotificationBroadcaster.addListener(mIdentifierCollection);
}
/**
* 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
* Constructs an abstract audio module with a default maximum audio segment length and a default timeslot 0.
*/
public int getAudioChannelId()
public AbstractAudioModule(AliasList aliasList)
{
return mAudioChannelId;
this(aliasList, DEFAULT_TIMESLOT, DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH);
}
/**
* Timeslot for this audio module
*/
protected int getTimeslot()
{
return mTimeslot;
}
/**
* 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();
}
/**
* Gets the current audio segment, or creates a new audio segment as necessary and broadcasts it to any registered
* listener(s).
*/
protected AudioSegment getAudioSegment()
{
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 +187,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioPacket
@Override
public Listener<IdentifierUpdateNotification> getIdentifierUpdateListener()
{
return mIdentifierCollection;
return mIdentifierUpdateNotificationBroadcaster;
}
/**
@@ -75,37 +199,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,3 +1,22 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio;
import javax.sound.sampled.AudioFormat;
@@ -5,81 +24,107 @@ import javax.sound.sampled.DataLine;
import javax.sound.sampled.Line;
import javax.sound.sampled.SourceDataLine;
/**
* Audio formats
*/
public class AudioFormats
{
public static final AudioFormat.Encoding IMBE_ENCODING =
new AudioFormat.Encoding( "IMBE" );
public static final boolean LITTLE_ENDIAN = false;
public static final boolean BIG_ENDIAN = true;
public static final float IMBE_FRAME_RATE = 50;
public static final float IMBE_SAMPLE_RATE = 50;
public static final float PCM_8KHZ_RATE = 8000;
public static final float PCM_48KHZ_RATE = 48000;
public static final int IMBE_FRAME_SIZE_BYTES = 18;
public static final int IMBE_SAMPLE_SIZE_BITS = 144;
public static final float PCM_8_KHZ_RATE = 8000;
public static final float PCM_16_KHZ_RATE = 16000;
public static final float PCM_22050_HZ_RATE = 22050;
public static final float PCM_44100_HZ_RATE = 44100;
public static final int ONE_CHANNEL = 1;
public static final int TWO_CHANNELS = 2;
public static final int PCM_SAMPLE_SIZE_BITS = 16;
public static final int PCM_FRAME_SIZE_BYTES_MONO = 2;
public static final int PCM_FRAME_SIZE_BYTES_STEREO = 4;
public static AudioFormat IMBE_AUDIO_FORMAT =
new AudioFormat( IMBE_ENCODING,
IMBE_SAMPLE_RATE,
IMBE_SAMPLE_SIZE_BITS,
ONE_CHANNEL,
IMBE_FRAME_SIZE_BYTES,
IMBE_FRAME_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_8KHZ_16BITS_MONO =
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_8KHZ_RATE,
PCM_SAMPLE_SIZE_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES_MONO,
PCM_8KHZ_RATE,
LITTLE_ENDIAN );
public static final int PCM_SAMPLE_SIZE_16_BITS = 16;
public static final int PCM_SAMPLE_SIZE_32_BITS = 32;
public static final int PCM_FRAME_SIZE_BYTES_16_BIT_MONO = 2;
public static final int PCM_FRAME_SIZE_BYTES_32_BIT_MONO = 4;
public static final int PCM_FRAME_SIZE_BYTES_16_BIT_STEREO = 4;
public static AudioFormat PCM_SIGNED_8KHZ_16BITS_STEREO =
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_8KHZ_RATE,
PCM_SAMPLE_SIZE_BITS,
TWO_CHANNELS,
PCM_FRAME_SIZE_BYTES_STEREO,
PCM_8KHZ_RATE,
public static AudioFormat PCM_SIGNED_8000_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_8_KHZ_RATE,
PCM_SAMPLE_SIZE_32_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
PCM_8_KHZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_8000_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_8_KHZ_RATE,
PCM_SAMPLE_SIZE_16_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
PCM_8_KHZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_16000_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_16_KHZ_RATE,
PCM_SAMPLE_SIZE_16_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
PCM_16_KHZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_16000_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_16_KHZ_RATE,
PCM_SAMPLE_SIZE_32_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
PCM_16_KHZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_22050_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_22050_HZ_RATE,
PCM_SAMPLE_SIZE_16_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
PCM_22050_HZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_22050_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_22050_HZ_RATE,
PCM_SAMPLE_SIZE_32_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
PCM_22050_HZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_44100_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_44100_HZ_RATE,
PCM_SAMPLE_SIZE_16_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
PCM_44100_HZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_44100_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_44100_HZ_RATE,
PCM_SAMPLE_SIZE_32_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
PCM_44100_HZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_8000_HZ_16BITS_STEREO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_8_KHZ_RATE,
PCM_SAMPLE_SIZE_16_BITS,
TWO_CHANNELS,
PCM_FRAME_SIZE_BYTES_16_BIT_STEREO,
PCM_8_KHZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_48KHZ_16BITS_MONO =
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_48KHZ_RATE,
PCM_SAMPLE_SIZE_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES_MONO,
PCM_48KHZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_48KHZ_16BITS_STEREO =
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_48KHZ_RATE,
PCM_SAMPLE_SIZE_BITS,
TWO_CHANNELS,
PCM_FRAME_SIZE_BYTES_STEREO,
PCM_48KHZ_RATE,
LITTLE_ENDIAN );
/**
* Source Data Line Info for a 48 kHz, 16-bits signed PCM one channel
*/
public static final Line.Info MONO_SOURCE_DATALINE_INFO =
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8KHZ_16BITS_MONO);
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8000_HZ_16_BIT_MONO);
/**
* Source Data Line Info for a 48 kHz, 16-bits signed PCM two channels
*/
public static final Line.Info STEREO_SOURCE_DATALINE_INFO =
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8KHZ_16BITS_STEREO);
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8000_HZ_16BITS_STEREO);
}

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

@@ -1,51 +1,46 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.audio.squelch.ISquelchStateListener;
import io.github.dsheirer.audio.squelch.SquelchState;
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
import io.github.dsheirer.sample.buffer.ReusableAudioPacketQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.sample.real.IRealBufferListener;
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, IRealBufferListener,
Listener<float[]>
{
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
private static float[] sHighPassFilterCoefficients;
@@ -75,34 +70,33 @@ public class AudioModule extends AbstractAudioModule implements IReusableBufferL
}
}
private ReusableAudioPacketQueue mAudioPacketQueue = new ReusableAudioPacketQueue("AudioModule");
private RealFIRFilter2 mHighPassFilter = new RealFIRFilter2(sHighPassFilterCoefficients);
private IRealFilter mHighPassFilter = FilterFactory.getRealFilter(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, int timeslot, long maxAudioSegmentLength)
{
super(aliasList, timeslot, maxAudioSegmentLength);
}
/**
* Creates an Audio Module.
*/
public AudioModule()
public AudioModule(AliasList aliasList)
{
super(aliasList);
}
@Override
public void dispose()
protected int getTimeslot()
{
removeAudioPacketListener();
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;
return 0;
}
@Override
@@ -114,30 +108,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,34 +117,17 @@ public class AudioModule extends AbstractAudioModule implements IReusableBufferL
}
@Override
public void receive(ReusableFloatBuffer reusableFloatBuffer)
public void receive(float[] 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();
}
else
{
reusableFloatBuffer.decrementUserCount();
audioBuffer = mHighPassFilter.filter(audioBuffer);
addAudio(audioBuffer);
}
}
@Override
public Listener<ReusableFloatBuffer> getReusableBufferListener()
public Listener<float[]> getBufferListener()
{
//Redirect received reusable buffers to the receive(buffer) method
return this;
@@ -186,16 +141,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,51 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
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 : getListeners())
{
audioSegment.incrementConsumerCount();
listener.receive(audioSegment);
}
//Decrement consumer counter for this broadcaster
audioSegment.decrementConsumerCount();
}
}

View File

@@ -1,28 +1,33 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio;
import io.github.dsheirer.sample.ConversionUtils;
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.Float;
import java.lang.Math;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
public class AudioUtils
@@ -32,18 +37,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 +57,67 @@ 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();
}
/**
* Normalize audio samples
*/
public static List<float[]> normalize(List<float[]> audioBuffers)
{
float targetMax = 0.95f; // -0.44dBFS
float max = 0.1f; // Initialize at 0.1 (-20dBFS) to limit maximum gain
for(float[] audioBuffer: audioBuffers)
{
for(float value: audioBuffer)
{
float newMax = Math.max(max, Math.abs(value));
if(!Float.isNaN(newMax))
{
max = newMax;
}
}
}
if(targetMax == max)
{
return audioBuffers;
}
float gain = targetMax / max;
for(float[] audioBuffer: audioBuffers)
{
for(int i=0;i<audioBuffer.length;i++)
{
audioBuffer[i] *= gain;
}
}
return audioBuffers;
}
}

View File

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

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

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