/** * File: GUI.java * Author: Brian Borowski * Date created: November 2000 * Date last modified: July 1, 2013 */ import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.text.DecimalFormat; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.SwingWorker; import javax.swing.border.BevelBorder; import javax.swing.border.EtchedBorder; public class GUI extends JFrame implements FocusListener, ItemListener, KeyListener { private static final long serialVersionUID = 1L; private static final int MAX_SAMPLE_SIZE = 8192; private static final DecimalFormat FREQ_FORMAT = new DecimalFormat("#,##0.00"); private static final DecimalFormat COEF_FORMAT = new DecimalFormat("#,##0.00000"); private static final DecimalFormat TIME_FORMAT = new DecimalFormat("###,##0.000"); private final ApplicationStarter applicationStarter; private final Cursor defaultCursor, waitCursor; private final FFTGraph graph; private final FFTSpectrum spectrum; private final JButton computeButton; private final JCheckBox filterBox, overlayBox, showFFTBox, showNormalBox; private final JLabel statusLabel; private final JTextArea resultsArea; private final JTextField characterField, sizeField, periodField, coefField, filterField; private char character; private double period, filterFreq = 3000.00; private int sampleSize, coefficients, numberOfHarmonics; private double[] x, y, yfft; public GUI(final ApplicationStarter appStarter) { super("Discrete FFT for Character Bit Patterns"); this.applicationStarter = appStarter; defaultCursor = new Cursor(Cursor.DEFAULT_CURSOR); waitCursor = new Cursor(Cursor.WAIT_CURSOR); characterField = new JTextField(10); characterField.addFocusListener(this); characterField.addKeyListener(this); characterField.setText("a"); sizeField = new JTextField(10); sizeField.addFocusListener(this); sizeField.addKeyListener(this); sizeField.setText("128"); periodField = new JTextField(10); periodField.addFocusListener(this); periodField.addKeyListener(this); periodField.setText("0.01"); coefField = new JTextField(10); coefField.addFocusListener(this); coefField.addKeyListener(this); coefField.setText("15"); filterField = new JTextField(10); filterField.addFocusListener(this); filterField.addKeyListener(this); filterField.setText(Double.toString(filterFreq)); final JPanel formPanel = new JPanel(); formPanel.setLayout(new GridBagLayout()); final GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(0, 5, 0, 5); gbc.gridwidth = 1; gbc.weightx = 0; gbc.fill = GridBagConstraints.HORIZONTAL; final JLabel characterLabel = new JLabel("ASCII Character:"); characterLabel.setDisplayedMnemonic('A'); characterLabel.setLabelFor(characterField); formPanel.add(characterLabel, gbc); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.weightx = 100; gbc.fill = GridBagConstraints.HORIZONTAL; formPanel.add(characterField, gbc); gbc.gridwidth = 1; gbc.weightx = 0; final JLabel sampleSizeLabel = new JLabel("Sample Size (n):"); sampleSizeLabel.setDisplayedMnemonic('S'); sampleSizeLabel.setLabelFor(sizeField); formPanel.add(sampleSizeLabel, gbc); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.weightx = 100; gbc.fill = GridBagConstraints.HORIZONTAL; formPanel.add(sizeField, gbc); gbc.gridwidth = 1; gbc.weightx = 0; final JLabel periodLabel = new JLabel("Period (sec):"); periodLabel.setDisplayedMnemonic('P'); periodLabel.setLabelFor(periodField); formPanel.add(periodLabel, gbc); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.weightx = 100; gbc.fill = GridBagConstraints.HORIZONTAL; formPanel.add(periodField, gbc); gbc.gridwidth = 1; gbc.weightx = 0; final JLabel coefLabel = new JLabel("Coefficients:"); coefLabel.setDisplayedMnemonic('e'); coefLabel.setLabelFor(coefField); formPanel.add(coefLabel, gbc); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.weightx = 100; gbc.fill = GridBagConstraints.HORIZONTAL; formPanel.add(coefField, gbc); gbc.gridwidth = 1; gbc.weightx = 0; final JLabel filterFreqLabel = new JLabel("Filter Frequency (Hz):"); filterFreqLabel.setDisplayedMnemonic('H'); filterFreqLabel.setLabelFor(filterField); formPanel.add(filterFreqLabel, gbc); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.weightx = 100; gbc.fill = GridBagConstraints.HORIZONTAL; formPanel.add(filterField, gbc); filterBox = new JCheckBox("Apply Filter"); filterBox.setMnemonic('r'); computeButton = new JButton("Compute"); computeButton.setMnemonic('C'); computeButton.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent ae) { final boolean isOK = getFieldsFromForm(); if (isOK) { (new FFTWorker()).execute(); } } }); final JPanel computePanel = new JPanel(); computePanel.setLayout(new FlowLayout()); computePanel.add(filterBox); computePanel.add(computeButton); final JPanel inputPanel = new JPanel(); inputPanel.setLayout(new BorderLayout()); inputPanel.add(formPanel, BorderLayout.NORTH); inputPanel.add(computePanel, BorderLayout.SOUTH); inputPanel.setBorder(BorderFactory.createTitledBorder( BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), "User Input")); spectrum = new FFTSpectrum(); final JPanel spectrumPanel = new JPanel(); spectrumPanel.setLayout(new GridBagLayout()); gbc.insets = new Insets(0, 5, 5, 5); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.weightx = 100; gbc.weighty = 100; gbc.fill = GridBagConstraints.BOTH; spectrumPanel.add(spectrum, gbc); spectrumPanel.setBorder(BorderFactory.createTitledBorder( BorderFactory.createEtchedBorder( EtchedBorder.LOWERED), "Spectrum (RMS Amplitudes)")); final JPanel topPanel = new JPanel(); topPanel.setLayout(new GridLayout(1, 2)); topPanel.add(inputPanel); topPanel.add(spectrumPanel); resultsArea = new JTextArea(); resultsArea.setEditable(false); resultsArea.setBackground(Color.white); resultsArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); final JPanel resultsPanel = new JPanel(); resultsPanel.setLayout(new GridBagLayout()); gbc.insets = new Insets(0, 5, 5, 5); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.weightx = 100; gbc.weighty = 100; gbc.fill = GridBagConstraints.BOTH; resultsPanel.add(new JScrollPane(resultsArea), gbc); resultsPanel.setBorder(BorderFactory.createTitledBorder( BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), "Calculations")); overlayBox = new JCheckBox("Overlay Graphs", false); overlayBox.setMnemonic('O'); overlayBox.addItemListener(this); showFFTBox = new JCheckBox("Show FFT Graph", true); showFFTBox.setMnemonic('F'); showFFTBox.addItemListener(this); showNormalBox = new JCheckBox("Show Bit Graph", true); showNormalBox.setMnemonic('B'); showNormalBox.addItemListener(this); final JPanel optionsPanel = new JPanel(); optionsPanel.setLayout(new GridBagLayout()); gbc.insets = new Insets(0, 0, 0, 0); gbc.gridwidth = 1; gbc.weightx = 0; gbc.weighty = 0; gbc.fill = GridBagConstraints.NONE; optionsPanel.add(overlayBox); optionsPanel.add(showFFTBox); gbc.gridwidth = GridBagConstraints.REMAINDER; optionsPanel.add(showNormalBox); graph = new FFTGraph(); final JPanel graphPanel = new JPanel(); graphPanel.setLayout(new GridBagLayout()); gbc.insets = new Insets(0, 5, 5, 5); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.weightx = 100; gbc.weighty = 100; gbc.fill = GridBagConstraints.BOTH; graphPanel.add(graph, gbc); gbc.weighty = 0; graphPanel.add(optionsPanel, gbc); graphPanel.setBorder(BorderFactory.createTitledBorder( BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), "Discrete Fourier Decomposition")); final JPanel centerPanel = new JPanel(); centerPanel.setLayout(new GridLayout(1, 2)); centerPanel.add(resultsPanel); centerPanel.add(graphPanel); statusLabel = new JLabel("Elapsed time: 0.0 s", JLabel.RIGHT); final JPanel statusPanel = new JPanel(); statusPanel.setLayout(new GridBagLayout()); statusPanel.setBorder(new BevelBorder(BevelBorder.LOWERED)); gbc.insets = new Insets(0, 0, 2, 5); statusPanel.add(statusLabel, gbc); final Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); contentPane.add(topPanel, BorderLayout.PAGE_START); contentPane.add(centerPanel, BorderLayout.CENTER); contentPane.add(statusPanel, BorderLayout.PAGE_END); setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); addWindowListener(new ClosingWindowListener(this)); Dimension d = new Dimension(760, 525); setMinimumSize(d); setPreferredSize(d); setLocationRelativeTo(null); setVisible(true); } private void doApplicationClosing(final JFrame parent) { if (applicationStarter != null) { applicationStarter.close(); } else { System.exit(0); } } private int getNumberOfHarmonics() { if (filterBox.isSelected()) { final double twoPi = 2 * Math.PI; final int coeffs = (int)Math.ceil((filterFreq * period - twoPi) / twoPi); if (coeffs > coefficients) { return coefficients; } return coeffs; } return coefficients; } private boolean getFieldsFromForm() { final String characterStr = characterField.getText().trim(); if (characterStr.length() == 0) { characterField.selectAll(); characterField.requestFocus(); new MessageBox(this, "Error", "ASCII character required.", MessageBox.EXCLAIM); return false; } if (characterStr.length() > 1) { characterField.selectAll(); characterField.requestFocus(); new MessageBox(this, "Error", "Enter a single ASCII character.", MessageBox.EXCLAIM); return false; } character = characterStr.charAt(0); characterField.setText(Character.toString(character)); final String sizeStr = sizeField.getText().trim(); if (sizeStr.length() == 0) { sizeField.selectAll(); sizeField.requestFocus(); new MessageBox(this, "Error", "Sample size required.", MessageBox.EXCLAIM); return false; } try { sampleSize = Integer.parseInt(sizeStr); } catch (final NumberFormatException nfe) { sizeField.selectAll(); sizeField.requestFocus(); new MessageBox(this, "Error", "Invalid numeric format.", MessageBox.EXCLAIM); return false; } if (!Utility.isPowerOfTwo(sampleSize)) { sizeField.selectAll(); sizeField.requestFocus(); new MessageBox(this, "Error", "Sample size must be a power of two.", MessageBox.EXCLAIM); return false; } if (sampleSize > MAX_SAMPLE_SIZE) { sizeField.selectAll(); sizeField.requestFocus(); new MessageBox(this, "Error", "Sample size is too large.", MessageBox.EXCLAIM); return false; } sizeField.setText(Integer.toString(sampleSize)); final String periodStr = periodField.getText().trim(); if (periodStr.length() == 0) { periodField.selectAll(); periodField.requestFocus(); new MessageBox(this, "Error", "Period required.", MessageBox.EXCLAIM); return false; } try { period = Double.parseDouble(periodStr); } catch (final NumberFormatException nfe) { periodField.selectAll(); periodField.requestFocus(); new MessageBox(this, "Error", "Invalid numeric format.", MessageBox.EXCLAIM); return false; } if (period <= 0) { periodField.selectAll(); periodField.requestFocus(); new MessageBox(this, "Error", "Period must be greater than 0.", MessageBox.EXCLAIM); return false; } periodField.setText(Double.toString(period)); final String coefficienctsStr = coefField.getText().trim(); if (coefficienctsStr.length() == 0) { coefField.selectAll(); coefField.requestFocus(); new MessageBox(this, "Error", "Number of coefficients required.", MessageBox.EXCLAIM); return false; } try { coefficients = Integer.parseInt(coefficienctsStr); } catch (final NumberFormatException nfe) { coefField.selectAll(); coefField.requestFocus(); new MessageBox(this, "Error", "Invalid numeric format.", MessageBox.EXCLAIM); return false; } if (coefficients < 1) { coefField.selectAll(); coefField.requestFocus(); new MessageBox(this, "Error", "Number of coefficients must be at least 1.", MessageBox.EXCLAIM); return false; } coefField.setText(Integer.toString(coefficients)); if (filterBox.isSelected()) { final String filterStr = filterField.getText().trim(); if (filterStr.length() == 0) { filterField.selectAll(); filterField.requestFocus(); new MessageBox(this, "Error", "Filter frequency required.", MessageBox.EXCLAIM); return false; } try { filterFreq = Double.parseDouble(filterStr); } catch (final NumberFormatException nfe) { filterField.selectAll(); filterField.requestFocus(); new MessageBox(this, "Error", "Invalid numeric format.", MessageBox.EXCLAIM); return false; } if (filterFreq < 1) { filterField.selectAll(); filterField.requestFocus(); new MessageBox(this, "Error", "Filter frequency must be at least 1.", MessageBox.EXCLAIM); return false; } filterField.setText(Double.toString(filterFreq)); } if (coefficients > sampleSize / 2) { coefField.selectAll(); coefField.requestFocus(); new MessageBox(this, "Error", "Coefficients must be less than / equal to " + "half the sample size (" + sampleSize / 2 + ").", MessageBox.EXCLAIM); return false; } return true; } private void displayResults(final double[] ar, final double[] ai) { final StringBuffer result = new StringBuffer(); result.append("Decomposition of '" + character + "', byte-code " + Utility.binary8BitCode(character) + ":\n"); if (filterBox.isSelected() && numberOfHarmonics < coefficients) { if (numberOfHarmonics + 1 == coefficients) { result.append( "WARNING: Harmonic " + coefficients + " is filtered out.\n"); } else { result.append("WARNING: Harmonics " + (numberOfHarmonics + 1) + " through " + coefficients + " are filtered out.\n"); } } result.append(" Constant "); final String coStr = Integer.toString(coefficients); for (int i = 0, length = coStr.length() - 1; i < length; ++i) { result.append(" "); } result.append("c: "); if (ar[0] >= 0) { result.append(" "); } result.append(COEF_FORMAT.format(ar[0]) + "\n"); for (int i = 1; i <= coefficients; ++i) { result.append(" Harmonic "); final String iStr = Integer.toString(i); for (int j = 0, length = coStr.length() - iStr.length(); j < length; ++j) { result.append(" "); } result.append(i); result.append(": "); final double freq = 2 * Math.PI * i / period; if (ar[i] >= 0) { result.append(" "); } result.append(COEF_FORMAT.format(ar[i]) + " cos(" + FREQ_FORMAT.format(freq) + " t)"); if (ai[i] < 0) { result.append(" - "); } else { result.append(" + "); } result.append(COEF_FORMAT.format(Math.abs(ai[i])) + " sin(" + FREQ_FORMAT.format(freq) + " t)"); if (i != coefficients) { result.append("\n"); } } resultsArea.setText(result.toString()); resultsArea.setCaretPosition(0); } private void doComputations() { numberOfHarmonics = getNumberOfHarmonics(); final FFT fft = new FFT(sampleSize, character, numberOfHarmonics, period); x = fft.getXValues(); y = fft.getYValues(); yfft = fft.getYFFTValues(); final double[] ar = fft.getRealValues(), ai = fft.getImagValues(); spectrum.plot(ar, ai, numberOfHarmonics); graph.plot(x, y, yfft, overlayBox.isSelected(), showFFTBox.isSelected(), showNormalBox.isSelected()); displayResults(ar, ai); } public void focusGained(final FocusEvent fe) { ((JTextField)fe.getSource()).selectAll(); } public void focusLost(final FocusEvent fe) { ((JTextField)fe.getSource()).select(0, 0); } public void keyPressed(final KeyEvent ke) { if ((KeyEvent.getKeyText(ke.getKeyCode()).equals("Enter"))) { final boolean isOK = getFieldsFromForm(); if (isOK) { ((JTextField)ke.getSource()).selectAll(); (new FFTWorker()).execute(); } } } public void keyReleased(final KeyEvent ke) { } public void keyTyped(final KeyEvent ke) { } public void itemStateChanged(final ItemEvent ie) { graph.plot(x, y, yfft, overlayBox.isSelected(), showFFTBox.isSelected(), showNormalBox.isSelected()); } class FFTWorker extends SwingWorker { private long startTime; public Void doInBackground() { statusLabel.setText("Processing..."); setCursor(waitCursor); startTime = System.currentTimeMillis(); doComputations(); return null; } protected void done() { statusLabel.setText("Elapsed time: " + TIME_FORMAT.format((System.currentTimeMillis() - startTime)/1000f) + " s"); setCursor(defaultCursor); } } class ClosingWindowListener implements WindowListener { private final JFrame parent; public ClosingWindowListener(final JFrame parent) { this.parent = parent; } public void windowClosing(final WindowEvent e) { doApplicationClosing(parent); } public void windowDeactivated(final WindowEvent e) { } public void windowActivated(final WindowEvent e) { } public void windowDeiconified(final WindowEvent e) { } public void windowIconified(final WindowEvent e) { } public void windowClosed(final WindowEvent e) { } public void windowOpened(final WindowEvent e) { } } }