001package jmri.jmrit.vsdecoder;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.io.*;
006import java.util.ArrayList;
007import java.util.Collections;
008import java.util.HashMap;
009import java.util.Iterator;
010import java.util.Map;
011import java.util.Set;
012import jmri.jmrit.XmlFile;
013import jmri.jmrit.vsdecoder.listener.ListeningSpot;
014import jmri.util.FileUtil;
015import jmri.util.PhysicalLocation;
016import org.jdom2.*;
017import org.slf4j.Logger;
018import org.slf4j.LoggerFactory;
019
020/**
021 * Manage VSDecoder Preferences.
022 *
023 * <hr>
024 * This file is part of JMRI.
025 * <p>
026 * JMRI is free software; you can redistribute it and/or modify it under
027 * the terms of version 2 of the GNU General Public License as published
028 * by the Free Software Foundation. See the "COPYING" file for a copy
029 * of this license.
030 * <p>
031 * JMRI is distributed in the hope that it will be useful, but WITHOUT
032 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
033 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
034 * for more details.
035 *
036 * @author Mark Underwood Copyright (C) 2011
037 */
038public class VSDecoderPreferences {
039
040    public final static String VSDPreferencesFileName = "VSDecoderPreferences.xml";
041
042    static public enum AudioMode {
043
044        ROOM_AMBIENT, HEADPHONES
045    }
046    static public final Map<AudioMode, String> AudioModeMap;
047
048    static {
049        Map<AudioMode, String> aMap = new HashMap<AudioMode, String>();
050        aMap.put(AudioMode.ROOM_AMBIENT, "RoomAmbient");
051        aMap.put(AudioMode.HEADPHONES, "Headphones");
052        AudioModeMap = Collections.unmodifiableMap(aMap);
053    }
054    static public final AudioMode DefaultAudioMode = AudioMode.ROOM_AMBIENT;
055    static public final int DefaultMasterVolume = 80;
056
057    // Private variables to hold preference values
058    private boolean _autoStartEngine = false; // play engine sound w/o waiting for "Engine Start" button pressed.
059    private boolean _autoLoadDefaultVSDFile = false; // Automatically load a VSD file.
060    private boolean _use_blocks = true;
061    private String _defaultVSDFilePath = null;
062    private String _defaultVSDFileName = null;
063    private ListeningSpot _listenerPosition;
064    private AudioMode _audioMode;
065    private int _masterVolume;
066
067    // Other internal variables
068    //private Dimension _winDim = new Dimension(800,600);
069    private String prefFile;
070    private ArrayList<PropertyChangeListener> listeners;
071
072    public VSDecoderPreferences(String sfile) {
073        prefFile = sfile;
074        VSDecoderPrefsXml prefs = new VSDecoderPrefsXml();
075        File file = new File(prefFile);
076        Element root;
077
078        // Set default values
079        _defaultVSDFilePath = FileUtil.getExternalFilename("program:resources/vsdecoder");
080        _defaultVSDFileName = "example.vsd";
081        _listenerPosition = new ListeningSpot(); // default to (0, 0, 0) Orientation (0,1,0)
082        _audioMode = DefaultAudioMode;
083        _masterVolume = DefaultMasterVolume;
084
085        // Try to load preferences from the file
086        try {
087            root = prefs.rootFromFile(file);
088        } catch (IOException e2) {
089            log.info("Did not find VSDecoder preferences file.  This is normal if you haven't save the preferences before");
090            root = null;
091        } catch (JDOMException | RuntimeException e) {
092            log.error("Exception while loading VSDecoder preferences", e);
093            root = null;
094        }
095        if (root != null) {
096            load(root.getChild("VSDecoderPreferences"));
097        }
098    }
099
100    public VSDecoderPreferences() {
101    }
102
103    public void load(org.jdom2.Element e) {
104        if (e == null) {
105            return;
106        }
107        org.jdom2.Attribute a;
108        org.jdom2.Element c;
109        if ((a = e.getAttribute("isAutoStartingEngine")) != null) {
110            setAutoStartEngine(a.getValue().compareTo("true") == 0);
111        }
112        if ((a = e.getAttribute("isAutoLoadingDefaultVSDFile")) != null) {
113            setAutoLoadDefaultVSDFile(a.getValue().compareTo("true") == 0);
114        }
115        if ((a = e.getAttribute("useBlocks")) != null) {
116            setUseBlocksSetting(a.getValue().compareTo("true") == 0);
117        }
118        if ((c = e.getChild("DefaultVSDFilePath")) != null) {
119            setDefaultVSDFilePath(c.getValue());
120        }
121        if ((c = e.getChild("DefaultVSDFileName")) != null) {
122            setDefaultVSDFileName(c.getValue());
123        }
124        if ((c = e.getChild("ListenerPosition")) != null) {
125            _listenerPosition = new ListeningSpot(c);
126        } else {
127            _listenerPosition = new ListeningSpot();
128        }
129        if ((c = e.getChild("AudioMode")) != null) {
130            setAudioMode(c.getValue());
131        }
132        if ((c = e.getChild("MasterVolume")) != null) {
133            setMasterVolume(Integer.parseInt(c.getValue()));
134        }
135    }
136
137    /**
138     * An extension of the abstract XmlFile. No changes made to that class.
139     *
140     */
141    static class VSDecoderPrefsXml extends XmlFile {
142    }
143
144    private org.jdom2.Element store() {
145        org.jdom2.Element ec;
146        org.jdom2.Element e = new org.jdom2.Element("VSDecoderPreferences");
147        e.setAttribute("isAutoStartingEngine", "" + isAutoStartingEngine());
148        e.setAttribute("isAutoLoadingDefaultVSDFile", "" + isAutoLoadingDefaultVSDFile());
149        e.setAttribute("useBlocks", "" + getUseBlocksSetting());
150        ec = new Element("DefaultVSDFilePath");
151        ec.setText("" + getDefaultVSDFilePath());
152        e.addContent(ec);
153        ec = new Element("DefaultVSDFileName");
154        ec.setText("" + getDefaultVSDFileName());
155        e.addContent(ec);
156        // ListenerPosition generates its own XML
157        e.addContent(_listenerPosition.getXml("ListenerPosition"));
158        ec = new Element("AudioMode");
159        ec.setText("" + AudioModeMap.get(_audioMode));
160        ec = new Element("MasterVolume");
161        ec.setText("" + getMasterVolume());
162        e.addContent(ec);
163        return e;
164    }
165
166    public void set(VSDecoderPreferences tp) {
167        setAutoStartEngine(tp.isAutoStartingEngine());
168        setAutoLoadDefaultVSDFile(tp.isAutoLoadingDefaultVSDFile());
169        setUseBlocksSetting(tp.getUseBlocksSetting());
170        setDefaultVSDFilePath(tp.getDefaultVSDFilePath());
171        setDefaultVSDFileName(tp.getDefaultVSDFileName());
172        setListenerPosition(tp.getListenerPosition());
173        setAudioMode(tp.getAudioMode());
174        setMasterVolume(tp.getMasterVolume());
175
176        if (listeners != null) {
177            for (int i = 0; i < listeners.size(); i++) {
178                PropertyChangeListener l = listeners.get(i);
179                PropertyChangeEvent e = new PropertyChangeEvent(this, "VSDecoderPreferences", null, this);
180                l.propertyChange(e);
181            }
182        }
183    }
184
185    public boolean compareTo(VSDecoderPreferences tp) {
186        return (isAutoStartingEngine() != tp.isAutoStartingEngine()
187                || isAutoLoadingDefaultVSDFile() != tp.isAutoLoadingDefaultVSDFile()
188                || getUseBlocksSetting() != tp.getUseBlocksSetting()
189                || !(getDefaultVSDFilePath().equals(tp.getDefaultVSDFilePath()))
190                || !(getDefaultVSDFileName().equals(tp.getDefaultVSDFileName()))
191                || !(getListenerPosition().equals(tp.getListenerPosition()))
192                || !(getAudioMode().equals(tp.getAudioMode()))
193                || !(getMasterVolume().equals(tp.getMasterVolume())));
194    }
195
196    public void save() {
197        if (prefFile == null) {
198            return;
199        }
200        XmlFile xf = new XmlFile() {
201        };   // odd syntax is due to XmlFile being abstract
202        xf.makeBackupFile(prefFile);
203        File file = new File(prefFile);
204        try {
205            //The file does not exist, create it before writing
206            File parentDir = file.getParentFile();
207            if (!parentDir.exists()) {
208                if (!parentDir.mkdir()) { // make directory, check result
209                    log.error("failed to make parent directory");
210                }
211            }
212            if (!file.createNewFile()) { // create file, check result
213                log.error("createNewFile failed");
214            }
215        } catch (IOException | RuntimeException exp) {
216            log.error("Exception while writing the new VSDecoder preferences file, may not be complete", exp);
217        }
218
219        try {
220            Element root = new Element("vsdecoder-preferences");
221            //Document doc = XmlFile.newDocument(root, XmlFile.dtdLocation+"vsdecoder-preferences.dtd");
222            Document doc = XmlFile.newDocument(root);
223            // add XSLT processing instruction
224            // <?xml-stylesheet type="text/xsl" href="XSLT/throttle.xsl"?>
225/*TODO      java.util.Map<String,String> m = new java.util.HashMap<String,String>();
226             m.put("type", "text/xsl");
227             m.put("href", jmri.jmrit.XmlFile.xsltLocation+"throttles-preferences.xsl");
228             ProcessingInstruction p = new ProcessingInstruction("xml-stylesheet", m);
229             doc.addContent(0,p);*/
230            root.setContent(store());
231            xf.writeXML(file, doc);
232        } catch (IOException | RuntimeException ex) { // TODO fix null value for Attribute
233            log.warn("Exception in storing vsdecoder preferences xml", ex);
234        }
235    }
236
237    public String getDefaultVSDFilePath() {
238        return _defaultVSDFilePath;
239    }
240
241    public void setDefaultVSDFilePath(String s) {
242        _defaultVSDFilePath = s;
243    }
244
245    public String getDefaultVSDFileName() {
246        return _defaultVSDFileName;
247    }
248
249    public void setDefaultVSDFileName(String s) {
250        _defaultVSDFileName = s;
251    }
252
253    public boolean isAutoStartingEngine() {
254        return _autoStartEngine;
255    }
256
257    public void setAutoStartEngine(boolean b) {
258        _autoStartEngine = b;
259    }
260
261    public boolean isAutoLoadingDefaultVSDFile() {
262        return _autoLoadDefaultVSDFile;
263    }
264
265    public void setUseBlocksSetting(boolean b) {
266        _use_blocks = b;
267    }
268
269    public boolean getUseBlocksSetting() {
270        return _use_blocks;
271    }
272
273    public void setAutoLoadDefaultVSDFile(boolean b) {
274        _autoLoadDefaultVSDFile = b;
275    }
276
277    public ListeningSpot getListenerPosition() {
278        log.debug("getListenerPosition() : {}", _listenerPosition);
279        return _listenerPosition;
280    }
281
282    public void setListenerPosition(ListeningSpot p) {
283        VSDecoderManager vm = VSDecoderManager.instance();
284        vm.setListenerLocation(vm.getDefaultListenerName(), p);
285        _listenerPosition = p;
286    }
287    // Note:  No setListenerPosition(String) for ListeningSpot implementation
288
289    public PhysicalLocation getListenerPhysicalLocation() {
290        return _listenerPosition.getPhysicalLocation();
291    }
292
293    public void setListenerPosition(PhysicalLocation p) {
294        VSDecoderManager vm = VSDecoderManager.instance();
295        vm.setListenerLocation(vm.getDefaultListenerName(), new ListeningSpot(p));
296        //_listenerPosition = new ListeningSpot();
297        //_listenerPosition.setLocation(p);
298    }
299
300    public AudioMode getAudioMode() {
301        return _audioMode;
302    }
303
304    public void setAudioMode(AudioMode am) {
305        _audioMode = am;
306    }
307
308    public void setAudioMode(String am) {
309        // There's got to be a more efficient way to do this
310        Set<Map.Entry<AudioMode, String>> ids = AudioModeMap.entrySet();
311        Iterator<Map.Entry<AudioMode, String>> idi = ids.iterator();
312        while (idi.hasNext()) {
313            Map.Entry<AudioMode, String> e = idi.next();
314            log.debug("    ID = {} Val = {}", e.getKey(), e.getValue());
315            if (e.getValue().equals(am)) {
316                _audioMode = e.getKey();
317                return;
318            }
319        }
320        // We fell out of the loop.  Must be an invalid string. Set default
321        _audioMode = DefaultAudioMode;
322    }
323
324    public void setMasterVolume(int v) {
325        _masterVolume = v;
326    }
327
328    public Integer getMasterVolume() {
329        return _masterVolume;
330    }
331
332    /**
333     * Add an AddressListener.
334     * <p>
335     * AddressListeners are notified when the user
336     * selects a new address and when a Throttle is acquired for that address.
337     *
338     * @param l listener to add.
339     *
340     */
341    public void addPropertyChangeListener(PropertyChangeListener l) {
342        if (listeners == null) {
343            listeners = new ArrayList<PropertyChangeListener>(2);
344        }
345        if (!listeners.contains(l)) {
346            listeners.add(l);
347        }
348    }
349
350    /**
351     * Remove an AddressListener.
352     *
353     * @param l listener to remove.
354     */
355    public void removePropertyChangeListener(PropertyChangeListener l) {
356        if (listeners == null) {
357            return;
358        }
359        if (listeners.contains(l)) {
360            listeners.remove(l);
361        }
362    }
363
364    private final static Logger log = LoggerFactory.getLogger(VSDecoderPreferences.class);
365
366}