/*
* *****************************************************************************
* 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
* ****************************************************************************
*/
package io.github.dsheirer.gui.control;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.InvalidFrequencyException;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
public class JFrequencyControl extends JPanel implements ISourceEventProcessor
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger(JFrequencyControl.class);
private List mProcessors = new ArrayList<>();
private Color mHighlightColor = Color.YELLOW;
private long mFrequency;
private Cursor mBlankCursor;
private HashMap mDigits = new HashMap();
public JFrequencyControl()
{
init();
}
private void init()
{
setLayout(new MigLayout("insets 0 0 0 0", "[]0[]", ""));
Font font = new Font(Font.MONOSPACED, Font.BOLD, 30);
for(int x = 9; x >= 0; x--)
{
Digit digit = null;
try
{
digit = new Digit(x);
}
catch(ParseException e)
{
mLog.error("JFrequencyControl - parse exception constructing a digit - " + e);
}
if(digit != null)
{
mDigits.put(x, digit);
add(digit);
digit.setFont(font);
if(x == 6)
{
add(new JLabel(". "));
}
}
}
/**
* Create a blank cursor to use when the mouse is over the digits
*/
//Create an empty byte array
byte[] imageByte = new byte[0];
//Create image for cursor using empty array
Image cursorImage = Toolkit.getDefaultToolkit().createImage(imageByte);
mBlankCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImage, new Point(0, 0), "cursor");
revalidate();
}
@Override
public void setEnabled(boolean enabled)
{
super.setEnabled(enabled);
for(Digit digit : mDigits.values())
{
digit.setEnabled(enabled);
}
}
/**
* Receives a frequency change event invoked by another control. We don't
* rebroadcast this event, just set the control to indicate the new frequency.
*/
@Override
public void process(SourceEvent event)
{
switch(event.getEvent())
{
case NOTIFICATION_FREQUENCY_CHANGE:
EventQueue.invokeLater(() -> setFrequency(event.getValue().longValue(), false));
break;
}
}
/**
* Loads the frequency into the display and optionally fires a change event
* to all registered listeners
*/
public void setFrequency(long frequency, boolean fireChangeEvent)
{
mFrequency = frequency;
for(Digit digit : mDigits.values())
{
digit.setFrequency(frequency, fireChangeEvent);
}
}
public long getFrequency()
{
return mFrequency;
}
private void updateFrequency()
{
long frequency = 0;
for(Digit digit : mDigits.values())
{
frequency += digit.getFrequency();
}
mFrequency = frequency;
}
private void fireSourceChanged() throws SourceException
{
updateFrequency();
Iterator it = mProcessors.iterator();
SourceEvent event = SourceEvent.frequencyRequest(mFrequency);
while(it.hasNext())
{
it.next().process(event);
}
}
/**
* Adds the source event listener
* @param processor
*/
public void addListener(ISourceEventProcessor processor)
{
if(!mProcessors.contains(processor))
{
mProcessors.add(processor);
}
}
/**
* Remove all source event processors
*/
public void clearListeners()
{
mProcessors.clear();
}
/**
* Removes the source event processor
* @param processor
*/
public void removeListener(ISourceEventProcessor processor)
{
mProcessors.remove(processor);
}
public class Digit extends JTextField
{
private static final long serialVersionUID = 1L;
private int mPower = 0;
private long mValue = 0;
private Digit(int position) throws ParseException
{
super("0");
mPower = position;
setToolTipText(getTooltip(mPower));
Listener listener = new Listener();
this.addKeyListener(listener);
this.addMouseListener(listener);
this.addMouseWheelListener(listener);
}
private static String getTooltip(int power)
{
StringBuilder sb = new StringBuilder();
sb.append("Center frequency ");
switch(power)
{
case 0:
sb.append("1 Hertz");
break;
case 1:
sb.append("10 Hertz");
break;
case 2:
sb.append("100 Hertz");
break;
case 3:
sb.append("1 Kilohertz");
break;
case 4:
sb.append("10 Kilohertz");
break;
case 5:
sb.append("100 Kilohertz");
break;
case 6:
sb.append("1 Megahertz");
break;
case 7:
sb.append("10 Megahertz");
break;
case 8:
sb.append("100 Megahertz");
break;
case 9:
sb.append("1 Gigahertz");
break;
}
sb.append(" units. Type a number, use up/down arrows, or click the upper/lower digit control to adjust");
return sb.toString();
}
/**
* Sets this digit to the value of the column in frequency that corresponds
* to the power (ie column) set for this digit. Optionally, fires a
* value change event to all listeners.
*/
public void setFrequency(long frequency, boolean fireChangeEvent)
{
//Strip the digits higher than this one
long lower = frequency % (long)(FastMath.pow(10, mPower + 1));
//Set the value to int value of dividing by 10 to this power
long value = (long)(lower / (long)(FastMath.pow(10, mPower)));
set(value, fireChangeEvent);
}
public long getFrequency()
{
return mValue * (long)FastMath.pow(10, mPower);
}
public void increment()
{
increment(true);
}
public void increment(boolean fireChangeEvent)
{
if(isEnabled())
{
set(mValue + 1, fireChangeEvent);
}
}
public void decrement()
{
decrement(true);
}
public void decrement(boolean fireChangeEvent)
{
if(isEnabled())
{
set(mValue - 1, fireChangeEvent);
}
}
/**
* Convenience wrapper to change amount and fire change event
*/
private void set(long amount)
{
set(amount, true);
}
/**
* Changes the value and optionally fires change event to listeners
*/
private void set(long amount, boolean fireChangeEvent)
{
long previous = mValue;
boolean higherDigitIncremented = false;
boolean higherDigitDecremented = false;
mValue = amount;
while(mValue < 0)
{
mValue += 10;
Digit nextHigherDigit = mDigits.get(mPower + 1);
if(nextHigherDigit != null)
{
nextHigherDigit.decrement(false);
higherDigitDecremented = true;
}
}
while(mValue > 9)
{
mValue -= 10;
Digit nextHigherDigit = mDigits.get(mPower + 1);
if(nextHigherDigit != null)
{
nextHigherDigit.increment(false);
higherDigitIncremented = true;
}
}
if(fireChangeEvent)
{
try
{
fireSourceChanged();
}
catch(SourceException se)
{
mValue = previous;
if(higherDigitIncremented)
{
Digit nextHigherDigit = mDigits.get(mPower + 1);
if(nextHigherDigit != null)
{
nextHigherDigit.decrement(false);
}
}
if(higherDigitDecremented)
{
Digit nextHigherDigit = mDigits.get(mPower + 1);
if(nextHigherDigit != null)
{
nextHigherDigit.increment(false);
}
}
if(se instanceof InvalidFrequencyException)
{
InvalidFrequencyException ife = (InvalidFrequencyException)se;
JOptionPane.showMessageDialog(this, "Frequency [" + ife.getInvalidFrequency() +
"] exceeds the frequency limit [" + ife.getValidFrequency() + "] for this tuner.");
}
}
}
setText(String.valueOf(mValue));
repaint();
}
private class Listener implements KeyListener,
MouseListener,
MouseWheelListener
{
@Override
public void keyReleased(KeyEvent e)
{
int key = e.getKeyCode();
switch(key)
{
case KeyEvent.VK_0:
case KeyEvent.VK_NUMPAD0:
set(0);
Digit.this.transferFocus();
break;
case KeyEvent.VK_1:
case KeyEvent.VK_NUMPAD1:
set(1);
Digit.this.transferFocus();
break;
case KeyEvent.VK_2:
case KeyEvent.VK_NUMPAD2:
set(2);
Digit.this.transferFocus();
break;
case KeyEvent.VK_3:
case KeyEvent.VK_NUMPAD3:
set(3);
Digit.this.transferFocus();
break;
case KeyEvent.VK_4:
case KeyEvent.VK_NUMPAD4:
set(4);
Digit.this.transferFocus();
break;
case KeyEvent.VK_5:
case KeyEvent.VK_NUMPAD5:
set(5);
Digit.this.transferFocus();
break;
case KeyEvent.VK_6:
case KeyEvent.VK_NUMPAD6:
set(6);
Digit.this.transferFocus();
break;
case KeyEvent.VK_7:
case KeyEvent.VK_NUMPAD7:
set(7);
Digit.this.transferFocus();
break;
case KeyEvent.VK_8:
case KeyEvent.VK_NUMPAD8:
set(8);
Digit.this.transferFocus();
break;
case KeyEvent.VK_9:
case KeyEvent.VK_NUMPAD9:
set(9);
Digit.this.transferFocus();
break;
case KeyEvent.VK_LEFT:
Digit.this.transferFocusBackward();
break;
case KeyEvent.VK_RIGHT:
Digit.this.transferFocus();
break;
case KeyEvent.VK_UP:
increment();
break;
case KeyEvent.VK_DOWN:
decrement();
break;
case KeyEvent.VK_TAB:
break;
default:
set(mValue);
break;
}
repaint();
}
@Override
public void keyPressed(KeyEvent e)
{
}
@Override
public void keyTyped(KeyEvent e)
{
}
@Override
public void mouseClicked(MouseEvent e)
{
switch(e.getButton())
{
case MouseEvent.BUTTON1:
increment();
break;
case MouseEvent.BUTTON2:
break;
case MouseEvent.BUTTON3:
decrement();
break;
}
}
public void mousePressed(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
}
@Override
public void mouseEntered(MouseEvent e)
{
Digit.this.setBackground(mHighlightColor);
setCursor(mBlankCursor);
repaint();
}
@Override
public void mouseExited(MouseEvent e)
{
Digit.this.setBackground(Color.WHITE);
setCursor(Cursor.getDefaultCursor());
repaint();
}
@Override
public void mouseWheelMoved(MouseWheelEvent e)
{
set(mValue - e.getWheelRotation());
}
}
}
}