001package jmri.jmrit.throttle.preferences;
002
003import java.awt.Dimension;
004import java.beans.PropertyChangeEvent;
005import java.beans.PropertyChangeListener;
006import java.io.File;
007import java.io.IOException;
008import java.util.ArrayList;
009
010import jmri.jmrit.XmlFile;
011import jmri.util.FileUtil;
012
013import org.jdom2.Document;
014import org.jdom2.Element;
015import org.jdom2.JDOMException;
016import org.slf4j.Logger;
017import org.slf4j.LoggerFactory;
018
019/**
020 * A class to store JMRI throttles preferences.
021 * <p>
022 * A singleton instance is provided by a call to
023 * <code>jmri.InstanceManager.getDefault(ThrottlesPreferences.class);</code> or
024 * <code>jmri.InstanceManager.getNullableDefault(ThrottlesPreferences.class)</code>;
025 * @author Lionel Jeanson - 2009-2021
026 *
027 */
028public class ThrottlesPreferences implements jmri.InstanceManagerAutoDefault {
029
030    public static final String prefPopertyName = "ThrottlePreferences";
031
032    private boolean _useExThrottle = true;
033    private boolean _useToolBar = true;
034    private boolean _useFunctionIcon = true;
035    private boolean _useLargeSpeedSlider = true;
036    private boolean _resizeWinImg = false;
037    private boolean _useRosterImage = true;
038    private boolean _enableRosterSearch = true;
039    private boolean _enableAutoLoad = true;
040    private boolean _hideUndefinedFunButton = false;
041    private boolean _hideSpeedStepSelector = false;
042    private boolean _ignoreThrottlePosition = true;
043    private boolean _saveThrottleOnLayoutSave = true;
044    private boolean _isAddressSelectorShowingAllRosterGroup = false;
045    private boolean _isSilentSteal = false;
046    private boolean _isSilentShare = false;
047    private String _defaultThrottleFilePath = "resources/throttle/NMRA throttle.xml";
048
049    // we store default throttle layout XML elements
050    private Element adressPanelDefXML;
051    private Element controlPanelDefXML;
052    private Element functionPanelDefXML;
053    private Element throttleFrameDefXML;
054
055    private ThrottlesPreferencesWindowKeyboardControls _tpwkc = new ThrottlesPreferencesWindowKeyboardControls();
056    protected boolean dirty = false;
057
058    private Dimension _winDim = new Dimension(800, 600);
059    private String prefFile;
060    private ArrayList<PropertyChangeListener> listeners;
061
062    public ThrottlesPreferences() {
063        String dirname = FileUtil.getUserFilesPath() + "throttle" + File.separator;
064        FileUtil.createDirectory(dirname);
065        prefFile = dirname + "ThrottlesPreferences.xml";
066        ThrottlesPrefsXml prefs = new ThrottlesPrefsXml();
067        File file = new File(prefFile);
068        Element root;
069        try {
070            root = prefs.rootFromFile(file);
071        } catch (java.io.FileNotFoundException e2) {
072            log.info("Did not find throttle preferences file.  This is normal if you haven't save the preferences before");
073            root = null;
074        } catch (Exception e) {
075            log.error("Exception while loading throttles preferences", e);
076            root = null;
077        }
078        if (root != null) {
079            load(root.getChild("throttlesPreferences"));
080        } else { // at least preload default throttle layout
081            preloadDefaultThrottleFile();
082        }
083    }
084
085    public void load(org.jdom2.Element e) {
086        if (e == null) {
087            return;
088        }
089        org.jdom2.Attribute a;
090        org.jdom2.Attribute b;
091        if ((a = e.getAttribute("isUsingExThrottle")) != null) {
092            setUseExThrottle(a.getValue().compareTo("true") == 0);
093        }
094        if ((a = e.getAttribute("isUsingToolBar")) != null) {
095            setUsingToolBar(a.getValue().compareTo("true") == 0);
096        }
097        if ((a = e.getAttribute("isResizingWindow")) != null) {
098            setResizeWindow(a.getValue().compareTo("true") == 0);
099        }
100        if ((a = e.getAttribute("isUsingFunctionIcon")) != null) {
101            setUsingFunctionIcon(a.getValue().compareTo("true") == 0);
102        }
103        if (((a = e.getAttribute("windowDimensionWidth")) != null) && ((b = e.getAttribute("windowDimensionHeight")) != null)) {
104            setWindowDimension(new Dimension(Integer.parseInt(a.getValue()), Integer.parseInt(b.getValue())));
105        }
106        if ((a = e.getAttribute("isSavingThrottleOnLayoutSave")) != null) {
107            setSaveThrottleOnLayoutSave(a.getValue().compareTo("true") == 0);
108        }
109        if ((a = e.getAttribute("isUsingRosterImage")) != null) {
110            setUseRosterImage(a.getValue().compareTo("true") == 0);
111        }
112        if ((a = e.getAttribute("isEnablingRosterSearch")) != null) {
113            setEnableRosterSearch(a.getValue().compareTo("true") == 0);
114        }
115        if ((a = e.getAttribute("isAutoLoading")) != null) {
116            setAutoLoad(a.getValue().compareTo("true") == 0);
117        }
118        if ((a = e.getAttribute("isHidingUndefinedFunctionButtons")) != null) {
119            setHideUndefinedFuncButt(a.getValue().compareTo("true") == 0);
120        }
121        if ((a = e.getAttribute("isIgnoringThrottlePosition")) != null) {
122            setIgnoreThrottlePosition(a.getValue().compareTo("true") == 0);
123        }
124        if ((a = e.getAttribute("isSilentSteal")) != null) {
125            setSilentSteal(a.getValue().compareTo("true") == 0);
126        }
127        if ((a = e.getAttribute("isSilentShare")) != null) {
128            setSilentShare(a.getValue().compareTo("true") == 0);
129        }
130        if ((a = e.getAttribute("isUsingLargeSpeedSlider")) != null) {
131            setUseLargeSpeedSlider(a.getValue().compareTo("true") == 0);
132        }
133        if ((a = e.getAttribute("isHidingSpeedStepSelector")) != null) {
134            setHideSpeedStepSelector(a.getValue().compareTo("true") == 0);
135        }
136        if ((a = e.getAttribute("isAddressSelectorShowingAllRosterGroup")) != null) {
137            setAddressSelectorShowingAllRosterGroup(a.getValue().compareTo("true") == 0);
138        }                 
139        if (e.getChild("throttlesControls") != null) {
140            this._tpwkc.load(e.getChild("throttlesControls"));
141        }
142        if ((a = e.getAttribute("defaultThrottleFilePath")) != null) {
143            setDefaultThrottleFilePath(a.getValue());
144        }
145
146        this.dirty = false;
147    }
148
149    /**
150     * @return true if preferences need to be saved
151     */
152    public boolean isDirty() {
153        return dirty;
154    }
155
156    /**
157     * An extension of the abstract XmlFile. No changes made to that class.
158     *
159     */
160    static class ThrottlesPrefsXml extends XmlFile {
161    }
162
163    public Element store() {
164        org.jdom2.Element e = new org.jdom2.Element("throttlesPreferences");
165        e.setAttribute("isUsingExThrottle", "" + isUsingExThrottle());
166        e.setAttribute("isUsingToolBar", "" + isUsingToolBar());
167        e.setAttribute("isUsingFunctionIcon", "" + isUsingFunctionIcon());
168        e.setAttribute("isResizingWindow", "" + isResizingWindow());
169        e.setAttribute("windowDimensionWidth", "" + (int) getWindowDimension().getWidth());
170        e.setAttribute("windowDimensionHeight", "" + (int) getWindowDimension().getHeight());
171        e.setAttribute("isSavingThrottleOnLayoutSave", "" + isSavingThrottleOnLayoutSave());
172        e.setAttribute("isUsingRosterImage", "" + isUsingRosterImage());
173        e.setAttribute("isEnablingRosterSearch", "" + isEnablingRosterSearch());
174        e.setAttribute("isAutoLoading", "" + isAutoLoading());
175        e.setAttribute("isHidingUndefinedFunctionButtons", "" + isHidingUndefinedFuncButt());
176        e.setAttribute("isIgnoringThrottlePosition", "" + isIgnoringThrottlePosition());
177        e.setAttribute("isSilentSteal", "" + isSilentSteal());
178        e.setAttribute("isSilentShare", "" + isSilentShare());
179        e.setAttribute("isHidingSpeedStepSelector", "" + isHidingSpeedStepSelector());
180        e.setAttribute("isUsingLargeSpeedSlider", "" + isUsingLargeSpeedSlider());
181        e.setAttribute("defaultThrottleFilePath", "" + getDefaultThrottleFilePath());
182        e.setAttribute("isAddressSelectorShowingAllRosterGroup", "" + isAddressSelectorShowingAllRosterGroup());
183        java.util.ArrayList<Element> children = new java.util.ArrayList<>(1);
184        children.add(this._tpwkc.store());
185        e.setContent(children);
186        return e;
187    }
188
189    public void set(ThrottlesPreferences tp) {
190        setWindowDimension(tp.getWindowDimension());
191        setUseExThrottle(tp.isUsingExThrottle());
192        setUsingToolBar(tp.isUsingToolBar());
193        setUsingFunctionIcon(tp.isUsingFunctionIcon());
194        setResizeWindow(tp.isResizingWindow());
195        setSaveThrottleOnLayoutSave(tp.isSavingThrottleOnLayoutSave());
196        setUseRosterImage(tp.isUsingRosterImage());
197        setEnableRosterSearch(tp.isEnablingRosterSearch());
198        setAutoLoad(tp.isAutoLoading());
199        setHideUndefinedFuncButt(tp.isHidingUndefinedFuncButt());
200        setIgnoreThrottlePosition(tp.isIgnoringThrottlePosition());
201        setSilentSteal(tp.isSilentSteal());
202        setSilentShare(tp.isSilentShare());
203        setUseLargeSpeedSlider(tp.isUsingLargeSpeedSlider());
204        setThrottlesKeyboardControls(tp.getThrottlesKeyboardControls());
205        setDefaultThrottleFilePath(tp.getDefaultThrottleFilePath());
206        setHideSpeedStepSelector(tp.isHidingSpeedStepSelector());
207        setAddressSelectorShowingAllRosterGroup(tp.isAddressSelectorShowingAllRosterGroup());
208        
209        if (listeners != null) {
210            for (int i = 0; i < listeners.size(); i++) {
211                PropertyChangeListener l = listeners.get(i);
212                PropertyChangeEvent e = new PropertyChangeEvent(this, prefPopertyName, null, this);
213                l.propertyChange(e);
214            }
215        }
216    }
217
218    public void save() {
219        if (prefFile == null) {
220            return;
221        }
222        XmlFile xf = new XmlFile() {
223        };   // odd syntax is due to XmlFile being abstract
224        xf.makeBackupFile(prefFile);
225        File file = new File(prefFile);
226        try {
227            //The file does not exist, create it before writing
228            File parentDir = file.getParentFile();
229            if (!parentDir.exists()) {
230                if (!parentDir.mkdir()) // make directory, check result
231                {
232                    log.error("failed to make parent directory");
233                }
234            }
235            if (!file.createNewFile()) // create file, check result
236            {
237                log.error("createNewFile failed");
238            }
239        } catch (Exception exp) {
240            log.error("Exception while writing the new throttles preferences file, may not be complete", exp);
241        }
242
243        try {
244            Element root = new Element("throttles-preferences");
245            Document doc = XmlFile.newDocument(root, XmlFile.getDefaultDtdLocation() + "throttles-preferences.dtd");
246            // add XSLT processing instruction
247            // <?xml-stylesheet type="text/xsl" href="XSLT/throttle.xsl"?>
248/*TODO (write XSLT processing instruction)     java.util.Map<String,String> m = new java.util.HashMap<String,String>();
249             m.put("type", "text/xsl");
250             m.put("href", jmri.jmrit.XmlFile.xsltLocation+"throttles-preferences.xsl");
251             ProcessingInstruction p = new ProcessingInstruction("xml-stylesheet", m);
252             doc.addContent(0,p);*/
253            root.setContent(store());
254            xf.writeXML(file, doc);
255        } catch (java.io.IOException ex) {
256            log.warn("Exception in storing throttles preferences xml", ex);
257        }
258        this.dirty = false;
259    }
260
261    public Dimension getWindowDimension() {
262        return _winDim;
263    }
264
265    public void setWindowDimension(Dimension d) {
266        _winDim = d;
267        this.dirty = true;
268    }
269
270    public boolean isUsingExThrottle() {
271        return _useExThrottle;
272    }
273
274    public void setUseExThrottle(boolean exThrottle) {
275        _useExThrottle = exThrottle;
276        this.dirty = true;
277    }
278
279    public boolean isUsingToolBar() {
280        return _useToolBar;
281    }
282
283    public void setUsingToolBar(boolean win4all) {
284        _useToolBar = win4all;
285        this.dirty = true;
286    }
287
288    /**
289     * Check if function icons are in use.
290     *
291     * @return user preference to use function icons.
292     */
293    public boolean isUsingFunctionIcon() {
294        return _useFunctionIcon;
295    }
296
297    public void setUsingFunctionIcon(boolean useFunctionIcon) {
298        _useFunctionIcon = useFunctionIcon;
299        this.dirty = true;
300    }
301
302    public boolean isResizingWindow() {
303        return _resizeWinImg;
304    }
305
306    public void setResizeWindow(boolean winImg) {
307        _resizeWinImg = winImg;
308        this.dirty = true;
309    }
310
311    public boolean isUsingRosterImage() {
312        return _useRosterImage;
313    }
314
315    public void setUseRosterImage(boolean rosterImage) {
316        _useRosterImage = rosterImage;
317        this.dirty = true;
318    }
319
320    public boolean isEnablingRosterSearch() {
321        return _enableRosterSearch;
322    }
323
324    public void setEnableRosterSearch(boolean b) {
325        _enableRosterSearch = b;
326        this.dirty = true;
327    }
328
329    public void setAutoLoad(boolean b) {
330        _enableAutoLoad = b;
331        this.dirty = true;
332    }
333
334    public boolean isAutoLoading() {
335        return _enableAutoLoad;
336    }
337
338    public void setHideUndefinedFuncButt(boolean b) {
339        _hideUndefinedFunButton = b;
340        this.dirty = true;
341    }
342
343    public boolean isHidingUndefinedFuncButt() {
344        return _hideUndefinedFunButton;
345    }
346
347    public void setIgnoreThrottlePosition(boolean b) {
348        _ignoreThrottlePosition = b;
349        this.dirty = true;
350    }
351
352    public boolean isIgnoringThrottlePosition() {
353        return _ignoreThrottlePosition;
354    }
355
356    public void setSaveThrottleOnLayoutSave(boolean b) {
357        _saveThrottleOnLayoutSave = b;
358        this.dirty = true;
359    }
360
361    public boolean isSavingThrottleOnLayoutSave() {
362        return _saveThrottleOnLayoutSave;
363    }
364
365    public boolean isSilentSteal() {
366        return _isSilentSteal;
367    }
368
369    public boolean isSilentShare() {
370        return _isSilentShare;
371    }
372    
373    public void setSilentSteal(boolean b) {
374        _isSilentSteal = b;
375        this.dirty = true;
376    }
377
378    public void setSilentShare(boolean b) {
379        _isSilentShare = b;
380        this.dirty = true;
381    }
382
383
384    public void setUseLargeSpeedSlider(boolean b) {
385        _useLargeSpeedSlider = b;
386        this.dirty = true;
387    }
388
389    public boolean isUsingLargeSpeedSlider() {
390        return _useLargeSpeedSlider;
391    }
392
393    public void setDefaultThrottleFilePath(String p) {
394        _defaultThrottleFilePath = p;
395        this.dirty = true;
396        preloadDefaultThrottleFile();
397    }
398
399    public String getDefaultThrottleFilePath() {
400        return _defaultThrottleFilePath;
401    }
402       
403    public boolean isHidingSpeedStepSelector() {
404        return _hideSpeedStepSelector;
405    }
406
407    public void setHideSpeedStepSelector(boolean b) {
408        _hideSpeedStepSelector = b;
409        this.dirty = true;
410    }
411    
412    public boolean isAddressSelectorShowingAllRosterGroup() {
413        return _isAddressSelectorShowingAllRosterGroup;
414    }
415    
416    public void setAddressSelectorShowingAllRosterGroup(boolean b) {
417        _isAddressSelectorShowingAllRosterGroup = b;
418        this.dirty = true;
419    }
420
421    /**
422     * @return the throttles keyboard controls preferences
423     */
424    public ThrottlesPreferencesWindowKeyboardControls getThrottlesKeyboardControls() {
425        return _tpwkc;
426    }
427
428    /**
429     * Set the throttles keyboard controls preferences
430     * @param tpwkc the new keyboard preferences
431     */
432    public void setThrottlesKeyboardControls(ThrottlesPreferencesWindowKeyboardControls tpwkc) {
433        _tpwkc = tpwkc;
434    }
435
436    /**
437     * Get the default throttle address panel XML layout
438     *
439     * @return the default address panel XML layout, null if none
440     */
441    public Element getAdressPanelDefaultXML() {
442        return adressPanelDefXML;
443    }
444
445    /**
446     * Get the default throttle control panel XML layout
447     *
448     * @return the default control panel XML layout, null if none
449     */
450    public Element getControlPanelDefaultXML() {
451        return controlPanelDefXML;
452    }
453
454    /**
455     * Get the default throttle function panel XML layout
456     *
457     * @return the default function panel XML layout, null if none
458     */
459    public Element getFunctionPanelDefaultXML() {
460        return functionPanelDefXML;
461    }
462
463    public Element getThrottleFrameDefaultXML() {
464        return throttleFrameDefXML;
465    }
466
467    /**
468     * Preload the default throttle layout
469     * 
470     */
471    private void preloadDefaultThrottleFile() {        
472        adressPanelDefXML = null;
473        controlPanelDefXML = null;
474        functionPanelDefXML = null;
475        throttleFrameDefXML = null;
476
477        if (getDefaultThrottleFilePath() == null) {
478            return;
479        }
480
481        XmlFile xf = new XmlFile() {};   // odd syntax is due to XmlFile being abstract
482        xf.setValidate(XmlFile.Validate.CheckDtdThenSchema);
483        File f = new File(getDefaultThrottleFilePath());
484        Element root;
485        try {
486            root = xf.rootFromFile(f);
487        } catch ( JDOMException | IOException e) {
488            log.debug("Exception while preloading default throttle file", e);
489            return;
490        }
491        throttleFrameDefXML = root.getChild("ThrottleFrame");
492        if (throttleFrameDefXML == null) {
493            return;
494        }
495        adressPanelDefXML = throttleFrameDefXML.getChild("AddressPanel");
496        controlPanelDefXML = throttleFrameDefXML.getChild("ControlPanel");
497        functionPanelDefXML = throttleFrameDefXML.getChild("FunctionPanel");
498    }
499
500    /**
501     * Add a PropertyChangeListener.
502     * AddressListeners are notified when the throtlle preferences are updated
503     * @param l listener to add.
504     *
505     */
506    public void addPropertyChangeListener(PropertyChangeListener l) {
507        if (listeners == null) {
508            listeners = new ArrayList<>(2);
509        }
510        if (!listeners.contains(l)) {
511            listeners.add(l);
512        }
513    }
514
515    /**
516     * Remove a PropertyChangeListener.
517     * @param l listener to remove.
518     */
519    public void removePropertyChangeListener(PropertyChangeListener l) {
520        if (listeners == null) {
521            return;
522        }
523        listeners.remove(l);
524    }
525
526    private static final Logger log = LoggerFactory.getLogger(ThrottlesPreferences.class);
527}