-
Notifications
You must be signed in to change notification settings - Fork 260
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#172 Allow users to adjust tuner min/max frequency ranges so that the…
…y can ensure tuners stay put and support channels in a predicatable manner. (#1850) Co-authored-by: Dennis Sheirer <[email protected]>
- Loading branch information
Showing
48 changed files
with
975 additions
and
124 deletions.
There are no files selected for viewing
192 changes: 192 additions & 0 deletions
192
src/main/java/io/github/dsheirer/gui/control/FrequencyTextField.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
src/main/java/io/github/dsheirer/gui/control/LongFormatter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.