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