001package jmri.jmrit.audio.swing;
002
003import java.awt.FlowLayout;
004import java.awt.event.ActionEvent;
005import java.io.File;
006
007import javax.swing.BorderFactory;
008import javax.swing.BoxLayout;
009import javax.swing.JButton;
010import javax.swing.JCheckBox;
011import javax.swing.JFileChooser;
012import javax.swing.JLabel;
013import javax.swing.JPanel;
014import javax.swing.JSpinner;
015import javax.swing.JTextField;
016import javax.swing.SpinnerNumberModel;
017import javax.swing.filechooser.FileNameExtensionFilter;
018
019import jmri.Audio;
020import jmri.AudioException;
021import jmri.AudioManager;
022import jmri.InstanceManager;
023import jmri.jmrit.audio.AudioBuffer;
024import jmri.jmrit.beantable.AudioTableAction.AudioTableDataModel;
025import jmri.util.FileUtil;
026import jmri.util.swing.JmriJOptionPane;
027
028/**
029 * Defines a GUI to edit AudioBuffer objects.
030 *
031 * <hr>
032 * This file is part of JMRI.
033 * <p>
034 * JMRI is free software; you can redistribute it and/or modify it under the
035 * terms of version 2 of the GNU General Public License as published by the Free
036 * Software Foundation. See the "COPYING" file for a copy of this license.
037 * <p>
038 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
039 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
040 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
041 *
042 * @author Matthew Harris copyright (c) 2009
043 */
044public class AudioBufferFrame extends AbstractAudioFrame {
045
046    private static int counter = 1;
047
048    private boolean newBuffer;
049
050    private final Object lock = new Object();
051
052    // UI components for Add/Edit Buffer
053    private final JLabel urlLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("LabelURL")));
054    private final JTextField url = new JTextField(40);
055    private final JButton buttonBrowse = new JButton("...");
056    private final JCheckBox stream = new JCheckBox(Bundle.getMessage("LabelStream"));
057    // JLabel formatLabel = new JLabel(Bundle.getMessage("LabelFormat"));
058    // JTextField format = new JTextField(20);
059    private final JLabel loopStartLabel = new JLabel(Bundle.getMessage("LabelLoopStart"));
060    private final JSpinner loopStart = new JSpinner();
061    private final JLabel loopEndLabel = new JLabel(Bundle.getMessage("LabelLoopEnd"));
062    private final JSpinner loopEnd = new JSpinner();
063    private JFileChooser fileChooser;
064    // AudioWaveFormPanel waveForm = new AudioWaveFormPanel();
065
066    private static final String PREFIX = "IAB";
067
068//    @SuppressWarnings("OverridableMethodCallInConstructor")
069    public AudioBufferFrame(String title, AudioTableDataModel model) {
070        super(title, model);
071        layoutFrame();
072
073        // For now, disable editing of loop points
074        // TODO: enable editing of looping points
075        loopStart.setEnabled(false);
076        loopStartLabel.setEnabled(false);
077        loopEnd.setEnabled(false);
078        loopEndLabel.setEnabled(false);
079    }
080
081    @Override
082    public final void layoutFrame() {
083        super.layoutFrame();
084
085        JPanel p = new JPanel();
086        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
087        p.setBorder(BorderFactory.createCompoundBorder(
088                BorderFactory.createTitledBorder(Bundle.getMessage("LabelSample")),
089                BorderFactory.createEmptyBorder(5, 5, 5, 5)));
090        JPanel p2 = new JPanel();
091        p2.setLayout(new FlowLayout());
092        p2.add(urlLabel);
093        p2.add(url);
094        buttonBrowse.addActionListener( e -> browsePressed());
095        buttonBrowse.setToolTipText(Bundle.getMessage("ToolTipButtonBrowse"));
096        p2.add(buttonBrowse);
097        p.add(p2);
098        p2 = new JPanel();
099        p2.setLayout(new FlowLayout());
100        p2.add(stream);
101        p.add(p2);
102//        p2 = new JPanel(); p2.setLayout(new FlowLayout());
103//        p2.add(formatLabel);
104//        p2.add(format);
105//        p.add(p2);
106        main.add(p);
107
108//        p = new JPanel();
109//        p.setBorder(BorderFactory.createCompoundBorder(
110//                        BorderFactory.createTitledBorder("Waveforms"),
111//                        BorderFactory.createEmptyBorder(5, 5, 5, 5)));
112//
113//        JLabel label = new JLabel("Whole sample");
114//        label.setUI(new VerticalLabelUI());
115//        p.add(label);
116//
117//        waveForm.setPreferredSize(new Dimension(400, 120));
118//        p.add(waveForm);
119//
120//        label = new JLabel("Loop-point detail");
121//        label.setUI(new VerticalLabelUI());
122//        p.add(label);
123//
124//        AudioWaveFormPanel waveFormLoop = new AudioWaveFormPanel();
125//        waveFormLoop.setPreferredSize(new Dimension(80, 120));
126//        p.add(waveFormLoop);
127//
128//        main.add(p);
129//
130        p = new JPanel();
131        p.setLayout(new FlowLayout());
132        p.setBorder(BorderFactory.createCompoundBorder(
133                BorderFactory.createTitledBorder(Bundle.getMessage("LabelLoopPoints")),
134                BorderFactory.createEmptyBorder(5, 5, 5, 5)));
135        p.add(loopStartLabel);
136        loopStart.setPreferredSize(new JTextField(8).getPreferredSize());
137        loopStart.setModel(
138                new SpinnerNumberModel(0L, 0L, Long.MAX_VALUE,1L));
139        loopStart.addChangeListener( e ->
140            loopEnd.setValue(
141                    ((Long) loopStart.getValue()
142                            < (Long) loopEnd.getValue())
143                            ? loopEnd.getValue()
144                            : loopStart.getValue()));
145        p.add(loopStart);
146        p.add(loopEndLabel);
147        loopEnd.setPreferredSize(new JTextField(8).getPreferredSize());
148        loopEnd.setModel(
149                new SpinnerNumberModel(0L, 0L, Long.MAX_VALUE, 1L));
150        loopEnd.addChangeListener( e ->
151            loopStart.setValue(
152                    ((Long) loopEnd.getValue()
153                            < (Long) loopStart.getValue())
154                            ? loopEnd.getValue()
155                            : loopStart.getValue()));
156        p.add(loopEnd);
157        main.add(p);
158
159        p = new JPanel();
160        JButton apply = new JButton(Bundle.getMessage("ButtonApply"));
161        p.add(apply);
162        apply.addActionListener( e -> applyPressed());
163        JButton ok = new JButton(Bundle.getMessage("ButtonOK"));
164        p.add(ok);
165        ok.addActionListener((ActionEvent e) -> {
166            if (applyPressed()) {
167                frame.dispose();
168            }
169        });
170        JButton cancel = new JButton(Bundle.getMessage("ButtonCancel"));
171        p.add(cancel);
172        cancel.addActionListener( e -> frame.dispose());
173        frame.getContentPane().add(p);
174    }
175
176    /**
177     * Populate the Edit Buffer frame with default values.
178     */
179    @Override
180    public void resetFrame() {
181        synchronized (lock) {
182            sysName.setText(PREFIX + nextCounter()); // NOI18N
183        }
184        userName.setText(null);
185        url.setText(null);
186        // format.setText(null);
187        stream.setSelected(false);
188        stream.setEnabled(false);
189        loopStart.setValue(0L);
190        loopEnd.setValue(0L);
191
192        this.newBuffer = true;
193    }
194
195    /**
196     * Populate the Edit Buffer frame with current values.
197     */
198    @Override
199    public void populateFrame(Audio a) {
200        if (!(a instanceof AudioBuffer)) {
201            throw new IllegalArgumentException(a + " is not an AudioBuffer object");
202        }
203        super.populateFrame(a);
204        AudioBuffer b = (AudioBuffer) a;
205        url.setText(b.getURL());
206        // format.setText(b.toString());
207        stream.setSelected(b.isStreamed());
208        stream.setEnabled(false); //(!b.isStreamedForced());
209        loopStart.setValue(b.getStartLoopPoint());
210        loopEnd.setValue(b.getEndLoopPoint());
211        loopStart.setEnabled(true);
212        loopStartLabel.setEnabled(true);
213        loopEnd.setEnabled(true);
214        loopEndLabel.setEnabled(true);
215
216        this.newBuffer = false;
217    }
218
219    private void browsePressed() {
220        if (fileChooser == null) {
221            fileChooser = new jmri.util.swing.JmriJFileChooser("resources" + File.separator + "sounds" + File.separator); // NOI18N
222            fileChooser.setFileFilter(new FileNameExtensionFilter("Audio Files (*.wav)", "wav")); // NOI18N
223        }
224
225        // Show dialog
226        fileChooser.rescanCurrentDirectory();
227        int retValue = fileChooser.showOpenDialog(this);
228
229        // Process selection
230        if (retValue == JFileChooser.APPROVE_OPTION) {
231            File file = fileChooser.getSelectedFile();
232            String fileName = FileUtil.getPortableFilename(file);
233            if (!url.getText().equals(fileName)) {
234                url.setText(fileName);
235//                try {
236//                    WaveFileReader wfr = new WaveFileReader(FileUtil.getExternalFilename(fileName));
237//                    JmriJOptionPane.showMessageDialog(null, wfr.toString(), wfr.toString(), JmriJOptionPane.INFORMATION_MESSAGE);
238//                } catch (AudioException ex) {
239//                    JmriJOptionPane.showMessageDialog(this, ex.getMessage(), rba.getString("TitleReadError"), JmriJOptionPane.ERROR_MESSAGE);
240//                }
241            }
242        }
243    }
244
245    private boolean applyPressed() {
246        String sName = sysName.getText();
247        if (entryError(sName, PREFIX, "" + counter)) {
248            return false;
249        }
250        String user = userName.getText();
251        if (user.isEmpty()) {
252            user = null;
253        }
254        AudioBuffer b;
255        try {
256            AudioManager am = InstanceManager.getDefault(jmri.AudioManager.class);
257            if (newBuffer && am.getBySystemName(sName) != null) {
258                throw new AudioException(Bundle.getMessage("DuplicateSystemName"));
259            }
260            try {
261                b = (AudioBuffer) am.provideAudio(sName);
262            } catch (IllegalArgumentException ex) {
263                throw new AudioException(Bundle.getMessage("ProblemCreatingBuffer"));
264            }
265            if ((user != null) && newBuffer && am.getByUserName(user) != null) {
266                am.deregister(b);
267                synchronized (lock) {
268                    prevCounter();
269                }
270                throw new AudioException(Bundle.getMessage("DuplicateUserName"));
271            }
272            b.setUserName(user);
273            b.setStreamed(stream.isSelected());
274            if (newBuffer || !b.getURL().equals(url.getText())) {
275                b.setURL(url.getText());
276                log.debug("After load, end loop point = {}", b.getEndLoopPoint());
277                //b.setStartLoopPoint((Long)loopStart.getValue());
278                //b.setEndLoopPoint((Long)loopEnd.getValue());
279            } else {
280                if (!b.getURL().equals(url.getText())) {
281                    log.debug("Sound changed from: {}", b.getURL());
282                    b.setURL(url.getText());
283                }
284            }
285
286            // Update streaming checkbox if necessary
287            stream.setSelected(b.isStreamed());
288            stream.setEnabled(false); //(!b.isStreamedForced());
289
290            // Notify changes
291            model.fireTableDataChanged();
292        } catch (AudioException ex) {
293            JmriJOptionPane.showMessageDialog(this, ex.getMessage(),
294                Bundle.getMessage("AudioCreateErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
295            return false;
296        }
297        newBuffer = false;  // If the user presses Apply, the dialog stays visible.
298        return true;
299    }
300
301    private static int nextCounter() {
302        counter++;
303        return counter-1;
304    }
305
306    private static void prevCounter() {
307        counter--;
308    }
309
310    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AudioBufferFrame.class);
311
312}