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 String _defaultVSDFilePath = null;
060    private String _defaultVSDFileName = null;
061    private boolean _autoLoadDefaultVSDFile = false; // Automatically load a VSD file.
062    private ListeningSpot _listenerPosition;
063    private AudioMode _audioMode;
064    private int _masterVolume;
065
066    // Other internal variables
067    //private Dimension _winDim = new Dimension(800,600);
068    private String prefFile;
069    private ArrayList<PropertyChangeListener> listeners;
070
071    public VSDecoderPreferences(String sfile) {
072        prefFile = sfile;
073        VSDecoderPrefsXml prefs = new VSDecoderPrefsXml();
074        File file = new File(prefFile);
075        Element root;
076
077        // Set default values
078        _defaultVSDFilePath = FileUtil.getExternalFilename("program:resources/vsdecoder");
079        _defaultVSDFileName = "example.vsd";
080        _listenerPosition = new ListeningSpot(); // default to (0, 0, 0) Orientation (0,1,0)
081        _audioMode = DefaultAudioMode;
082        _masterVolume = DefaultMasterVolume;
083
084        // Try to load preferences from the file
085        try {
086            root = prefs.rootFromFile(file);
087        } catch (IOException e2) {
088            log.info("Did not find VSDecoder preferences file.  This is normal if you haven't save the preferences before");
089            root = null;
090        } catch (JDOMException | RuntimeException e) {
091            log.error("Exception while loading VSDecoder preferences: {}", e);
092            root = null;
093        }
094        if (root != null) {
095            load(root.getChild("VSDecoderPreferences"));
096        }
097    }
098
099    public VSDecoderPreferences() {
100    }
101
102    public void load(org.jdom2.Element e) {
103        if (e == null) {
104            return;
105        }
106        org.jdom2.Attribute a;
107        org.jdom2.Element c;
108        if ((a = e.getAttribute("isAutoStartingEngine")) != null) {
109            setAutoStartEngine(a.getValue().compareTo("true") == 0);
110        }
111        if ((a = e.getAttribute("isAutoLoadingDefaultVSDFile")) != null) {
112            setAutoLoadDefaultVSDFile(a.getValue().compareTo("true") == 0);
113        }
114        if ((c = e.getChild("DefaultVSDFilePath")) != null) {
115            setDefaultVSDFilePath(c.getValue());
116        }
117        if ((c = e.getChild("DefaultVSDFileName")) != null) {
118            setDefaultVSDFileName(c.getValue());
119        }
120        if ((c = e.getChild("ListenerPosition")) != null) {
121            _listenerPosition = new ListeningSpot(c);
122        } else {
123            _listenerPosition = new ListeningSpot();
124        }
125        if ((c = e.getChild("AudioMode")) != null) {
126            setAudioMode(c.getValue());
127        }
128        if ((c = e.getChild("MasterVolume")) != null) {
129            setMasterVolume(Integer.parseInt(c.getValue()));
130        }
131    }
132
133    /**
134     * An extension of the abstract XmlFile. No changes made to that class.
135     *
136     */
137    static class VSDecoderPrefsXml extends XmlFile {
138    }
139
140    private org.jdom2.Element store() {
141        org.jdom2.Element ec;
142        org.jdom2.Element e = new org.jdom2.Element("VSDecoderPreferences");
143        e.setAttribute("isAutoStartingEngine", "" + isAutoStartingEngine());
144        e.setAttribute("isAutoLoadingDefaultVSDFile", "" + isAutoLoadingDefaultVSDFile());
145        ec = new Element("DefaultVSDFilePath");
146        ec.setText("" + getDefaultVSDFilePath());
147        e.addContent(ec);
148        ec = new Element("DefaultVSDFileName");
149        ec.setText("" + getDefaultVSDFileName());
150        e.addContent(ec);
151        // ListenerPosition generates its own XML
152        e.addContent(_listenerPosition.getXml("ListenerPosition"));
153        ec = new Element("AudioMode");
154        ec.setText("" + AudioModeMap.get(_audioMode));
155        ec = new Element("MasterVolume");
156        ec.setText("" + getMasterVolume());
157        e.addContent(ec);
158        return e;
159    }
160
161    public void set(VSDecoderPreferences tp) {
162        setAutoStartEngine(tp.isAutoStartingEngine());
163        setAutoLoadDefaultVSDFile(tp.isAutoLoadingDefaultVSDFile());
164        setDefaultVSDFilePath(tp.getDefaultVSDFilePath());
165        setDefaultVSDFileName(tp.getDefaultVSDFileName());
166        setListenerPosition(tp.getListenerPosition());
167        setAudioMode(tp.getAudioMode());
168        setMasterVolume(tp.getMasterVolume());
169
170        if (listeners != null) {
171            for (int i = 0; i < listeners.size(); i++) {
172                PropertyChangeListener l = listeners.get(i);
173                PropertyChangeEvent e = new PropertyChangeEvent(this, "VSDecoderPreferences", null, this);
174                l.propertyChange(e);
175            }
176        }
177    }
178
179    public boolean compareTo(VSDecoderPreferences tp) {
180        return (isAutoStartingEngine() != tp.isAutoStartingEngine()
181                || isAutoLoadingDefaultVSDFile() != tp.isAutoLoadingDefaultVSDFile()
182                || !(getDefaultVSDFilePath().equals(tp.getDefaultVSDFilePath()))
183                || !(getDefaultVSDFileName().equals(tp.getDefaultVSDFileName()))
184                || !(getListenerPosition().equals(tp.getListenerPosition()))
185                || !(getAudioMode().equals(tp.getAudioMode()))
186                || !(getMasterVolume().equals(tp.getMasterVolume())));
187    }
188
189    public void save() {
190        if (prefFile == null) {
191            return;
192        }
193        XmlFile xf = new XmlFile() {
194        };   // odd syntax is due to XmlFile being abstract
195        xf.makeBackupFile(prefFile);
196        File file = new File(prefFile);
197        try {
198            //The file does not exist, create it before writing
199            File parentDir = file.getParentFile();
200            if (!parentDir.exists()) {
201                if (!parentDir.mkdir()) { // make directory, check result
202                    log.error("failed to make parent directory");
203                }
204            }
205            if (!file.createNewFile()) { // create file, check result
206                log.error("createNewFile failed");
207            }
208        } catch (IOException | RuntimeException exp) {
209            log.error("Exception while writing the new VSDecoder preferences file, may not be complete: {}", exp);
210        }
211
212        try {
213            Element root = new Element("vsdecoder-preferences");
214            //Document doc = XmlFile.newDocument(root, XmlFile.dtdLocation+"vsdecoder-preferences.dtd");
215            Document doc = XmlFile.newDocument(root);
216            // add XSLT processing instruction
217            // <?xml-stylesheet type="text/xsl" href="XSLT/throttle.xsl"?>
218/*TODO      java.util.Map<String,String> m = new java.util.HashMap<String,String>();
219             m.put("type", "text/xsl");
220             m.put("href", jmri.jmrit.XmlFile.xsltLocation+"throttles-preferences.xsl");
221             ProcessingInstruction p = new ProcessingInstruction("xml-stylesheet", m);
222             doc.addContent(0,p);*/
223            root.setContent(store());
224            xf.writeXML(file, doc);
225        } catch (IOException | RuntimeException ex) { // TODO fix null value for Attribute
226            log.warn("Exception in storing vsdecoder preferences xml: {}", ex);
227        }
228    }
229
230    public String getDefaultVSDFilePath() {
231        return _defaultVSDFilePath;
232    }
233
234    public void setDefaultVSDFilePath(String s) {
235        _defaultVSDFilePath = s;
236    }
237
238    public String getDefaultVSDFileName() {
239        return _defaultVSDFileName;
240    }
241
242    public void setDefaultVSDFileName(String s) {
243        _defaultVSDFileName = s;
244    }
245
246    public boolean isAutoStartingEngine() {
247        return _autoStartEngine;
248    }
249
250    public void setAutoStartEngine(boolean b) {
251        _autoStartEngine = b;
252    }
253
254    public boolean isAutoLoadingDefaultVSDFile() {
255        return _autoLoadDefaultVSDFile;
256    }
257
258    public void setAutoLoadDefaultVSDFile(boolean b) {
259        _autoLoadDefaultVSDFile = b;
260    }
261
262    public ListeningSpot getListenerPosition() {
263        log.debug("getListenerPosition() : {}", _listenerPosition);
264        return _listenerPosition;
265    }
266
267    public void setListenerPosition(ListeningSpot p) {
268        VSDecoderManager vm = VSDecoderManager.instance();
269        vm.setListenerLocation(vm.getDefaultListenerName(), p);
270        _listenerPosition = p;
271    }
272    // Note:  No setListenerPosition(String) for ListeningSpot implementation
273
274    public PhysicalLocation getListenerPhysicalLocation() {
275        return _listenerPosition.getPhysicalLocation();
276    }
277
278    public void setListenerPosition(PhysicalLocation p) {
279        VSDecoderManager vm = VSDecoderManager.instance();
280        vm.setListenerLocation(vm.getDefaultListenerName(), new ListeningSpot(p));
281        //_listenerPosition = new ListeningSpot();
282        //_listenerPosition.setLocation(p);
283    }
284
285    public AudioMode getAudioMode() {
286        return _audioMode;
287    }
288
289    public void setAudioMode(AudioMode am) {
290        _audioMode = am;
291    }
292
293    public void setAudioMode(String am) {
294        // There's got to be a more efficient way to do this
295        Set<Map.Entry<AudioMode, String>> ids = AudioModeMap.entrySet();
296        Iterator<Map.Entry<AudioMode, String>> idi = ids.iterator();
297        while (idi.hasNext()) {
298            Map.Entry<AudioMode, String> e = idi.next();
299            log.debug("    ID = {} Val = {}", e.getKey(), e.getValue());
300            if (e.getValue().equals(am)) {
301                _audioMode = e.getKey();
302                return;
303            }
304        }
305        // We fell out of the loop.  Must be an invalid string. Set default
306        _audioMode = DefaultAudioMode;
307    }
308
309    public void setMasterVolume(int v) {
310        _masterVolume = v;
311    }
312
313    public Integer getMasterVolume() {
314        return _masterVolume;
315    }
316
317    /**
318     * Add an AddressListener.
319     * <p>
320     * AddressListeners are notified when the user
321     * selects a new address and when a Throttle is acquired for that address.
322     * 
323     * @param l listener to add.
324     *
325     */
326    public void addPropertyChangeListener(PropertyChangeListener l) {
327        if (listeners == null) {
328            listeners = new ArrayList<PropertyChangeListener>(2);
329        }
330        if (!listeners.contains(l)) {
331            listeners.add(l);
332        }
333    }
334
335    /**
336     * Remove an AddressListener.
337     *
338     * @param l listener to remove.
339     */
340    public void removePropertyChangeListener(PropertyChangeListener l) {
341        if (listeners == null) {
342            return;
343        }
344        if (listeners.contains(l)) {
345            listeners.remove(l);
346        }
347    }
348
349    private final static Logger log = LoggerFactory.getLogger(VSDecoderPreferences.class);
350
351}