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

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

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

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

View File

@@ -26,11 +26,12 @@ public enum PreferenceType
{
DECODE_EVENT,
DIRECTORY,
IDENTIFIER,
JMBE_LIBRARY,
MULTI_FREQUENCY,
PLAYLIST,
PLAYBACK,
RADIO_REFERENCE,
RECORD,
TALKGROUP_FORMAT,
TUNER;
}

View File

@@ -24,6 +24,7 @@ import io.github.dsheirer.preference.decoder.JmbeLibraryPreference;
import io.github.dsheirer.preference.directory.DirectoryPreference;
import io.github.dsheirer.preference.event.DecodeEventPreference;
import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference;
import io.github.dsheirer.preference.javafx.JavaFxPreferences;
import io.github.dsheirer.preference.playback.PlaybackPreference;
import io.github.dsheirer.preference.playlist.PlaylistPreference;
import io.github.dsheirer.preference.radioreference.RadioReferencePreference;
@@ -32,7 +33,6 @@ import io.github.dsheirer.preference.source.ChannelMultiFrequencyPreference;
import io.github.dsheirer.preference.source.TunerPreference;
import io.github.dsheirer.preference.swing.SwingPreference;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.mixer.MixerManager;
/**
* User Preferences. A collection of preferences that can be accessed by preference type.
@@ -63,6 +63,7 @@ public class UserPreferences implements Listener<PreferenceType>
private TalkgroupFormatPreference mTalkgroupFormatPreference;
private TunerPreference mTunerPreference;
private SwingPreference mSwingPreference = new SwingPreference();
private JavaFxPreferences mJavaFxPreferences = new JavaFxPreferences();
/**
* Constructs a new user preferences instance
@@ -72,6 +73,14 @@ public class UserPreferences implements Listener<PreferenceType>
loadPreferenceTypes();
}
/**
* Java FX window management preferences
*/
public JavaFxPreferences getJavaFxPreferences()
{
return mJavaFxPreferences;
}
/**
* Decode Event preferences
*/

View File

@@ -20,6 +20,8 @@
package io.github.dsheirer.preference.identifier;
import java.util.EnumSet;
/**
* Options for formatting of integer values.
*/
@@ -40,4 +42,7 @@ public enum IntegerFormat
{
return mLabel;
}
public static EnumSet<IntegerFormat> DECIMAL_HEXADECIMAL = EnumSet.of(DECIMAL, HEXADECIMAL);
public static EnumSet<IntegerFormat> DECIMAL_FORMATTED = EnumSet.of(DECIMAL, FORMATTED);
}

View File

