001package jmri.jmrit.audio.swing;
002
003import java.awt.FlowLayout;
004import java.util.Hashtable;
005import javax.swing.*;
006import javax.swing.event.ChangeEvent;
007import javax.vecmath.Vector3f;
008import jmri.Audio;
009import jmri.implementation.AbstractAudio;
010import jmri.jmrit.beantable.AudioTableAction.AudioTableDataModel;
011import jmri.util.JmriJFrame;
012
013/**
014 * Abstract GUI to edit Audio objects
015 *
016 * <hr>
017 * This file is part of JMRI.
018 * <p>
019 * JMRI is free software; you can redistribute it and/or modify it under the
020 * terms of version 2 of the GNU General Public License as published by the Free
021 * Software Foundation. See the "COPYING" file for a copy of this license.
022 * <p>
023 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
024 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
025 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
026 *
027 * @author Matthew Harris copyright (c) 2009
028 */
029abstract public class AbstractAudioFrame extends JmriJFrame {
030
031    AbstractAudioFrame frame = this;
032
033    JPanel main = new JPanel();
034    private JScrollPane scroll
035            = new JScrollPane(main,
036                    ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
037                    ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
038
039    AudioTableDataModel model;
040
041    private static final int INT_PRECISION = (int) Math.pow(10, Audio.DECIMAL_PLACES);
042    static final float FLT_PRECISION = 1 / (float) INT_PRECISION;
043
044    // Common UI components for Add/Edit Audio
045    private static final JLabel SYS_NAME_LABEL = new JLabel(Bundle.getMessage("LabelSystemName"));
046    JTextField sysName = new JTextField(5);
047    private static final JLabel USER_NAME_LABEL = new JLabel(Bundle.getMessage("LabelUserName"));
048    JTextField userName = new JTextField(15);
049
050    /**
051     * Standard constructor.
052     *
053     * @param title Title of this AudioFrame
054     * @param model AudioTableDataModel holding Audio data
055     */
056    public AbstractAudioFrame(String title, AudioTableDataModel model) {
057        super(title);
058        this.model = model;
059    }
060
061    /**
062     * Layout the frame.
063     * <p>
064     * This contains common items.
065     * <p>
066     * Sub-classes will override this method and provide additional GUI items.
067     */
068    public void layoutFrame() {
069        frame.addHelpMenu("package.jmri.jmrit.beantable.AudioAddEdit", true);
070        frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.Y_AXIS));
071        main.setLayout(new BoxLayout(main, BoxLayout.Y_AXIS));
072
073        JPanel p;
074
075        p = new JPanel();
076        p.setLayout(new FlowLayout());
077        p.add(SYS_NAME_LABEL);
078        p.add(sysName);
079        frame.getContentPane().add(p);
080
081        p = new JPanel();
082        p.setLayout(new FlowLayout());
083        p.add(USER_NAME_LABEL);
084        p.add(userName);
085        frame.getContentPane().add(p);
086
087        frame.add(scroll);
088    }
089
090    /**
091     * Populate the Audio frame with default values.
092     */
093    abstract public void resetFrame();
094
095    /**
096     * Populate the Audio frame with current values.
097     *
098     * @param a Audio object to use
099     */
100    public void populateFrame(Audio a) {
101        sysName.setText(a.getSystemName());
102        userName.setText(a.getUserName());
103    }
104
105    /**
106     * Check System Name user input.
107     *
108     * @param entry string retrieved from text field
109     * @param counter index of all similar (Source/Buffer) items
110     * @param prefix (AudioListener/Source/Buffer) system name prefix string to compare entry against
111     * @return true if prefix doesn't match
112     */
113    protected boolean entryError(String entry, String prefix, String counter) {
114        if (!entry.startsWith(prefix)) {
115            JOptionPane.showMessageDialog(null, Bundle.getMessage("AudioCreateError", prefix),
116                    Bundle.getMessage("AudioCreateErrorTitle"), JOptionPane.ERROR_MESSAGE);
117            sysName.setText(prefix + counter);
118            return true;
119        }
120        return false;
121    }
122
123    //private static final Logger log = LoggerFactory.getLogger(AbstractAudioFrame.class);
124    /**
125     * Convenience class to create a JPanel to edit a Vector3f object using 3
126     * separate JSpinner Swing objects.
127     */
128    protected static class JPanelVector3f extends JPanel {
129
130        JLabel xLabel = new JLabel(Bundle.getMessage("LabelX"));
131        JSpinner xValue = new JSpinner();
132        JLabel yLabel = new JLabel(Bundle.getMessage("LabelY"));
133        JSpinner yValue = new JSpinner();
134        JLabel zLabel = new JLabel(Bundle.getMessage("LabelZ"));
135        JSpinner zValue = new JSpinner();
136        JLabel unitsLabel = new JLabel();
137
138        JPanelVector3f() {
139            super();
140            layoutPanel("", "");
141        }
142
143        JPanelVector3f(String title) {
144            super();
145            layoutPanel(title, "");
146        }
147
148        JPanelVector3f(String title, String units) {
149            super();
150            layoutPanel(title, units);
151        }
152
153        @SuppressWarnings("UnnecessaryBoxing")
154        private void layoutPanel(String title, String units) {
155            this.setLayout(new FlowLayout());
156            if (title.length() != 0) {
157                this.setBorder(BorderFactory.createCompoundBorder(
158                        BorderFactory.createTitledBorder(title),
159                        BorderFactory.createEmptyBorder(5, 5, 5, 5)));
160            }
161            this.add(xLabel);
162            xValue.setPreferredSize(new JTextField(8).getPreferredSize());
163            xValue.setModel(
164                    new SpinnerNumberModel(Float.valueOf(0f), Float.valueOf(-Audio.MAX_DISTANCE), Float.valueOf(Audio.MAX_DISTANCE), Float.valueOf(FLT_PRECISION)));
165            xValue.setEditor(new JSpinner.NumberEditor(xValue, "0.00"));
166            this.add(xValue);
167
168            this.add(yLabel);
169            yValue.setPreferredSize(new JTextField(8).getPreferredSize());
170            yValue.setModel(
171                    new SpinnerNumberModel(Float.valueOf(0f), Float.valueOf(-Audio.MAX_DISTANCE), Float.valueOf(Audio.MAX_DISTANCE), Float.valueOf(FLT_PRECISION)));
172            yValue.setEditor(new JSpinner.NumberEditor(yValue, "0.00"));
173            this.add(yValue);
174
175            this.add(zLabel);
176            zValue.setPreferredSize(new JTextField(8).getPreferredSize());
177            zValue.setModel(
178                    new SpinnerNumberModel(Float.valueOf(0f), Float.valueOf(-Audio.MAX_DISTANCE), Float.valueOf(Audio.MAX_DISTANCE), Float.valueOf(FLT_PRECISION)));
179            zValue.setEditor(new JSpinner.NumberEditor(zValue, "0.00"));
180            this.add(zValue);
181
182            if (units.length() != 0) {
183                unitsLabel.setText(units);
184                this.add(unitsLabel);
185            }
186        }
187
188        /**
189         * Set the value of this object.
190         *
191         * @param value value to set
192         */
193        public void setValue(Vector3f value) {
194            xValue.setValue(value.x);
195            yValue.setValue(value.y);
196            zValue.setValue(value.z);
197        }
198
199        /**
200         * Retrieve the current value of this object
201         *
202         * @return current value
203         */
204        public Vector3f getValue() {
205            return new Vector3f(
206                    AbstractAudio.roundDecimal((Float) xValue.getValue()),
207                    AbstractAudio.roundDecimal((Float) yValue.getValue()),
208                    AbstractAudio.roundDecimal((Float) zValue.getValue()));
209        }
210    }
211
212    /**
213     * A convenience class to create a JPanel for editing a float value using
214     * combined JSlider and JSPinner Swing objects.
215     */
216    protected static class JPanelSliderf extends JPanel {
217
218        JSlider slider = new JSlider();
219
220        JSpinner spinner = new JSpinner();
221
222        @SuppressWarnings({"UnnecessaryBoxing", "OverridableMethodCallInConstructor"})
223        JPanelSliderf(String title, Float min, Float max, int majorTicks, int minorTicks) {
224            super();
225            int iMin = Math.round(min * INT_PRECISION);
226            int iMax = Math.round(max * INT_PRECISION);
227            int iInterval = (iMax - iMin) / majorTicks;
228
229            this.setLayout(new FlowLayout());
230            this.setBorder(BorderFactory.createCompoundBorder(
231                    BorderFactory.createTitledBorder(title),
232                    BorderFactory.createEmptyBorder(5, 5, 5, 5)));
233            slider.setMinimum(Math.round(min * INT_PRECISION));
234            slider.setMaximum(Math.round(max * INT_PRECISION));
235            slider.setMajorTickSpacing(iInterval);
236            slider.setMinorTickSpacing(iInterval / minorTicks);
237            @SuppressWarnings("UseOfObsoleteCollectionType")
238            // Need to use Hashtable for JSlider labels
239            Hashtable<Integer, JLabel> labelTable = new Hashtable<>();
240            for (int i = iMin; i <= iMax; i += iInterval) {
241                float f = i;
242                f /= INT_PRECISION;
243                labelTable.put(i, new JLabel(Float.toString(f)));
244            }
245            slider.setLabelTable(labelTable);
246            slider.setPaintTicks(true);
247            slider.setPaintLabels(true);
248            slider.addChangeListener((ChangeEvent e) -> {
249                float f = slider.getValue();
250                f /= INT_PRECISION;
251                spinner.setValue(f);
252            });
253            spinner.setPreferredSize(new JTextField(5).getPreferredSize());
254            spinner.setModel(
255                    new SpinnerNumberModel(min, min, max, Float.valueOf(FLT_PRECISION)));
256            spinner.setEditor(new JSpinner.NumberEditor(spinner, "0.00"));
257            spinner.addChangeListener((ChangeEvent e) -> {
258                slider.setValue(
259                        Math.round((Float) spinner.getValue() * INT_PRECISION));
260            });
261            this.add(slider);
262            this.add(spinner);
263        }
264
265        /**
266         * Set the value of this object.
267         *
268         * @param value value to set
269         */
270        public void setValue(float value) {
271            spinner.setValue(value);
272        }
273
274        /**
275         * Retrieve the current value of this object.
276         *
277         * @return current value
278         */
279        public float getValue() {
280            return AbstractAudio.roundDecimal((Float) spinner.getValue());
281        }
282    }
283
284}