001package jmri.jmrit.audio.swing;
002
003import java.awt.FlowLayout;
004import java.awt.event.ActionEvent;
005import javax.swing.BorderFactory;
006import javax.swing.BoxLayout;
007import javax.swing.JButton;
008import javax.swing.JCheckBox;
009import javax.swing.JComboBox;
010import javax.swing.JLabel;
011import javax.swing.JOptionPane;
012import javax.swing.JPanel;
013import javax.swing.JSpinner;
014import javax.swing.JTextField;
015import javax.swing.SpinnerNumberModel;
016import javax.swing.event.ChangeEvent;
017import javax.vecmath.Vector3f;
018import jmri.Audio;
019import jmri.AudioException;
020import jmri.AudioManager;
021import jmri.InstanceManager;
022import jmri.jmrit.audio.AudioSource;
023import jmri.jmrit.beantable.AudioTableAction.AudioTableDataModel;
024
025/**
026 * Defines a GUI for editing AudioSource objects.
027 *
028 * <hr>
029 * This file is part of JMRI.
030 * <p>
031 * JMRI is free software; you can redistribute it and/or modify it under the
032 * terms of version 2 of the GNU General Public License as published by the Free
033 * Software Foundation. See the "COPYING" file for a copy of this license.
034 * <p>
035 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
036 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
037 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
038 *
039 * @author Matthew Harris copyright (c) 2009
040 */
041public class AudioSourceFrame extends AbstractAudioFrame {
042
043    private static int counter = 1;
044
045    private boolean newSource;
046
047    private final Object lock = new Object();
048
049    // UI components for Add/Edit Source
050    JLabel assignedBufferLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("LabelAssignedBuffer")));
051    JComboBox<String> assignedBuffer = new JComboBox<>();
052    JLabel loopMinLabel = new JLabel(Bundle.getMessage("LabelLoopMin"));
053    JSpinner loopMin = new JSpinner();
054    JLabel loopMaxLabel = new JLabel(Bundle.getMessage("LabelLoopMax"));
055    JSpinner loopMax = new JSpinner();
056    //    JLabel loopMinDelayLabel = new JLabel(Bundle.getMessage("LabelLoopMin"));
057    //    JSpinner loopMinDelay = new JSpinner();
058    //    JLabel loopMaxDelayLabel = new JLabel(Bundle.getMessage("LabelLoopMax"));
059    //    JSpinner loopMaxDelay = new JSpinner();
060    //    JLabel loopDelayUnitsLabel = new JLabel(Bundle.getMessage("UnitMS"));
061    JCheckBox loopInfinite = new JCheckBox(Bundle.getMessage("LabelLoopInfinite"));
062    JPanelVector3f position = new JPanelVector3f("",
063            Bundle.getMessage("UnitUnits"));
064    JCheckBox positionRelative = new JCheckBox(Bundle.getMessage("LabelPositionRelative"));
065    JPanelVector3f velocity = new JPanelVector3f(Bundle.getMessage("LabelVelocity"),
066            Bundle.getMessage("UnitU/S"));
067    JPanelSliderf gain = new JPanelSliderf(Bundle.getMessage("LabelGain"), 0.0f, 1.0f, 5, 4);
068    JPanelSliderf pitch = new JPanelSliderf(Bundle.getMessage("LabelPitch"), 0.5f, 2.0f, 6, 5);
069    JLabel refDistanceLabel = new JLabel(Bundle.getMessage("LabelReferenceDistance"));
070    JSpinner refDistance = new JSpinner();
071    JLabel maxDistanceLabel = new JLabel(Bundle.getMessage("LabelMaximumDistance"));
072    JSpinner maxDistance = new JSpinner();
073    JLabel distancesLabel = new JLabel(Bundle.getMessage("UnitUnits"));
074    JLabel rollOffFactorLabel = new JLabel(Bundle.getMessage("LabelRollOffFactor"));
075    JSpinner rollOffFactor = new JSpinner();
076    JLabel fadeInTimeLabel = new JLabel(Bundle.getMessage("LabelFadeIn"));
077    JSpinner fadeInTime = new JSpinner();
078    JLabel fadeOutTimeLabel = new JLabel(Bundle.getMessage("LabelFadeOut"));
079    JSpinner fadeOutTime = new JSpinner();
080    JLabel fadeTimeUnitsLabel = new JLabel(Bundle.getMessage("UnitMS"));
081
082    private final static String PREFIX = "IAS";
083
084    @SuppressWarnings("OverridableMethodCallInConstructor")
085    public AudioSourceFrame(String title, AudioTableDataModel model) {
086        super(title, model);
087        layoutFrame();
088    }
089
090    @Override
091    public void layoutFrame() {
092        super.layoutFrame();
093        JPanel p;
094
095        p = new JPanel();
096        p.setLayout(new FlowLayout());
097        p.add(assignedBufferLabel);
098        p.add(assignedBuffer);
099        main.add(p);
100
101        p = new JPanel();
102        p.setLayout(new FlowLayout());
103        p.setBorder(BorderFactory.createCompoundBorder(
104                BorderFactory.createTitledBorder(Bundle.getMessage("LabelLoop")),
105                BorderFactory.createEmptyBorder(5, 5, 5, 5)));
106        p.add(loopMinLabel);
107        loopMin.setPreferredSize(new JTextField(8).getPreferredSize());
108        loopMin.setModel(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1));
109        loopMin.addChangeListener((ChangeEvent e) -> {
110            loopMax.setValue(
111                    ((Integer) loopMin.getValue()
112                            < (Integer) loopMax.getValue())
113                            ? loopMax.getValue()
114                            : loopMin.getValue());
115        });
116        p.add(loopMin);
117        p.add(loopMaxLabel);
118        loopMax.setPreferredSize(new JTextField(8).getPreferredSize());
119        loopMax.setModel(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1));
120        loopMax.addChangeListener((ChangeEvent e) -> {
121            loopMin.setValue(
122                    ((Integer) loopMax.getValue()
123                            < (Integer) loopMin.getValue())
124                            ? loopMax.getValue()
125                            : loopMin.getValue());
126        });
127        p.add(loopMax);
128        loopInfinite.addChangeListener((ChangeEvent e) -> {
129            loopMin.setEnabled(!loopInfinite.isSelected());
130            loopMax.setEnabled(!loopInfinite.isSelected());
131        });
132        p.add(loopInfinite);
133        main.add(p);
134
135//        p = new JPanel(); p.setLayout(new FlowLayout());
136//        p.setBorder(BorderFactory.createCompoundBorder(
137//                        BorderFactory.createTitledBorder(Bundle.getMessage("LabelLoopDelay")),
138//                        BorderFactory.createEmptyBorder(5, 5, 5, 5)));
139//        p.add(loopMinDelayLabel);
140//        loopMinDelay.setPreferredSize(new JTextField(8).getPreferredSize());
141//        loopMinDelay.setModel(new SpinnerNumberModel(0,0,Integer.MAX_VALUE,1));
142//        loopMinDelay.addChangeListener(new ChangeListener() {
143//            public void stateChanged(ChangeEvent e) {
144//                loopMaxDelay.setValue(
145//                        ((Integer)loopMinDelay.getValue()
146//                        <(Integer)loopMaxDelay.getValue())
147//                        ?loopMaxDelay.getValue()
148//                        :loopMinDelay.getValue());
149//            }
150//        });
151//        p.add(loopMinDelay);
152//        p.add(loopMaxDelayLabel);
153//        loopMaxDelay.setPreferredSize(new JTextField(8).getPreferredSize());
154//        loopMaxDelay.setModel(new SpinnerNumberModel(0,0,Integer.MAX_VALUE,1));
155//        loopMaxDelay.addChangeListener(new ChangeListener() {
156//            public void stateChanged(ChangeEvent e) {
157//                loopMinDelay.setValue(
158//                        ((Integer)loopMaxDelay.getValue()
159//                        <(Integer)loopMinDelay.getValue())
160//                        ?loopMaxDelay.getValue()
161//                        :loopMinDelay.getValue());
162//            }
163//        });
164//        p.add(loopMaxDelay);
165//        p.add(loopDelayUnitsLabel);
166//        main.add(p);
167//
168        p = new JPanel();
169        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
170        p.setBorder(BorderFactory.createCompoundBorder(
171                BorderFactory.createTitledBorder(Bundle.getMessage("LabelPosition")),
172                BorderFactory.createEmptyBorder(5, 5, 5, 5)));
173        p.add(position);
174        p.add(positionRelative);
175        main.add(p);
176
177        main.add(velocity);
178        main.add(gain);
179        main.add(pitch);
180
181        p = new JPanel();
182        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
183        p.setBorder(BorderFactory.createCompoundBorder(
184                BorderFactory.createTitledBorder(Bundle.getMessage("LabelDistances")),
185                BorderFactory.createEmptyBorder(5, 5, 5, 5)));
186
187        JPanel p2;
188        p2 = new JPanel();
189        p2.setLayout(new FlowLayout());
190        p2.add(refDistanceLabel);
191        refDistance.setPreferredSize(new JTextField(8).getPreferredSize());
192        refDistance.setModel(
193                new SpinnerNumberModel(Float.valueOf(0f), Float.valueOf(0f), Float.valueOf(Audio.MAX_DISTANCE), Float.valueOf(FLT_PRECISION)));
194        refDistance.setEditor(new JSpinner.NumberEditor(refDistance, "0.00"));
195        refDistance.addChangeListener((ChangeEvent e) -> {
196            maxDistance.setValue(
197                    ((Float) refDistance.getValue()
198                            < (Float) maxDistance.getValue())
199                            ? maxDistance.getValue()
200                            : refDistance.getValue());
201        });
202        p2.add(refDistance);
203
204        p2.add(maxDistanceLabel);
205        maxDistance.setPreferredSize(new JTextField(8).getPreferredSize());
206        maxDistance.setModel(
207                new SpinnerNumberModel(Float.valueOf(0f), Float.valueOf(0f), Float.valueOf(Audio.MAX_DISTANCE), Float.valueOf(FLT_PRECISION)));
208        maxDistance.setEditor(new JSpinner.NumberEditor(maxDistance, "0.00"));
209        maxDistance.addChangeListener((ChangeEvent e) -> {
210            refDistance.setValue(
211                    ((Float) maxDistance.getValue()
212                            < (Float) refDistance.getValue())
213                            ? maxDistance.getValue()
214                            : refDistance.getValue());
215        });
216        p2.add(maxDistance);
217        p2.add(distancesLabel);
218        p.add(p2);
219
220        p2 = new JPanel();
221        p2.setLayout(new FlowLayout());
222        p2.add(rollOffFactorLabel);
223        rollOffFactor.setPreferredSize(new JTextField(8).getPreferredSize());
224        rollOffFactor.setModel(
225                new SpinnerNumberModel(Float.valueOf(0f), Float.valueOf(0f), Float.valueOf(Audio.MAX_DISTANCE), Float.valueOf(FLT_PRECISION)));
226        rollOffFactor.setEditor(new JSpinner.NumberEditor(rollOffFactor, "0.00"));
227        p2.add(rollOffFactor);
228        p.add(p2);
229        main.add(p);
230
231        p = new JPanel();
232        p.setLayout(new FlowLayout());
233        p.setBorder(BorderFactory.createCompoundBorder(
234                BorderFactory.createTitledBorder(Bundle.getMessage("LabelFadeTimes")),
235                BorderFactory.createEmptyBorder(5, 5, 5, 5)));
236
237        p.add(fadeInTimeLabel);
238        fadeInTime.setPreferredSize(new JTextField(8).getPreferredSize());
239        fadeInTime.setModel(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1));
240        p.add(fadeInTime);
241
242        p.add(fadeOutTimeLabel);
243        fadeOutTime.setPreferredSize(new JTextField(8).getPreferredSize());
244        fadeOutTime.setModel(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1));
245        p.add(fadeOutTime);
246
247        p.add(fadeTimeUnitsLabel);
248        main.add(p);
249
250        p = new JPanel();
251        JButton apply;
252        p.add(apply = new JButton(Bundle.getMessage("ButtonApply")));
253        apply.addActionListener((ActionEvent e) -> {
254            applyPressed(e);
255        });
256        JButton ok;
257        p.add(ok = new JButton(Bundle.getMessage("ButtonOK")));
258        ok.addActionListener((ActionEvent e) -> {
259            applyPressed(e);
260            frame.dispose();
261        });
262        JButton cancel;
263        p.add(cancel = new JButton(Bundle.getMessage("ButtonCancel")));
264        cancel.addActionListener((ActionEvent e) -> {
265            frame.dispose();
266        });
267        frame.getContentPane().add(p);
268    }
269
270    /**
271     * Populate the Edit Source frame with default values.
272     */
273    @Override
274    public void resetFrame() {
275        synchronized (lock) {
276            sysName.setText(PREFIX + nextCounter());
277        }
278        userName.setText(null);
279        assignedBuffer.setSelectedIndex(0);
280        loopInfinite.setSelected(false);
281        loopMin.setValue(AudioSource.LOOP_NONE);
282        loopMax.setValue(AudioSource.LOOP_NONE);
283//        loopMinDelay.setValue(0);
284//        loopMaxDelay.setValue(0);
285        position.setValue(new Vector3f(0, 0, 0));
286        positionRelative.setSelected(false);
287        velocity.setValue(new Vector3f(0, 0, 0));
288        gain.setValue(1.0f);
289        pitch.setValue(1.0f);
290        refDistance.setValue(1.0f);
291        maxDistance.setValue(Audio.MAX_DISTANCE);
292        rollOffFactor.setValue(1.0f);
293        fadeInTime.setValue(1000);
294        fadeOutTime.setValue(1000);
295
296        this.newSource = true;
297    }
298
299    /**
300     * Populate the Edit Source frame with current values.
301     */
302    @Override
303    public void populateFrame(Audio a) {
304        if (!(a instanceof AudioSource)) {
305            throw new IllegalArgumentException(a.getSystemName() + " is not an AudioSource object");
306        }
307        super.populateFrame(a);
308        AudioSource s = (AudioSource) a;
309        AudioManager am = InstanceManager.getDefault(jmri.AudioManager.class);
310        String ab = s.getAssignedBufferName();
311        Audio b = am.getAudio(ab);
312        if (b != null) {
313            assignedBuffer.setSelectedItem(b.getUserName() == null ? ab : b.getUserName());
314        }
315        loopInfinite.setSelected((s.getMinLoops() == AudioSource.LOOP_CONTINUOUS));
316        loopMin.setValue(loopInfinite.isSelected() ? 0 : s.getMinLoops());
317        loopMax.setValue(loopInfinite.isSelected() ? 0 : s.getMaxLoops());
318        //        loopMinDelay.setValue(s.getMinLoopDelay());
319        //        loopMaxDelay.setValue(s.getMaxLoopDelay());
320        position.setValue(s.getPosition());
321        positionRelative.setSelected(s.isPositionRelative());
322        velocity.setValue(s.getVelocity());
323        gain.setValue(s.getGain());
324        pitch.setValue(s.getPitch());
325        refDistance.setValue(s.getReferenceDistance());
326        maxDistance.setValue(s.getMaximumDistance());
327        rollOffFactor.setValue(s.getRollOffFactor());
328        fadeInTime.setValue(s.getFadeIn());
329        fadeOutTime.setValue(s.getFadeOut());
330
331        this.newSource = false;
332    }
333
334    public void updateBufferList() {
335        AudioManager am = InstanceManager.getDefault(jmri.AudioManager.class);
336        assignedBuffer.removeAllItems();
337        assignedBuffer.addItem(Bundle.getMessage("SelectBufferFromList"));
338        am.getNamedBeanSet(Audio.BUFFER).stream().forEach((s) -> {
339            Audio a = am.getAudio(s.getSystemName());
340            if (a != null) {
341                String u = a.getUserName();
342                if (u != null) {
343                    assignedBuffer.addItem(u);
344                } else {
345                    assignedBuffer.addItem(s.getSystemName());
346                }
347            } else {
348                assignedBuffer.addItem(s.getSystemName());
349            }
350        });
351    }
352
353    private void applyPressed(ActionEvent e) {
354        String sName = sysName.getText();
355        if (entryError(sName, PREFIX, "" + counter)) {
356            return;
357        }
358        String user = userName.getText();
359        if (user.equals("")) {
360            user = null;
361        }
362        AudioSource s;
363        try {
364            AudioManager am = InstanceManager.getDefault(jmri.AudioManager.class);
365            try {
366                s = (AudioSource) am.provideAudio(sName);
367            } catch (IllegalArgumentException ex) {
368                throw new AudioException("Problem creating source");
369            }
370            if ((user != null) && (newSource) && (am.getByUserName(user) != null)) {
371                am.deregister(s);
372                synchronized (lock) {
373                    prevCounter();
374                }
375                throw new AudioException("Duplicate user name - please modify");
376            }
377            s.setUserName(user);
378            if (assignedBuffer.getSelectedIndex() > 0) {
379                String sel = (String) assignedBuffer.getSelectedItem();
380                if (sel != null) {
381                    Audio a = am.getAudio(sel);
382                    if (a != null) {
383                        s.setAssignedBuffer(a.getSystemName());
384                    }
385                }
386            }
387            s.setMinLoops(loopInfinite.isSelected() ? AudioSource.LOOP_CONTINUOUS : (Integer) loopMin.getValue());
388            s.setMaxLoops(loopInfinite.isSelected() ? AudioSource.LOOP_CONTINUOUS : (Integer) loopMax.getValue());
389            // s.setMinLoopDelay((Integer) loopMinDelay.getValue());
390            // s.setMaxLoopDelay((Integer) loopMaxDelay.getValue());
391            s.setPosition(position.getValue());
392            s.setPositionRelative(positionRelative.isSelected());
393            s.setVelocity(velocity.getValue());
394            s.setGain(gain.getValue());
395            s.setPitch(pitch.getValue());
396            s.setReferenceDistance((Float) refDistance.getValue());
397            s.setMaximumDistance((Float) maxDistance.getValue());
398            s.setRollOffFactor((Float) rollOffFactor.getValue());
399            s.setFadeIn((Integer) fadeInTime.getValue());
400            s.setFadeOut((Integer) fadeOutTime.getValue());
401
402            // Notify changes
403            model.fireTableDataChanged();
404        } catch (AudioException ex) {
405            JOptionPane.showMessageDialog(null, ex.getMessage(), Bundle.getMessage("AudioCreateErrorTitle"), JOptionPane.ERROR_MESSAGE);
406        }
407    }
408
409    private static int nextCounter() {
410        return counter++;
411    }
412
413    private static void prevCounter() {
414        counter--;
415    }
416
417    //private static final Logger log = LoggerFactory.getLogger(AudioSourceFrame.class);
418
419}