@@ -42,6 +42,7 @@ import org.slf4j.LoggerFactory;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.prefs.Preferences;
/**
@@ -110,7 +111,7 @@ public class TalkgroupFormatPreference extends Preference
@Override
public PreferenceType getPreferenceType()
{
return PreferenceType.IDENTIFIER;
return PreferenceType.TALKGROUP_FORMAT;
}
/**
@@ -139,52 +140,54 @@ public class TalkgroupFormatPreference extends Preference
/**
* Default format for each protocol
*/
private IntegerFormat getDefaultFormat(Protocol protocol)
public static IntegerFormat getDefaultFormat(Protocol protocol)
{
switch(protocol)
{
case APCO25:
return IntegerFormat.DECIMAL;
case FLEETSYNC:
return IntegerFormat.FORMATTED;
case LTR:
return IntegerFormat.FORMATTED;
case LTR_NET:
return IntegerFormat.FORMATTED;
case LTR_STANDARD:
return IntegerFormat.FORMATTED;
case MDC1200:
return IntegerFormat.DECIMAL;
case MPT1327:
return IntegerFormat.FORMATTED;
case APCO25:
case MDC1200:
case PASSPORT:
return IntegerFormat.DECIMAL;
case UNKNOWN:
default:
return IntegerFormat.DECIMAL;
}
}
public static Set<IntegerFormat> getFormats(Protocol protocol)
{
switch(protocol)
{
case FLEETSYNC:
case LTR:
case LTR_NET:
case MPT1327:
return IntegerFormat.DECIMAL_FORMATTED;
case APCO25:
case MDC1200:
case PASSPORT:
case UNKNOWN:
default:
return IntegerFormat.DECIMAL_HEXADECIMAL;
}
}
private boolean getDefaultFixedWidth(Protocol protocol)
{
switch(protocol)
{
case APCO25:
return true;
case FLEETSYNC:
return true;
case LTR:
return true;
case LTR_NET:
return true;
case LTR_STANDARD:
return true;
case MDC1200:
return false;
case MPT1327:
return true;
case PASSPORT:
return false;
case APCO25:
case FLEETSYNC:
case LTR:
case LTR_NET:
case MPT1327:
case UNKNOWN:
default:
return true;
@@ -215,12 +218,19 @@ public class TalkgroupFormatPreference extends Preference
*/
public IntegerFormat getTalkgroupFormat(Protocol protocol)
{
IntegerFormat mapValue = mTalkgroupFormatProtocolMap.get(protocol);
if (mapValue != null) {
return mapValue;
IntegerFormat format = null;
if(mTalkgroupFormatProtocolMap.containsKey(protocol))
{
format = mTalkgroupFormatProtocolMap.get(protocol);
}
return IntegerFormat.DECIMAL;
if(format == null || !getFormats(protocol).contains(format))
{
format = getDefaultFormat(protocol);
}
return format;
}
/**
@@ -304,12 +314,9 @@ public class TalkgroupFormatPreference extends Preference
case FLEETSYNC:
return FleetsyncTalkgroupFormatter.format(talkgroupIdentifier, getTalkgroupFormat(Protocol.FLEETSYNC),
isTalkgroupFixedWidth(Protocol.FLEETSYNC));
case LTR_STANDARD:
return LTRTalkgroupFormatter.format(talkgroupIdentifier, getTalkgroupFormat(Protocol.LTR_STANDARD),
isTalkgroupFixedWidth(Protocol.LTR_STANDARD));
case LTR_NET:
return LTRTalkgroupFormatter.format(talkgroupIdentifier, getTalkgroupFormat(Protocol.LTR_NET),
isTalkgroupFixedWidth(Protocol.LTR_NET));
case LTR:
return LTRTalkgroupFormatter.format(talkgroupIdentifier, getTalkgroupFormat(Protocol.LTR),
isTalkgroupFixedWidth(Protocol.LTR));
case MDC1200:
return MDC1200TalkgroupFormatter.format(talkgroupIdentifier, getTalkgroupFormat(Protocol.MDC1200),
isTalkgroupFixedWidth(Protocol.MDC1200));
@@ -355,6 +362,9 @@ public class TalkgroupFormatPreference extends Preference
case APCO25:
return APCO25TalkgroupFormatter.format(radioIdentifier, getTalkgroupFormat(Protocol.APCO25),
isTalkgroupFixedWidth(Protocol.APCO25));
case PASSPORT:
return PassportTalkgroupFormatter.format(radioIdentifier, getTalkgroupFormat(Protocol.PASSPORT),
isTalkgroupFixedWidth(Protocol.PASSPORT));
default:
return radioIdentifier.toString();
}

View File

@@ -28,7 +28,7 @@ import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.preference.identifier.IntegerFormat;
public class APCO25TalkgroupFormatter extends IntegerFormatter
public class APCO25TalkgroupFormatter extends AbstractIntegerFormatter
{
public static final int GROUP_DECIMAL_WIDTH = 5;
public static final int UNIT_DECIMAL_WIDTH = 8;
@@ -131,4 +131,18 @@ public class APCO25TalkgroupFormatter extends IntegerFormatter
}
}
@Override
public String format(int value, IntegerFormat integerFormat)
{
switch(integerFormat)
{
case DECIMAL:
case FORMATTED:
return format(value);
case HEXADECIMAL:
return toHex(value);
default:
throw new IllegalArgumentException("Unrecognized integer format: " + integerFormat);
}
}
}

View File

@@ -20,6 +20,7 @@
package io.github.dsheirer.preference.identifier.talkgroup;
import io.github.dsheirer.preference.identifier.IntegerFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -28,9 +29,9 @@ import java.text.ParseException;
/**
* Base formatter class for integer values
*/
public class IntegerFormatter
public abstract class AbstractIntegerFormatter
{
private final static Logger mLog = LoggerFactory.getLogger(IntegerFormatter.class);
private final static Logger mLog = LoggerFactory.getLogger(AbstractIntegerFormatter.class);
public static String toHex(int value)
{
@@ -57,6 +58,8 @@ public class IntegerFormatter
return String.valueOf(value);
}
public abstract String format(int value, IntegerFormat integerFormat);
/**
* Parses an integer value from the formatted string value
*

View File

@@ -33,7 +33,7 @@ import java.util.regex.Pattern;
/**
* Formats Fleetsync 20-bit identifiers into either an integer value or an 8-bit fleet and 12-bit ident
*/
public class FleetsyncTalkgroupFormatter extends IntegerFormatter
public class FleetsyncTalkgroupFormatter extends AbstractIntegerFormatter
{
private final static Logger mLog = LoggerFactory.getLogger(FleetsyncTalkgroupFormatter.class);
private static final int IDENTIFIER_DECIMAL_WIDTH = 7;
@@ -133,6 +133,13 @@ public class FleetsyncTalkgroupFormatter extends IntegerFormatter
throw new ParseException("Error parsing value from fleetsync talkgroup [" + formattedTalkgroup + "]", 0);
}
@Override
public String format(int value, IntegerFormat format)
{
return format(value);
}
/**
* Prefix for the specified talkgroup value
* @param talkgroup value containing a prefix

View File

@@ -33,7 +33,7 @@ import java.util.regex.Pattern;
/**
* Formats LTR identifiers
*/
public class LTRTalkgroupFormatter extends IntegerFormatter
public class LTRTalkgroupFormatter extends AbstractIntegerFormatter
{
private final static Logger mLog = LoggerFactory.getLogger(LTRTalkgroupFormatter.class);
private static final int IDENTIFIER_DECIMAL_WIDTH = 4;
@@ -41,7 +41,7 @@ public class LTRTalkgroupFormatter extends IntegerFormatter
private static final int HOME_REPEATER_DECIMAL_WIDTH = 2;
private static final int TALKGROUP_DECIMAL_WIDTH = 3;
private static final String SEPARATOR = "-";
private static final Pattern LTR_PATTERN = Pattern.compile("(\\d{1,2})-(\\d{1,3})");
private static final Pattern LTR_PATTERN = Pattern.compile("([01])-(\\d{1,2})-(\\d{1,3})");
/**
* Formats the group identifier to the specified format and width.
@@ -59,7 +59,8 @@ public class LTRTalkgroupFormatter extends IntegerFormatter
case DECIMAL:
return toDecimal(ltr.getValue(), IDENTIFIER_DECIMAL_WIDTH);
case FORMATTED:
return toDecimal(ltr.getHomeChannel(), HOME_REPEATER_DECIMAL_WIDTH) + SEPARATOR +
return ltr.getArea() + SEPARATOR +
toDecimal(ltr.getHomeChannel(), HOME_REPEATER_DECIMAL_WIDTH) + SEPARATOR +
toDecimal(ltr.getTalkgroup(), TALKGROUP_DECIMAL_WIDTH);
case HEXADECIMAL:
return toHex(ltr.getValue(), IDENTIFIER_HEXADECIMAL_WIDTH);
@@ -74,7 +75,7 @@ public class LTRTalkgroupFormatter extends IntegerFormatter
case DECIMAL:
return ltr.toString();
case FORMATTED:
return ltr.getHomeChannel() + SEPARATOR + ltr.getTalkgroup();
return ltr.getArea() + SEPARATOR + ltr.getHomeChannel() + SEPARATOR + ltr.getTalkgroup();
case HEXADECIMAL:
return toHex(identifier.getValue());
default:
@@ -97,11 +98,19 @@ public class LTRTalkgroupFormatter extends IntegerFormatter
if(m.matches())
{
String rawLcn = m.group(1);
String rawGroup = m.group(2);
String rawArea = m.group(1);
String rawLcn = m.group(2);
String rawGroup = m.group(3);
try
{
int area = Integer.parseInt(rawArea);
if(area < 0 || area > 1)
{
throw new ParseException("LTR area must be in range 0-1. Error parsing [" + formattedTalkgroup + "]", 0);
}
int lcn = Integer.parseInt(rawLcn);
if(lcn < 1 || lcn > 20)
@@ -116,7 +125,7 @@ public class LTRTalkgroupFormatter extends IntegerFormatter
throw new ParseException("LTR talkgroup must be in range 1-255. Error parsing [" + formattedTalkgroup + "]", 0);
}
return (lcn << 8) + group;
return (area << 13) + (lcn << 8) + group;
}
catch(Exception e)
{
@@ -128,6 +137,18 @@ public class LTRTalkgroupFormatter extends IntegerFormatter
throw new ParseException("Error parsing LTR talkgroup [" + formattedTalkgroup + "]", 0);
}
@Override
public String format(int value, IntegerFormat format)
{
switch(format)
{
case DECIMAL:
case FORMATTED:
case HEXADECIMAL:
default:
return format(value);
}
}
/**
* Formats the talkgroup as a string
@@ -136,8 +157,19 @@ public class LTRTalkgroupFormatter extends IntegerFormatter
*/
public String format(int talkgroup)
{
return toDecimal(getLcn(talkgroup), HOME_REPEATER_DECIMAL_WIDTH) + SEPARATOR +
toDecimal(getTalkgroup(talkgroup), TALKGROUP_DECIMAL_WIDTH);
return getArea(talkgroup) + SEPARATOR +
toDecimal(getLcn(talkgroup), HOME_REPEATER_DECIMAL_WIDTH) + SEPARATOR +
toDecimal(getTalkgroup(talkgroup), TALKGROUP_DECIMAL_WIDTH);
}
/**
* Area value for the talkgroup, 0 or 1
* @param value of talkgroup
* @return area code
*/
public static int getArea(int value)
{
return (value >> 13) & 0x1;
}
/**

View File

@@ -23,7 +23,7 @@ package io.github.dsheirer.preference.identifier.talkgroup;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.preference.identifier.IntegerFormat;
public class MDC1200TalkgroupFormatter extends IntegerFormatter
public class MDC1200TalkgroupFormatter extends AbstractIntegerFormatter
{
public static final int DECIMAL_WIDTH = 5;
public static final int HEXADECIMAL_WIDTH = 4;
@@ -60,4 +60,19 @@ public class MDC1200TalkgroupFormatter extends IntegerFormatter
}
}
}
@Override
public String format(int value, IntegerFormat integerFormat)
{
switch(integerFormat)
{
case DECIMAL:
case FORMATTED:
return format(value);
case HEXADECIMAL:
return toHex(value);
default:
throw new IllegalArgumentException("Unrecognized integer format: " + integerFormat);
}
}
}

View File

@@ -33,7 +33,7 @@ import java.util.regex.Pattern;
/**
* Formats Fleetsync 20-bit identifiers into either an integer value or an 8-bit fleet and 12-bit ident
*/
public class MPT1327TalkgroupFormatter extends IntegerFormatter
public class MPT1327TalkgroupFormatter extends AbstractIntegerFormatter
{
private final static Logger mLog = LoggerFactory.getLogger(MPT1327TalkgroupFormatter.class);
@@ -141,6 +141,13 @@ public class MPT1327TalkgroupFormatter extends IntegerFormatter
return toDecimal(getPrefix(talkgroup), FLEET_DECIMAL_WIDTH) + SEPARATOR + toDecimal(getIdent(talkgroup), IDENT_DECIMAL_WIDTH);
}
@Override
public String format(int value, IntegerFormat integerFormat)
{
//Always use the fixed width format
return format(value);
}
/**
* Prefix for the specified talkgroup value
* @param talkgroup value containing a prefix

View File

@@ -20,13 +20,17 @@
package io.github.dsheirer.preference.identifier.talkgroup;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.preference.identifier.IntegerFormat;
public class PassportTalkgroupFormatter extends IntegerFormatter
public class PassportTalkgroupFormatter extends AbstractIntegerFormatter
{
public static final int DECIMAL_WIDTH = 5;
public static final int HEXADECIMAL_WIDTH = 4;
public static final int GROUP_DECIMAL_WIDTH = 5;
public static final int GROUP_HEXADECIMAL_WIDTH = 4;
public static final int UNIT_DECIMAL_WIDTH = 7;
public static final int UNIT_HEXADECIMAL_WIDTH = 6;
/**
* Formats the individual or group identifier to the specified format and width.
@@ -39,9 +43,9 @@ public class PassportTalkgroupFormatter extends IntegerFormatter
{
case DECIMAL:
case FORMATTED:
return toDecimal(identifier.getValue(), DECIMAL_WIDTH);
return toDecimal(identifier.getValue(), GROUP_DECIMAL_WIDTH);
case HEXADECIMAL:
return toHex(identifier.getValue(), HEXADECIMAL_WIDTH);
return toHex(identifier.getValue(), GROUP_HEXADECIMAL_WIDTH);
default:
throw new IllegalArgumentException("Unrecognized integer format: " + format);
}
@@ -60,4 +64,52 @@ public class PassportTalkgroupFormatter extends IntegerFormatter
}
}
}
/**
* Formats the individual or group identifier to the specified format and width.
*/
public static String format(RadioIdentifier identifier, IntegerFormat format, boolean fixedWidth)
{
if(fixedWidth)
{
switch(format)
{
case DECIMAL:
case FORMATTED:
return toDecimal(identifier.getValue(), UNIT_DECIMAL_WIDTH);
case HEXADECIMAL:
return toHex(identifier.getValue(), UNIT_HEXADECIMAL_WIDTH);
default:
throw new IllegalArgumentException("Unrecognized integer format: " + format);
}
}
else
{
switch(format)
{
case DECIMAL:
case FORMATTED:
return identifier.getValue().toString();
case HEXADECIMAL:
return toHex(identifier.getValue());
default:
throw new IllegalArgumentException("Unrecognized integer format: " + format);
}
}
}
@Override
public String format(int value, IntegerFormat format)
{
switch(format)
{
case DECIMAL:
case FORMATTED:
return format(value);
case HEXADECIMAL:
return toHex(value);
default:
throw new IllegalArgumentException("Unrecognized integer format: " + format);
}
}
}

View File

@@ -0,0 +1,31 @@
/*
* *****************************************************************************
* 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.preference.identifier.talkgroup;
import io.github.dsheirer.preference.identifier.IntegerFormat;
public class UnknownTalkgroupFormatter extends AbstractIntegerFormatter
{
@Override
public String format(int value, IntegerFormat integerFormat)
{
return format(value);
}
}

View File

@@ -0,0 +1,171 @@
/*
* *****************************************************************************
* 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.preference.javafx;
import io.github.dsheirer.preference.decoder.JmbeLibraryPreference;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.stage.Screen;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.prefs.Preferences;
/**
* Manages user preferences for JavaFX elements (e.g. stages).
*/
public class JavaFxPreferences
{
private final static Logger mLog = LoggerFactory.getLogger(JmbeLibraryPreference.class);
private static final String PREFERENCE_STAGE_HEIGHT_PREFIX = "stage.height.";
private static final String PREFERENCE_STAGE_WIDTH_PREFIX = "stage.width.";
private static final String PREFERENCE_STAGE_X_PREFIX = "stage.x.";
private static final String PREFERENCE_STAGE_Y_PREFIX = "stage.y.";
private Preferences mPreferences = Preferences.userNodeForPackage(JavaFxPreferences.class);
private List<StageMonitor> mStageMonitors = new ArrayList<>();
public JavaFxPreferences()
{
}
/**
* Monitors a stage size and location so that it can be restored to the same location on application startup.
* @param stage to monitor
* @param key to label corresponding preference settings
*/
public void monitor(Stage stage, String key)
{
mStageMonitors.remove(new StageMonitor(stage, key));
}
/**
* Removes monitoring for the specified stage
*/
public void unmonitor(Stage stage)
{
Iterator<StageMonitor> it = mStageMonitors.iterator();
while(it.hasNext())
{
StageMonitor next = it.next();
if(next.equals(stage))
{
it.remove();
next.dispose();
}
}
}
/**
* Monitors a stage's coordinates and stores location. On construction, applies stored coordinates to the stage.
*/
public class StageMonitor
{
private Stage mStage;
private String mKey;
private CoordinateMonitor mX;
private CoordinateMonitor mY;
private CoordinateMonitor mHeight;
private CoordinateMonitor mWidth;
public StageMonitor(Stage stage, String key)
{
mStage = stage;
mKey = key;
//Position the stage before we add the coordinate monitoring
reposition(stage);
mX = new CoordinateMonitor(PREFERENCE_STAGE_X_PREFIX + key);
mY = new CoordinateMonitor(PREFERENCE_STAGE_Y_PREFIX + key);
mHeight = new CoordinateMonitor(PREFERENCE_STAGE_HEIGHT_PREFIX + key);
mWidth = new CoordinateMonitor(PREFERENCE_STAGE_WIDTH_PREFIX + key);
mStage.xProperty().addListener(mX);
mStage.yProperty().addListener(mY);
mStage.heightProperty().addListener(mHeight);
mStage.widthProperty().addListener(mWidth);
}
public void dispose()
{
mStage.xProperty().removeListener(mX);
mStage.yProperty().removeListener(mY);
mStage.heightProperty().removeListener(mHeight);
mStage.widthProperty().removeListener(mWidth);
mStage = null;
}
/**
* Repositions a stage to the last stored display location if there is currently a monitor that can display
* those coordinates. Otherwise, uses the stage's default coordinates.
*
* @param stage to reposition
*/
private void reposition(Stage stage)
{
double x = mPreferences.getDouble(PREFERENCE_STAGE_X_PREFIX + mKey, stage.getX());
double y = mPreferences.getDouble(PREFERENCE_STAGE_Y_PREFIX + mKey, stage.getY());
double height = mPreferences.getDouble(PREFERENCE_STAGE_HEIGHT_PREFIX + mKey, stage.getHeight());
double width = mPreferences.getDouble(PREFERENCE_STAGE_WIDTH_PREFIX + mKey, stage.getWidth());
//If there is a screen (ie monitor) available to display the stage at these coordinates, then move the
// stage, otherwise let the stage display in the default coordinates
ObservableList<Screen> screens = Screen.getScreensForRectangle(x, y, width, height);
if(!screens.isEmpty())
{
stage.setX(x);
stage.setY(y);
stage.setHeight(height);
stage.setWidth(width);
}
}
}
/**
* Monitors a stage coordinate and stores the value to user preferences as it changes.
*/
public class CoordinateMonitor implements ChangeListener<Number>
{
private String mKey;
public CoordinateMonitor(String key)
{
mKey = key;
}
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue)
{
if(newValue != null)
{
mPreferences.putDouble(mKey, newValue.doubleValue());
}
}
}
}

View File

@@ -1,25 +1,29 @@
/*
* ******************************************************************************
* 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-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 distributed in the hope that it will be useful,
* but WITHOUT 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.preference.playlist;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import io.github.dsheirer.preference.Preference;
import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.preference.directory.DirectoryPreference;
@@ -30,6 +34,8 @@ import org.slf4j.LoggerFactory;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.prefs.Preferences;
/**
@@ -43,13 +49,9 @@ public class PlaylistPreference extends Preference
private static final String FILE_PLAYLIST = "default.xml";
private static final String FILE_LEGACY_PLAYLIST = "playlist_v2.xml";
private static final String PREFERENCE_KEY_PLAYLIST_LAST_ACCESSED = "playlist.last.accessed";
private static final String PREFERENCE_KEY_PLAYLIST_STARTUP = "playlist.default";
private static final String PREFERENCE_KEY_PLAYLIST_USE_LAST_ACCESSED = "playlist.use.last.accessed";
private Path mPlaylistFolder;
private Path mPlaylistStartupPath;
private Path mPlaylistLastAccessedPath;
private Boolean mUsePlaylistLastAccessedByDefault = true;
private static final String PREFERENCE_KEY_PLAYLIST = "playlist.path";
private static final String PREFERENCE_KEY_PLAYLIST_LIST = "playlist.list";
private Path mPlaylistPath;
private DirectoryPreference mDirectoryPreference;
/**
@@ -65,23 +67,20 @@ public class PlaylistPreference extends Preference
@Override
public PreferenceType getPreferenceType()
{
return PreferenceType.DIRECTORY;
return PreferenceType.PLAYLIST;
}
/**
* Path to the current playlist
*/
public Path getPlaylist()
{
if(mUsePlaylistLastAccessedByDefault)
if(mPlaylistPath == null)
{
return getPlaylistLastAccessedPath();
}
else
{
return getPlaylistStartupPath();
mPlaylistPath = getPath(PREFERENCE_KEY_PLAYLIST, getDefaultPlaylistPath());
}
return mPlaylistPath;
}
/**
@@ -111,75 +110,15 @@ public class PlaylistPreference extends Preference
}
/**
* Path to the playlist to use at startup when 'use last accessed' is set to false.
* Sets the path to the playlist
*/
public Path getPlaylistStartupPath()
public void setPlaylist(Path path)
{
if(mPlaylistStartupPath == null)
{
mPlaylistStartupPath = getPath(PREFERENCE_KEY_PLAYLIST_STARTUP, getDefaultPlaylistPath());
}
return mPlaylistStartupPath;
}
/**
* Sets the path to the default playlist
*/
public void setPlaylistStartupPath(Path path)
{
mPlaylistStartupPath = path;
mPreferences.put(PREFERENCE_KEY_PLAYLIST_STARTUP, path.toString());
mPlaylistPath = path;
mPreferences.put(PREFERENCE_KEY_PLAYLIST, path.toString());
notifyPreferenceUpdated();
}
/**
* Path for the last accesssed playlist
*/
public Path getPlaylistLastAccessedPath()
{
if(mPlaylistLastAccessedPath == null)
{
mPlaylistLastAccessedPath = getPath(PREFERENCE_KEY_PLAYLIST_LAST_ACCESSED, getDefaultPlaylistPath());
}
return mPlaylistLastAccessedPath;
}
/**
* Sets the path to the last accessed playlist
*/
public void setPlaylistLastAccessedPath(Path path)
{
mPlaylistLastAccessedPath = path;
mPreferences.put(PREFERENCE_KEY_PLAYLIST_LAST_ACCESSED, path.toString());
notifyPreferenceUpdated();
}
/**
* Indicates the playlist to load on startup, either the last accessed playlist (true) or the default playlist (false).
*/
public boolean usePlaylistLastAccessedByDefault()
{
if(mUsePlaylistLastAccessedByDefault == null)
{
mUsePlaylistLastAccessedByDefault = mPreferences.getBoolean(PREFERENCE_KEY_PLAYLIST_USE_LAST_ACCESSED, true);
}
return mUsePlaylistLastAccessedByDefault;
}
/**
* Sets using the last accessed playlist by default (true) or the default playlist (false).
*/
public void setUsePlaylistLastAccessedByDefault(boolean useLastAccessed)
{
mUsePlaylistLastAccessedByDefault = useLastAccessed;
mPreferences.putBoolean(PREFERENCE_KEY_PLAYLIST_USE_LAST_ACCESSED, useLastAccessed);
notifyPreferenceUpdated();
}
/**
* Default playlist
*/
@@ -188,6 +127,60 @@ public class PlaylistPreference extends Preference
return mDirectoryPreference.getDefaultPlaylistDirectory().resolve(FILE_PLAYLIST);
}
/**
* List of known playlists
*/
public List<Path> getPlaylistList()
{
List<Path> playlists = new ArrayList<>();
String raw = mPreferences.get(PREFERENCE_KEY_PLAYLIST_LIST, null);
if(raw != null && !raw.isEmpty())
{
for(String rawPath: Splitter.on(",").split(raw))
{
Path path = Paths.get(rawPath);
if(!playlists.contains(path))
{
playlists.add(path);
}
}
}
//Check that the list contains the current playlist
boolean hasCurrent = false;
for(Path path: playlists)
{
if(path.toString().contentEquals(getPlaylist().toString()))
{
hasCurrent = true;
continue;
}
}
if(!hasCurrent)
{
playlists.add(0, getPlaylist());
}
return playlists;
}
/**
* Sets the list of known playlists
*/
public void setPlaylistList(List<Path> playlistPaths)
{
if(playlistPaths != null && !playlistPaths.isEmpty())
{
String rawList = Joiner.on(",").join(playlistPaths);
mPreferences.put(PREFERENCE_KEY_PLAYLIST_LIST, rawList);
}
}
/**
* Returns the path stored in preferences and referenced by the key argument if it exists, otherwise returns the
* default path.

View File

@@ -22,9 +22,12 @@
package io.github.dsheirer.preference.radioreference;
import io.github.dsheirer.gui.playlist.radioreference.Level;
import io.github.dsheirer.preference.Preference;
import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.rrapi.type.AuthorizationInformation;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.service.radioreference.RadioReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -36,14 +39,43 @@ import java.util.prefs.Preferences;
public class RadioReferencePreference extends Preference
{
private final static Logger mLog = LoggerFactory.getLogger(RadioReferencePreference.class);
public static final int INVALID_ID = -1;
private Preferences mPreferences = Preferences.userNodeForPackage(RadioReferencePreference.class);
private static final String STORE_CREDENTIALS = "store.credentials";
private static final String USER_NAME = "user.name";
private static final String PASSWORD = "user.authorization";
private static final String SHOW_PASSWORD = "show.password";
private static final String PREFERRED_COUNTRY_ID = "preferred.country";
private static final String PREFERRED_STATE_ID = "preferred.state";
private static final String PREFERRED_COUNTY_ID = "preferred.county";
private static final String PREFERRED_SYSTEM_ID_STATE = "preferred.system.state";
private static final String PREFERRED_SYSTEM_ID_COUNTY = "preferred.system.county";
private static final String PREFERRED_AGENCY_ID_NATIONAL = "preferred.agency.national";
private static final String PREFERRED_AGENCY_ID_STATE = "preferred.agency.state";
private static final String PREFERRED_AGENCY_ID_COUNTY = "preferred.agency.county";
private static final String SHOW_CHANNEL_EDITOR_NATIONAL = "create.and.show.editor.national";
private static final String SHOW_CHANNEL_EDITOR_STATE = "create.and.show.editor.state";
private static final String SHOW_CHANNEL_EDITOR_COUNTY = "create.and.show.editor.county";
private static final String ENCRYPTED_TALKGROUP_DO_NOT_MONITOR = "encrypted.talkgroup.import.do.not.monitor";
private static final String CREATE_AND_SHOW_CHANNEL_EDITOR = "create.and.show.channel.editor";
private String mUserName;
private String mPassword;
private Boolean mStoreCredentials;
private Boolean mShowPassword;
private Boolean mShowChannelEditorNational;
private Boolean mShowChannelEditorState;
private Boolean mShowChannelEditorCounty;
private Boolean mEncryptedTalkgroupImport;
private Boolean mCreateAndShowChannelEditor;
private int mPreferredCountryId = INVALID_ID;
private int mPreferredStateId = INVALID_ID;
private int mPreferredCountyId = INVALID_ID;
private int mPreferredAgencyIdNational = INVALID_ID;
private int mPreferredAgencyIdState = INVALID_ID;
private int mPreferredAgencyIdCounty = INVALID_ID;
private int mPreferredSystemIdState = INVALID_ID;
private int mPreferredSystemIdCounty = INVALID_ID;
/**
* Constructs an instance.
@@ -69,7 +101,7 @@ public class RadioReferencePreference extends Preference
/**
* Clears user credentials and resets username and password to null
*/
public void removeStoreCredentials()
public void removeStoredCredentials()
{
setStoreCredentials(false);
setUserName(null);
@@ -86,6 +118,23 @@ public class RadioReferencePreference extends Preference
return mPreferences.get(USER_NAME, null) != null || mPreferences.get(PASSWORD, null) != null;
}
/**
* Creates an authorization information instance with stored login credentials when available.
* @return information or null if there are no stored credentials.
*/
public AuthorizationInformation getAuthorizationInformation()
{
if(hasStoredCredentials())
{
String username = getUserName();
String password = getPassword();
return RadioReference.getAuthorizatonInformation(username, password);
}
return null;
}
/**
* User name
*/
@@ -178,4 +227,290 @@ public class RadioReferencePreference extends Preference
mPreferences.putBoolean(STORE_CREDENTIALS, store);
notifyPreferenceUpdated();
}
/**
* Indicates if the login dialog should show or mask the user password
*/
public boolean getShowPassword()
{
if(mShowPassword == null)
{
mShowPassword = mPreferences.getBoolean(SHOW_PASSWORD, false);
}
return mShowPassword;
}
/**
* Sets the show password preference
*/
public void setShowPassword(boolean show)
{
mShowPassword = show;
mPreferences.putBoolean(SHOW_PASSWORD, show);
notifyPreferenceUpdated();
}
/**
* Preferred country to use with the service
*/
public int getPreferredCountryId()
{
if(mPreferredCountryId < 0)
{
mPreferredCountryId = mPreferences.getInt(PREFERRED_COUNTRY_ID, INVALID_ID);
}
return mPreferredCountryId;
}
public void setPreferredCountryId(int countryId)
{
mPreferredCountryId = countryId;
mPreferences.putInt(PREFERRED_COUNTRY_ID, countryId);
notifyPreferenceUpdated();
}
/**
* Preferred state to use with the service
*/
public int getPreferredStateId()
{
if(mPreferredStateId < 0)
{
mPreferredStateId = mPreferences.getInt(PREFERRED_STATE_ID, INVALID_ID);
}
return mPreferredStateId;
}
public void setPreferredStateId(int state)
{
mPreferredStateId = state;
mPreferences.putInt(PREFERRED_STATE_ID, mPreferredStateId);
notifyPreferenceUpdated();
}
/**
* Preferred county to use with the service
*/
public int getPreferredCountyId()
{
if(mPreferredCountyId < 0)
{
mPreferredCountyId = mPreferences.getInt(PREFERRED_COUNTY_ID, INVALID_ID);
}
return mPreferredCountyId;
}
public void setPreferredCountyId(int county)
{
mPreferredCountyId = county;
mPreferences.putInt(PREFERRED_COUNTY_ID, mPreferredCountyId);
notifyPreferenceUpdated();
}
/**
* Preferred system to use with the service
*/
public int getPreferredSystemId(Level level)
{
switch(level)
{
case STATE:
if(mPreferredSystemIdState < 0)
{
mPreferredSystemIdState = mPreferences.getInt(PREFERRED_SYSTEM_ID_STATE, INVALID_ID);
}
return mPreferredSystemIdState;
case COUNTY:
default:
if(mPreferredSystemIdCounty < 0)
{
mPreferredSystemIdCounty = mPreferences.getInt(PREFERRED_SYSTEM_ID_COUNTY, INVALID_ID);
}
return mPreferredSystemIdCounty;
}
}
public void setPreferredSystemId(int systemId, Level level)
{
switch(level)
{
case STATE:
mPreferredSystemIdState = systemId;
mPreferences.putInt(PREFERRED_SYSTEM_ID_STATE, mPreferredSystemIdState);
notifyPreferenceUpdated();
break;
case COUNTY:
default:
mPreferredSystemIdCounty = systemId;
mPreferences.putInt(PREFERRED_SYSTEM_ID_COUNTY, mPreferredSystemIdCounty);
notifyPreferenceUpdated();
break;
}
}
/**
* Preferred country agency to use with the service
*/
public int getPreferredAgencyId(Level level)
{
switch(level)
{
case NATIONAL:
if(mPreferredAgencyIdNational < 0)
{
mPreferredAgencyIdNational = mPreferences.getInt(PREFERRED_AGENCY_ID_NATIONAL, INVALID_ID);
}
return mPreferredAgencyIdNational;
case STATE:
if(mPreferredAgencyIdState < 0)
{
mPreferredAgencyIdState = mPreferences.getInt(PREFERRED_AGENCY_ID_STATE, INVALID_ID);
}
return mPreferredAgencyIdState;
case COUNTY:
default:
if(mPreferredAgencyIdCounty < 0)
{
mPreferredAgencyIdCounty = mPreferences.getInt(PREFERRED_AGENCY_ID_COUNTY, INVALID_ID);
}
return mPreferredAgencyIdCounty;
}
}
public void setPreferredAgencyId(int agencyId, Level level)
{
switch(level)
{
case NATIONAL:
mPreferredAgencyIdNational = agencyId;
mPreferences.putInt(PREFERRED_AGENCY_ID_NATIONAL, mPreferredAgencyIdNational);
notifyPreferenceUpdated();
break;
case STATE:
mPreferredAgencyIdState = agencyId;
mPreferences.putInt(PREFERRED_AGENCY_ID_STATE, mPreferredAgencyIdState);
notifyPreferenceUpdated();
break;
case COUNTY:
default:
mPreferredAgencyIdCounty = agencyId;
mPreferences.putInt(PREFERRED_AGENCY_ID_COUNTY, mPreferredAgencyIdCounty);
notifyPreferenceUpdated();
break;
}
}
/**
* Preference for showing a channel editor after creating a national agency channel
*/
public boolean getShowChannelEditor(Level level)
{
switch(level)
{
case NATIONAL:
if(mShowChannelEditorNational == null)
{
mShowChannelEditorNational = mPreferences.getBoolean(SHOW_CHANNEL_EDITOR_NATIONAL, true);
}
return mShowChannelEditorNational;
case STATE:
if(mShowChannelEditorState == null)
{
mShowChannelEditorState = mPreferences.getBoolean(SHOW_CHANNEL_EDITOR_STATE, true);
}
return mShowChannelEditorState;
case COUNTY:
default:
if(mShowChannelEditorCounty == null)
{
mShowChannelEditorCounty = mPreferences.getBoolean(SHOW_CHANNEL_EDITOR_COUNTY, true);
}
return mShowChannelEditorCounty;
}
}
public void setShowChannelEditor(boolean show, Level level)
{
switch(level)
{
case NATIONAL:
mShowChannelEditorNational = show;
mPreferences.putBoolean(SHOW_CHANNEL_EDITOR_NATIONAL, mShowChannelEditorNational);
notifyPreferenceUpdated();
break;
case STATE:
mShowChannelEditorState = show;
mPreferences.putBoolean(SHOW_CHANNEL_EDITOR_STATE, mShowChannelEditorState);
notifyPreferenceUpdated();
break;
case COUNTY:
default:
mShowChannelEditorCounty = show;
mPreferences.putBoolean(SHOW_CHANNEL_EDITOR_COUNTY, mShowChannelEditorCounty);
notifyPreferenceUpdated();
break;
}
}
/**
* Indicates if radio reference encrypted talgkroups should be imported as Do Not Monitor priority.
* @return true if set to do not monitor
*/
public boolean isEncryptedTalkgroupDoNotMonitor()
{
if(mEncryptedTalkgroupImport == null)
{
mEncryptedTalkgroupImport = mPreferences.getBoolean(ENCRYPTED_TALKGROUP_DO_NOT_MONITOR, true);
}
return mEncryptedTalkgroupImport;
}
/**
* Set the import encrypted talkgroups as Do Not Monitor
* @param doNotMonitor true if set to do not monitor
*/
public void setEncryptedTalkgroupDoNotMonitor(boolean doNotMonitor)
{
mEncryptedTalkgroupImport = doNotMonitor;
mPreferences.putBoolean(ENCRYPTED_TALKGROUP_DO_NOT_MONITOR, doNotMonitor);
notifyPreferenceUpdated();
}
/**
* Indicates if should show channel editor after trunked channel configuration create
*/
public boolean isCreateAndShowChannelEditor()
{
if(mCreateAndShowChannelEditor == null)
{
mCreateAndShowChannelEditor = mPreferences.getBoolean(CREATE_AND_SHOW_CHANNEL_EDITOR, true);
}
return mCreateAndShowChannelEditor;
}
/**
* Sets preference to show channel editor after channel configuration create
*/
public void setCreateAndShowChannelEditor(boolean show)
{
mCreateAndShowChannelEditor = show;
mPreferences.putBoolean(CREATE_AND_SHOW_CHANNEL_EDITOR, show);
notifyPreferenceUpdated();
}
}