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