Skip to content

Commit

Permalink
#172 Allow users to adjust tuner min/max frequency ranges so that the…
Browse files Browse the repository at this point in the history
…y can ensure tuners stay put and support channels in a predicatable manner. (#1850)

Co-authored-by: Dennis Sheirer <[email protected]>
  • Loading branch information
DSheirer and Dennis Sheirer authored Feb 11, 2024
1 parent 2c32096 commit 73275aa
Show file tree
Hide file tree
Showing 48 changed files with 975 additions and 124 deletions.
192 changes: 192 additions & 0 deletions src/main/java/io/github/dsheirer/gui/control/FrequencyTextField.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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.gui.control;

import java.awt.EventQueue;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.DocumentFilter;
import javax.swing.text.PlainDocument;

/**
* Swing text field control for entering frequency (MHz) values.
*/
public class FrequencyTextField extends JTextField
{
private static final Logger LOGGER = LoggerFactory.getLogger(FrequencyTextField.class);
private static final String REGEX = "[1-9][0-9]{0,3}(\\.[0-9]{0,6})?";
private double mMinimum;
private double mMaximum;

/**
* Constructs an instance
*
* @param minimum allowable frequency value in Hertz
* @param maximum allowable frequency value in Hertz
* @param current frequency value in Hertz
*/
public FrequencyTextField(long minimum, long maximum, long current)
{
super(8);
mMinimum = minimum / 1E6d;
mMaximum = maximum / 1E6d;

PlainDocument document = (PlainDocument)this.getDocument();
document.setDocumentFilter(new FrequencyFilter());
setFrequency(current);
}

/**
* Current frequency value
* @return frequency in Hertz
*/
public long getFrequency()
{
String value = getText();

if(value == null || value.isEmpty())
{
return 0;
}

try
{
return (long)(Double.parseDouble(getText()) * 1E6d);
}
catch(Exception e)
{
LOGGER.error("Unable to parse frequency value from text [" + value + "] " + e.getLocalizedMessage());
}

return 0;
}

/**
* Sets the current frequency value
* @param frequency in Hertz
*/
public void setFrequency(long frequency)
{
double frequencyMHz = frequency / 1E6d;

if(isValid(String.valueOf(frequencyMHz)))
{
setText(String.valueOf(frequencyMHz));
}
else
{
LOGGER.warn("Can't set frequency [" + frequency + "Hz / " + frequencyMHz + "MHz] with current minimum [" + mMinimum + "MHz] and maximum [" + mMaximum + "MHz] limits");
}
}

/**
* Indicates if the value is a valid double value that is between the minimum and maximum extents.
* @param value to test
* @return true if it is valid.
*/
private boolean isValid(String value)
{
if(value == null || value.isEmpty())
{
return true;
}

if(value.matches(REGEX))
{
try
{
double frequency = Double.parseDouble(value);
return mMinimum <= frequency && frequency <= mMaximum;
}
catch(NumberFormatException e)
{
return false;
}
}

return false;
}

/**
* Input filter for user entered values.
*/
class FrequencyFilter extends DocumentFilter
{
@Override
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException
{
Document doc = fb.getDocument();
StringBuilder sb = new StringBuilder();
sb.append(doc.getText(0, doc.getLength()));
sb.insert(offset, string);

if(isValid(sb.toString()))
{
super.insertString(fb, offset, string, attr);
}
}

@Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException
{
Document doc = fb.getDocument();
StringBuilder sb = new StringBuilder();
sb.append(doc.getText(0, doc.getLength()));
sb.replace(offset, offset + length, text);

if(isValid(sb.toString()))
{
super.replace(fb, offset, length, text, attrs);
}
}

@Override
public void remove(FilterBypass fb, int offset, int length) throws BadLocationException
{
Document doc = fb.getDocument();
StringBuilder sb = new StringBuilder();
sb.append(doc.getText(0, doc.getLength()));
sb.delete(offset, offset + length);

if(isValid(sb.toString()))
{
super.remove(fb, offset, length);
}
}
}

public static void main(String[] args)
{
JFrame frame = new JFrame("Frequency Control Test");
frame.setSize(300, 200);
FrequencyTextField ftf = new FrequencyTextField(20, 2_000_000_000, 101_100_000);
frame.setLayout(new MigLayout());
frame.add(ftf);

EventQueue.invokeLater(() -> frame.setVisible(true));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2024 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
Expand All @@ -19,20 +19,18 @@

package io.github.dsheirer.gui.control;

import io.github.dsheirer.gui.playlist.alias.AliasItemEditor;
import java.util.function.UnaryOperator;
import javafx.scene.control.TextFormatter;
import javafx.util.converter.IntegerStringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.function.UnaryOperator;

/**
* Text formatter for integer values that constrains values to specified minimum and maximum valid values.
*/
public class IntegerFormatter extends TextFormatter<Integer>
{
private static final Logger mLog = LoggerFactory.getLogger(AliasItemEditor.class);
private static final Logger mLog = LoggerFactory.getLogger(IntegerFormatter.class);

/**
* Constructs an instance
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2024 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
Expand Down Expand Up @@ -434,8 +434,7 @@ private void set(long amount, boolean fireChangeEvent)

if(se instanceof InvalidFrequencyException ife)
{
JOptionPane.showMessageDialog(this, "Frequency [" + ife.getInvalidFrequency() +
"] exceeds the frequency limit [" + ife.getValidFrequency() + "] for this tuner.");
JOptionPane.showMessageDialog(this, ife.getMessage() + " for this tuner.");
}
}
}
Expand Down
111 changes: 111 additions & 0 deletions src/main/java/io/github/dsheirer/gui/control/LongFormatter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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.gui.control;

import java.util.function.UnaryOperator;
import javafx.scene.control.TextFormatter;
import javafx.util.converter.LongStringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Text formatter for long values that constrains values to specified minimum and maximum valid values.
*/
public class LongFormatter extends TextFormatter<Long>
{
private static final Logger mLog = LoggerFactory.getLogger(LongFormatter.class);

/**
* Constructs an instance
* @param minimum allowed value
* @param maximum allowed value
*/
public LongFormatter(int minimum, int maximum)
{
super(new LongStringConverter(), null, new LongFilter(minimum, maximum));
}

/**
* Formatted text change filter that only allows hexadecimal characters where the converted decimal value
* is also constrained within minimum and maximum valid values.
*/
public static class LongFilter implements UnaryOperator<Change>
{
private String DECIMAL_REGEX = "\\-?[0-9].*";
private int mMinimum;
private int mMaximum;

/**
* Constructs an instance
* @param minimum value
* @param maximum value
*/
public LongFilter(int minimum, int maximum)
{
mMinimum = minimum;
mMaximum = maximum;
}

/**
* Indicates if the value argument is parsable as an integer, or is empty or null.
*/
private boolean isValid(String value)
{
if(value == null || value.isEmpty())
{
return true;
}

try
{
long parsed = Long.parseLong(value);
return mMinimum <= parsed && parsed <= mMaximum;
}
catch(Exception e)
{
//no-op
}

return false;
}

@Override
public Change apply(Change change)
{
//Only validate if the user added text to the control. Otherwise, allow it to go through
if(change.getText() != null)
{
String updatedText = change.getControlNewText();

if(updatedText == null || updatedText.isEmpty())
{
return change;
}

if(!updatedText.matches(DECIMAL_REGEX) || !isValid(updatedText))
{
return null;
}
}

return change;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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
Expand All @@ -14,8 +14,8 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
* ****************************************************************************
*/
package io.github.dsheirer.source;

public class InvalidFrequencyException extends SourceException
Expand Down Expand Up @@ -54,11 +54,4 @@ public long getValidFrequency()
{
return mValidFrequency;
}

@Override
public String getMessage()
{
return super.getMessage() + " Invalid Frequency: " + getInvalidFrequency() +
" - current valid frequency:" + getValidFrequency();
}
}
Loading

0 comments on commit 73275aa

Please sign in to comment.