Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#172 Tuner Supports Adjusting Min/Max Frequencies #1850

Merged
merged 1 commit into from
Feb 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading