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