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