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    @SuppressWarnings("UnnecessaryBoxing")
092    public void layoutFrame() {
093        super.layoutFrame();
094        JPanel p;
095
096        p = new JPanel();
097        p.setLayout(new FlowLayout());
098        p.add(assignedBufferLabel);
099        p.add(assignedBuffer);
100        main.add(p);
101
102        p = new JPanel();
103        p.setLayout(new FlowLayout());
104        p.setBorder(BorderFactory.createCompoundBorder(
105                BorderFactory.createTitledBorder(Bundle.getMessage("LabelLoop")),
106                BorderFactory.createEmptyBorder(5, 5, 5, 5)));
107        p.add(loopMinLabel);
108        loopMin.setPreferredSize(new JTextField(8).getPreferredSize());
109        loopMin.setModel(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1));
110        loopMin.addChangeListener((ChangeEvent e) -> {
111            loopMax.setValue(
112                    ((Integer) loopMin.getValue()
113                            < (Integer) loopMax.getValue())
114                            ? loopMax.getValue()
115                            : loopMin.getValue());
116        });
117        p.add(loopMin);
118        p.add(loopMaxLabel);
119        loopMax.setPreferredSize(new JTextField(8).getPreferredSize());
120        loopMax.setModel(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1));
121        loopMax.addChangeListener((ChangeEvent e) -> {
122            loopMin.setValue(
123                    ((Integer) loopMax.getValue()
124                            < (Integer) loopMin.getValue())
125                            ? loopMax.getValue()
126                            : loopMin.getValue());
127        });
128        p.add(loopMax);
129        loopInfinite.addChangeListener((ChangeEvent e) -> {
130            loopMin.setEnabled(!loopInfinite.isSelected());
131            loopMax.setEnabled(!loopInfinite.isSelected());
132        });
133        p.add(loopInfinite);
134        main.add(p);
135
136//        p = new JPanel(); p.setLayout(new FlowLayout());
137//        p.setBorder(BorderFactory.createCompoundBorder(
138//                        BorderFactory.createTitledBorder(Bundle.getMessage("LabelLoopDelay")),
139//                        BorderFactory.createEmptyBorder(5, 5, 5, 5)));
140//        p.add(loopMinDelayLabel);
141//        loopMinDelay.setPreferredSize(new JTextField(8).getPreferredSize());
142//        loopMinDelay.setModel(new SpinnerNumberModel(0,0,Integer.MAX_VALUE,1));
143//        loopMinDelay.addChangeListener(new ChangeListener() {
144//            public void stateChanged(ChangeEvent e) {
145//                loopMaxDelay.setValue(
146//                        ((Integer)loopMinDelay.getValue()
147//                        <(Integer)loopMaxDelay.getValue())
148//                        ?loopMaxDelay.getValue()
149//                        :loopMinDelay.getValue());
150//            }
151//        });
152//        p.add(loopMinDelay);
153//        p.add(loopMaxDelayLabel);
154//        loopMaxDelay.setPreferredSize(new JTextField(8).getPreferredSize());
155//        loopMaxDelay.setModel(new SpinnerNumberModel(0,0,Integer.MAX_VALUE,1));
156//        loopMaxDelay.addChangeListener(new ChangeListener() {
157//            public void stateChanged(ChangeEvent e) {
158//                loopMinDelay.setValue(
159//                        ((Integer)loopMaxDelay.getValue()
160//                        <(Integer)loopMinDelay.getValue())
161//                        ?loopMaxDelay.getValue()
162//                        :loopMinDelay.getValue());
163//            }
164//        });
165//        p.add(loopMaxDelay);
166//        p.add(loopDelayUnitsLabel);
167//        main.add(p);
168//
169        p = new JPanel();
170        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
171        p.setBorder(BorderFactory.createCompoundBorder(
172                BorderFactory.createTitledBorder(Bundle.getMessage("LabelPosition")),
173                BorderFactory.createEmptyBorder(5, 5, 5, 5)));
174        p.add(position);
175        p.add(positionRelative);
176        main.add(p);
177
178        main.add(velocity);
179        main.add(gain);
180        main.add(pitch);
181
182        p = new JPanel();
183        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
184        p.setBorder(BorderFactory.createCompoundBorder(
185                BorderFactory.createTitledBorder(Bundle.getMessage("LabelDistances")),
186                BorderFactory.createEmptyBorder(5, 5, 5, 5)));
187
188        JPanel p2;
189        p2 = new JPanel();
190        p2.setLayout(new FlowLayout());
191        p2.add(refDistanceLabel);
192        refDistance.setPreferredSize(new JTextField(8).getPreferredSize());
193        refDistance.setModel(
194                new SpinnerNumberModel(Float.valueOf(0f), Float.valueOf(0f), Float.valueOf(Audio.MAX_DISTANCE), Float.valueOf(FLT_PRECISION)));
195        refDistance.setEditor(new JSpinner.NumberEditor(refDistance, "0.00"));
196        refDistance.addChangeListener((ChangeEvent e) -> {
197            maxDistance.setValue(
198                    ((Float) refDistance.getValue()
199                            < (Float) maxDistance.getValue())
200                            ? maxDistance.getValue()
201                            : refDistance.getValue());
202        });
203        p2.add(refDistance);
204
205        p2.add(maxDistanceLabel);
206        maxDistance.setPreferredSize(new JTextField(8).getPreferredSize());
207        maxDistance.setModel(
208                new SpinnerNumberModel(Float.valueOf(0f), Float.valueOf(0f), Float.valueOf(Audio.MAX_DISTANCE), Float.valueOf(FLT_PRECISION)));
209        maxDistance.setEditor(new JSpinner.NumberEditor(maxDistance, "0.00"));
210        maxDistance.addChangeListener((ChangeEvent e) -> {
211            refDistance.setValue(
212                    ((Float) maxDistance.getValue()
213                            < (Float) refDistance.getValue())
214                            ? maxDistance.getValue()
215                            : refDistance.getValue());
216        });
217        p2.add(maxDistance);
218        p2.add(distancesLabel);
219        p.add(p2);
220
221        p2 = new JPanel();
222        p2.setLayout(new FlowLayout());
223        p2.add(rollOffFactorLabel);
224        rollOffFactor.setPreferredSize(new JTextField(8).getPreferredSize());
225        rollOffFactor.setModel(
226                new SpinnerNumberModel(Float.valueOf(0f), Float.valueOf(0f), Float.valueOf(Audio.MAX_DISTANCE), Float.valueOf(FLT_PRECISION)));
227        rollOffFactor.setEditor(new JSpinner.NumberEditor(rollOffFactor, "0.00"));
228        p2.add(rollOffFactor);
229        p.add(p2);
230        main.add(p);
231
232        p = new JPanel();
233        p.setLayout(new FlowLayout());
234        p.setBorder(BorderFactory.createCompoundBorder(
235                BorderFactory.createTitledBorder(Bundle.getMessage("LabelFadeTimes")),
236                BorderFactory.createEmptyBorder(5, 5, 5, 5)));
237
238        p.add(fadeInTimeLabel);
239        fadeInTime.setPreferredSize(new JTextField(8).getPreferredSize());
240        fadeInTime.setModel(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1));
241        p.add(fadeInTime);
242
243        p.add(fadeOutTimeLabel);
244        fadeOutTime.setPreferredSize(new JTextField(8).getPreferredSize());
245        fadeOutTime.setModel(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1));
246        p.add(fadeOutTime);
247
248        p.add(fadeTimeUnitsLabel);
249        main.add(p);
250
251        p = new JPanel();
252        JButton apply;
253        p.add(apply = new JButton(Bundle.getMessage("ButtonApply")));
254        apply.addActionListener((ActionEvent e) -> {
255            applyPressed(e);
256        });
257        JButton ok;
258        p.add(ok = new JButton(Bundle.getMessage("ButtonOK")));
259        ok.addActionListener((ActionEvent e) -> {
260            applyPressed(e);
261            frame.dispose();
262        });
263        JButton cancel;
264        p.add(cancel = new JButton(Bundle.getMessage("ButtonCancel")));
265        cancel.addActionListener((ActionEvent e) -> {
266            frame.dispose();
267        });
268        frame.getContentPane().add(p);
269    }
270
271    /**
272     * Populate the Edit Source frame with default values.
273     */
274    @Override
275    public void resetFrame() {
276        synchronized (lock) {
277            sysName.setText(PREFIX + nextCounter());
278        }
279        userName.setText(null);
280        assignedBuffer.setSelectedIndex(0);
281        loopInfinite.setSelected(false);
282        loopMin.setValue(AudioSource.LOOP_NONE);
283        loopMax.setValue(AudioSource.LOOP_NONE);
284//        loopMinDelay.setValue(0);
285//        loopMaxDelay.setValue(0);
286        position.setValue(new Vector3f(0, 0, 0));
287        positionRelative.setSelected(false);
288        velocity.setValue(new Vector3f(0, 0, 0));
289        gain.setValue(1.0f);
290        pitch.setValue(1.0f);
291        refDistance.setValue(1.0f);
292        maxDistance.setValue(Audio.MAX_DISTANCE);
293        rollOffFactor.setValue(1.0f);
294        fadeInTime.setValue(1000);
295        fadeOutTime.setValue(1000);
296
297        this.newSource = true;
298    }
299
300    /**
301     * Populate the Edit Source frame with current values.
302     */
303    @Override
304    public void populateFrame(Audio a) {
305        if (!(a instanceof AudioSource)) {
306            throw new IllegalArgumentException(a.getSystemName() + " is not an AudioSource object");
307        }
308        super.populateFrame(a);
309        AudioSource s = (AudioSource) a;
310        AudioManager am = InstanceManager.getDefault(jmri.AudioManager.class);
311        String ab = s.getAssignedBufferName();
312        Audio b = am.getAudio(ab);
313        if (b != null) {
314            assignedBuffer.setSelectedItem(b.getUserName() == null ? ab : b.getUserName());
315        }
316        loopInfinite.setSelected((s.getMinLoops() == AudioSource.LOOP_CONTINUOUS));
317        loopMin.setValue(loopInfinite.isSelected() ? 0 : s.getMinLoops());
318        loopMax.setValue(loopInfinite.isSelected() ? 0 : s.getMaxLoops());
319        //        loopMinDelay.setValue(s.getMinLoopDelay());
320        //        loopMaxDelay.setValue(s.getMaxLoopDelay());
321        position.setValue(s.getPosition());
322        positionRelative.setSelected(s.isPositionRelative());
323        velocity.setValue(s.getVelocity());
324        gain.setValue(s.getGain());
325        pitch.setValue(s.getPitch());
326        refDistance.setValue(s.getReferenceDistance());
327        maxDistance.setValue(s.getMaximumDistance());
328        rollOffFactor.setValue(s.getRollOffFactor());
329        fadeInTime.setValue(s.getFadeIn());
330        fadeOutTime.setValue(s.getFadeOut());
331
332        this.newSource = false;
333    }
334
335    public void updateBufferList() {
336        AudioManager am = InstanceManager.getDefault(jmri.AudioManager.class);
337        assignedBuffer.removeAllItems();
338        assignedBuffer.addItem(Bundle.getMessage("SelectBufferFromList"));
339        am.getNamedBeanSet(Audio.BUFFER).stream().forEach((s) -> {
340            Audio a = am.getAudio(s.getSystemName());
341            if (a != null) {
342                String u = a.getUserName();
343                if (u != null) {
344                    assignedBuffer.addItem(u);
345                } else {
346                    assignedBuffer.addItem(s.getSystemName());
347                }
348            } else {
349                assignedBuffer.addItem(s.getSystemName());
350            }
351        });
352    }
353
354    private void applyPressed(ActionEvent e) {
355        String sName = sysName.getText();
356        if (entryError(sName, PREFIX, "" + counter)) {
357            return;
358        }
359        String user = userName.getText();
360        if (user.equals("")) {
361            user = null;
362        }
363        AudioSource s;
364        try {
365            AudioManager am = InstanceManager.getDefault(jmri.AudioManager.class);
366            try {
367                s = (AudioSource) am.provideAudio(sName);
368            } catch (IllegalArgumentException ex) {
369                throw new AudioException("Problem creating source");
370            }
371            if ((user != null) && (newSource) && (am.getByUserName(user) != null)) {
372                am.deregister(s);
373                synchronized (lock) {
374                    prevCounter();
375                }
376                throw new AudioException("Duplicate user name - please modify");
377            }
378            s.setUserName(user);
379            if (assignedBuffer.getSelectedIndex() > 0) {
380                String sel = (String) assignedBuffer.getSelectedItem();
381                if (sel != null) {
382                    Audio a = am.getAudio(sel);
383                    if (a != null) {
384                        s.setAssignedBuffer(a.getSystemName());
385                    }
386                }
387            }
388            s.setMinLoops(loopInfinite.isSelected() ? AudioSource.LOOP_CONTINUOUS : (Integer) loopMin.getValue());
389            s.setMaxLoops(loopInfinite.isSelected() ? AudioSource.LOOP_CONTINUOUS : (Integer) loopMax.getValue());
390            // s.setMinLoopDelay((Integer) loopMinDelay.getValue());
391            // s.setMaxLoopDelay((Integer) loopMaxDelay.getValue());
392            s.setPosition(position.getValue());
393            s.setPositionRelative(positionRelative.isSelected());
394            s.setVelocity(velocity.getValue());
395            s.setGain(gain.getValue());
396            s.setPitch(pitch.getValue());
397            s.setReferenceDistance((Float) refDistance.getValue());
398            s.setMaximumDistance((Float) maxDistance.getValue());
399            s.setRollOffFactor((Float) rollOffFactor.getValue());
400            s.setFadeIn((Integer) fadeInTime.getValue());
401            s.setFadeOut((Integer) fadeOutTime.getValue());
402
403            // Notify changes
404            model.fireTableDataChanged();
405        } catch (AudioException ex) {
406            JOptionPane.showMessageDialog(null, ex.getMessage(), Bundle.getMessage("AudioCreateErrorTitle"), JOptionPane.ERROR_MESSAGE);
407        }
408    }
409
410    private static int nextCounter() {
411        return counter++;
412    }
413
414    private static void prevCounter() {
415        counter--;
416    }
417
418    //private static final Logger log = LoggerFactory.getLogger(AudioSourceFrame.class);
419
420}